mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
72 Commits
alpha-1.2.
...
attachment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6b15fe657 | ||
|
|
f3bbc7179e | ||
|
|
9abe32ab4b | ||
|
|
d07b98eae1 | ||
|
|
34583e6d2d | ||
|
|
ea5a862242 | ||
|
|
3f60098099 | ||
|
|
e965021e3d | ||
|
|
7d9380d3d6 | ||
|
|
3c8c0e579e | ||
|
|
bd2bbe9268 | ||
|
|
89d24b1753 | ||
|
|
861dbe20b1 | ||
|
|
197800de8b | ||
|
|
07e824ad68 | ||
|
|
d210215bd1 | ||
|
|
00705447ec | ||
|
|
9095ccef85 | ||
|
|
3196204094 | ||
|
|
2bae639105 | ||
|
|
f73d298752 | ||
|
|
bc3a443276 | ||
|
|
2a29d33303 | ||
|
|
30e0be9f43 | ||
|
|
3828d16971 | ||
|
|
a54eb64eb5 | ||
|
|
ad2d3e70d6 | ||
|
|
1f91842c52 | ||
|
|
c07a0a2fd7 | ||
|
|
4ee4905e06 | ||
|
|
67b7517f2b | ||
|
|
cd3174a643 | ||
|
|
9d9bc4ca84 | ||
|
|
7358091699 | ||
|
|
11eefaedcf | ||
|
|
bb5a6c0241 | ||
|
|
70d29af2ba | ||
|
|
baedb14e2b | ||
|
|
2796926709 | ||
|
|
fc6275b037 | ||
|
|
f76f9be4ed | ||
|
|
6167ba5c46 | ||
|
|
55f4600a69 | ||
|
|
c73801c7e8 | ||
|
|
249e1e28fe | ||
|
|
f0cea28aeb | ||
|
|
32e8ea9888 | ||
|
|
5a1caed89f | ||
|
|
22f5c42fc1 | ||
|
|
aab46040a5 | ||
|
|
18fd238aa1 | ||
|
|
3a837b3c5a | ||
|
|
ac2597865c | ||
|
|
4a67cf3ce7 | ||
|
|
a5041e651e | ||
|
|
b0e97d787f | ||
|
|
0d8af780a3 | ||
|
|
9c20e6b333 | ||
|
|
ab14976c96 | ||
|
|
ec3f821ba6 | ||
|
|
1d546da781 | ||
|
|
f2c951b70b | ||
|
|
1e259c100d | ||
|
|
3636aeba9a | ||
|
|
132e20a6ce | ||
|
|
c228e5c219 | ||
|
|
ae1d1fc5a7 | ||
|
|
37f02a40e9 | ||
|
|
3c8b8c39e1 | ||
|
|
8f839e2c30 | ||
|
|
da4b63f20f | ||
|
|
cd40e771d2 |
@@ -0,0 +1,11 @@
|
|||||||
|
package org.briarproject.bramble.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for specifying which features are enabled in a build.
|
||||||
|
*/
|
||||||
|
public interface FeatureFlags {
|
||||||
|
|
||||||
|
boolean shouldEnableImageAttachments();
|
||||||
|
|
||||||
|
boolean shouldEnableRemoteContacts();
|
||||||
|
}
|
||||||
@@ -25,7 +25,10 @@ public interface ClientHelper {
|
|||||||
throws DbException, FormatException;
|
throws DbException, FormatException;
|
||||||
|
|
||||||
void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata,
|
void addLocalMessage(Transaction txn, Message m, BdfDictionary metadata,
|
||||||
boolean shared) throws DbException, FormatException;
|
boolean shared, boolean temporary)
|
||||||
|
throws DbException, FormatException;
|
||||||
|
|
||||||
|
Message createMessage(GroupId g, long timestamp, byte[] body);
|
||||||
|
|
||||||
Message createMessage(GroupId g, long timestamp, BdfList body)
|
Message createMessage(GroupId g, long timestamp, BdfList body)
|
||||||
throws FormatException;
|
throws FormatException;
|
||||||
@@ -108,7 +111,7 @@ public interface ClientHelper {
|
|||||||
Author parseAndValidateAuthor(BdfList author) throws FormatException;
|
Author parseAndValidateAuthor(BdfList author) throws FormatException;
|
||||||
|
|
||||||
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
|
PublicKey parseAndValidateAgreementPublicKey(byte[] publicKeyBytes)
|
||||||
throws FormatException;
|
throws FormatException;
|
||||||
|
|
||||||
TransportProperties parseAndValidateTransportProperties(
|
TransportProperties parseAndValidateTransportProperties(
|
||||||
BdfDictionary properties) throws FormatException;
|
BdfDictionary properties) throws FormatException;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
|
|||||||
public enum PendingContactState {
|
public enum PendingContactState {
|
||||||
|
|
||||||
WAITING_FOR_CONNECTION,
|
WAITING_FOR_CONNECTION,
|
||||||
|
OFFLINE,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
ADDING_CONTACT,
|
ADDING_CONTACT,
|
||||||
FAILED
|
FAILED
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.briarproject.bramble.api.transport.TransportKeySet;
|
|||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -77,7 +78,7 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
* Stores a local message.
|
* Stores a local message.
|
||||||
*/
|
*/
|
||||||
void addLocalMessage(Transaction txn, Message m, Metadata meta,
|
void addLocalMessage(Transaction txn, Message m, Metadata meta,
|
||||||
boolean shared) throws DbException;
|
boolean shared, boolean temporary) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a pending contact.
|
* Stores a pending contact.
|
||||||
@@ -427,6 +428,13 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
*/
|
*/
|
||||||
Settings getSettings(Transaction txn, String namespace) throws DbException;
|
Settings getSettings(Transaction txn, String namespace) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the versions of the sync protocol supported by the given contact.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
List<Byte> getSyncVersions(Transaction txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all transport keys for the given transport.
|
* Returns all transport keys for the given transport.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -510,6 +518,12 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void removePendingContact(Transaction txn, PendingContactId p)
|
void removePendingContact(Transaction txn, PendingContactId p)
|
||||||
throws DbException;
|
throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all temporary messages (and all associated state) from the
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
void removeTemporaryMessages(Transaction txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a transport (and all associated state) from the database.
|
* Removes a transport (and all associated state) from the database.
|
||||||
*/
|
*/
|
||||||
@@ -538,6 +552,11 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void setGroupVisibility(Transaction txn, ContactId c, GroupId g,
|
void setGroupVisibility(Transaction txn, ContactId c, GroupId g,
|
||||||
Visibility v) throws DbException;
|
Visibility v) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the given message as permanent, i.e. not temporary.
|
||||||
|
*/
|
||||||
|
void setMessagePermanent(Transaction txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given message as shared.
|
* Marks the given message as shared.
|
||||||
*/
|
*/
|
||||||
@@ -568,6 +587,12 @@ public interface DatabaseComponent extends TransactionManager {
|
|||||||
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
|
void setReorderingWindow(Transaction txn, KeySetId k, TransportId t,
|
||||||
long timePeriod, long base, byte[] bitmap) throws DbException;
|
long timePeriod, long base, byte[] bitmap) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the versions of the sync protocol supported by the given contact.
|
||||||
|
*/
|
||||||
|
void setSyncVersions(Transaction txn, ContactId c, List<Byte> supported)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given transport keys as usable for outgoing streams.
|
* Marks the given transport keys as usable for outgoing streams.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
package org.briarproject.bramble.api.sync;
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A record acknowledging receipt of one or more {@link Message Messages}.
|
* A record acknowledging receipt of one or more {@link Message Messages}.
|
||||||
*/
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
public class Ack {
|
public class Ack {
|
||||||
|
|
||||||
private final Collection<MessageId> acked;
|
private final Collection<MessageId> acked;
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
package org.briarproject.bramble.api.sync;
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
public class Message {
|
public class Message {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
package org.briarproject.bramble.api.sync;
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A record offering the recipient one or more {@link Message Messages}.
|
* A record offering the recipient one or more {@link Message Messages}.
|
||||||
*/
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
public class Offer {
|
public class Offer {
|
||||||
|
|
||||||
private final Collection<MessageId> offered;
|
private final Collection<MessageId> offered;
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ public interface RecordTypes {
|
|||||||
byte MESSAGE = 1;
|
byte MESSAGE = 1;
|
||||||
byte OFFER = 2;
|
byte OFFER = 2;
|
||||||
byte REQUEST = 3;
|
byte REQUEST = 3;
|
||||||
|
byte VERSIONS = 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
package org.briarproject.bramble.api.sync;
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A record requesting one or more {@link Message Messages} from the recipient.
|
* A record requesting one or more {@link Message Messages} from the recipient.
|
||||||
*/
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
public class Request {
|
public class Request {
|
||||||
|
|
||||||
private final Collection<MessageId> requested;
|
private final Collection<MessageId> requested;
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package org.briarproject.bramble.api.sync;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.UniqueId;
|
import org.briarproject.bramble.api.UniqueId;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
|
|
||||||
public interface SyncConstants {
|
public interface SyncConstants {
|
||||||
@@ -11,6 +14,11 @@ public interface SyncConstants {
|
|||||||
*/
|
*/
|
||||||
byte PROTOCOL_VERSION = 0;
|
byte PROTOCOL_VERSION = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The versions of the sync protocol this peer supports.
|
||||||
|
*/
|
||||||
|
List<Byte> SUPPORTED_VERSIONS = singletonList(PROTOCOL_VERSION);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum length of a group descriptor in bytes.
|
* The maximum length of a group descriptor in bytes.
|
||||||
*/
|
*/
|
||||||
@@ -35,4 +43,10 @@ public interface SyncConstants {
|
|||||||
* The maximum number of message IDs in an ack, offer or request record.
|
* The maximum number of message IDs in an ack, offer or request record.
|
||||||
*/
|
*/
|
||||||
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
|
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_BYTES / UniqueId.LENGTH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of versions of the sync protocol a peer may support
|
||||||
|
* simultaneously.
|
||||||
|
*/
|
||||||
|
int MAX_SUPPORTED_VERSIONS = 10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,4 +25,7 @@ public interface SyncRecordReader {
|
|||||||
|
|
||||||
Request readRequest() throws IOException;
|
Request readRequest() throws IOException;
|
||||||
|
|
||||||
|
boolean hasVersions() throws IOException;
|
||||||
|
|
||||||
|
Versions readVersions() throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,5 +15,7 @@ public interface SyncRecordWriter {
|
|||||||
|
|
||||||
void writeRequest(Request r) throws IOException;
|
void writeRequest(Request r) throws IOException;
|
||||||
|
|
||||||
|
void writeVersions(Versions v) throws IOException;
|
||||||
|
|
||||||
void flush() throws IOException;
|
void flush() throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.briarproject.bramble.api.sync;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A record telling the recipient which versions of the sync protocol the
|
||||||
|
* sender supports.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class Versions {
|
||||||
|
|
||||||
|
private final List<Byte> supported;
|
||||||
|
|
||||||
|
public Versions(List<Byte> supported) {
|
||||||
|
this.supported = supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Byte> getSupportedVersions() {
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.briarproject.bramble.api.sync.event;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is broadcast when the versions of the sync protocol supported
|
||||||
|
* by a contact are updated.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class SyncVersionsUpdatedEvent extends Event {
|
||||||
|
|
||||||
|
private final ContactId contactId;
|
||||||
|
private final List<Byte> supported;
|
||||||
|
|
||||||
|
public SyncVersionsUpdatedEvent(ContactId contactId, List<Byte> supported) {
|
||||||
|
this.contactId = contactId;
|
||||||
|
this.supported = supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContactId getContactId() {
|
||||||
|
return contactId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Byte> getSupportedVersions() {
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,14 +85,21 @@ class ClientHelperImpl implements ClientHelper {
|
|||||||
@Override
|
@Override
|
||||||
public void addLocalMessage(Message m, BdfDictionary metadata,
|
public void addLocalMessage(Message m, BdfDictionary metadata,
|
||||||
boolean shared) throws DbException, FormatException {
|
boolean shared) throws DbException, FormatException {
|
||||||
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared));
|
db.transaction(false, txn -> addLocalMessage(txn, m, metadata, shared,
|
||||||
|
false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addLocalMessage(Transaction txn, Message m,
|
public void addLocalMessage(Transaction txn, Message m,
|
||||||
BdfDictionary metadata, boolean shared)
|
BdfDictionary metadata, boolean shared, boolean temporary)
|
||||||
throws DbException, FormatException {
|
throws DbException, FormatException {
|
||||||
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared);
|
db.addLocalMessage(txn, m, metadataEncoder.encode(metadata), shared,
|
||||||
|
temporary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message createMessage(GroupId g, long timestamp, byte[] body) {
|
||||||
|
return messageFactory.createMessage(g, timestamp, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -147,7 +147,6 @@ class ContactManagerImpl implements ContactManager, EventListener {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction(txn);
|
db.endTransaction(txn);
|
||||||
}
|
}
|
||||||
states.put(p.getId(), WAITING_FOR_CONNECTION);
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import org.briarproject.bramble.api.transport.TransportKeySet;
|
|||||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@@ -115,7 +116,7 @@ interface Database<T> {
|
|||||||
* if the message was created locally.
|
* if the message was created locally.
|
||||||
*/
|
*/
|
||||||
void addMessage(T txn, Message m, MessageState state, boolean shared,
|
void addMessage(T txn, Message m, MessageState state, boolean shared,
|
||||||
@Nullable ContactId sender) throws DbException;
|
boolean temporary, @Nullable ContactId sender) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a dependency between two messages, where the dependent message is
|
* Adds a dependency between two messages, where the dependent message is
|
||||||
@@ -528,6 +529,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Settings getSettings(T txn, String namespace) throws DbException;
|
Settings getSettings(T txn, String namespace) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the versions of the sync protocol supported by the given contact.
|
||||||
|
* <p/>
|
||||||
|
* Read-only.
|
||||||
|
*/
|
||||||
|
List<Byte> getSyncVersions(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all transport keys for the given transport.
|
* Returns all transport keys for the given transport.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -630,6 +638,12 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void removePendingContact(T txn, PendingContactId p) throws DbException;
|
void removePendingContact(T txn, PendingContactId p) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all temporary messages (and all associated state) from the
|
||||||
|
* database.
|
||||||
|
*/
|
||||||
|
void removeTemporaryMessages(T txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a transport (and all associated state) from the database.
|
* Removes a transport (and all associated state) from the database.
|
||||||
*/
|
*/
|
||||||
@@ -671,6 +685,11 @@ interface Database<T> {
|
|||||||
void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey,
|
void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey,
|
||||||
PrivateKey privateKey) throws DbException;
|
PrivateKey privateKey) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the given message as permanent, i.e. not temporary.
|
||||||
|
*/
|
||||||
|
void setMessagePermanent(T txn, MessageId m) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given message as shared.
|
* Marks the given message as shared.
|
||||||
*/
|
*/
|
||||||
@@ -689,6 +708,12 @@ interface Database<T> {
|
|||||||
void setReorderingWindow(T txn, KeySetId k, TransportId t,
|
void setReorderingWindow(T txn, KeySetId k, TransportId t,
|
||||||
long timePeriod, long base, byte[] bitmap) throws DbException;
|
long timePeriod, long base, byte[] bitmap) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the versions of the sync protocol supported by the given contact.
|
||||||
|
*/
|
||||||
|
void setSyncVersions(T txn, ContactId c, List<Byte> supported)
|
||||||
|
throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks the given transport keys as usable for outgoing streams.
|
* Marks the given transport keys as usable for outgoing streams.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
|
|||||||
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
import org.briarproject.bramble.api.sync.event.MessageToRequestEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||||
|
import org.briarproject.bramble.api.sync.event.SyncVersionsUpdatedEvent;
|
||||||
import org.briarproject.bramble.api.sync.validation.MessageState;
|
import org.briarproject.bramble.api.sync.validation.MessageState;
|
||||||
import org.briarproject.bramble.api.transport.KeySetId;
|
import org.briarproject.bramble.api.transport.KeySetId;
|
||||||
import org.briarproject.bramble.api.transport.TransportKeySet;
|
import org.briarproject.bramble.api.transport.TransportKeySet;
|
||||||
@@ -273,13 +274,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addLocalMessage(Transaction transaction, Message m,
|
public void addLocalMessage(Transaction transaction, Message m,
|
||||||
Metadata meta, boolean shared) throws DbException {
|
Metadata meta, boolean shared, boolean temporary)
|
||||||
|
throws DbException {
|
||||||
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
T txn = unbox(transaction);
|
T txn = unbox(transaction);
|
||||||
if (!db.containsGroup(txn, m.getGroupId()))
|
if (!db.containsGroup(txn, m.getGroupId()))
|
||||||
throw new NoSuchGroupException();
|
throw new NoSuchGroupException();
|
||||||
if (!db.containsMessage(txn, m.getId())) {
|
if (!db.containsMessage(txn, m.getId())) {
|
||||||
db.addMessage(txn, m, DELIVERED, shared, null);
|
db.addMessage(txn, m, DELIVERED, shared, temporary, null);
|
||||||
transaction.attach(new MessageAddedEvent(m, null));
|
transaction.attach(new MessageAddedEvent(m, null));
|
||||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||||
DELIVERED));
|
DELIVERED));
|
||||||
@@ -715,6 +717,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
return db.getSettings(txn, namespace);
|
return db.getSettings(txn, namespace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Byte> getSyncVersions(Transaction transaction, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
return db.getSyncVersions(txn, c);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<TransportKeySet> getTransportKeys(Transaction transaction,
|
public Collection<TransportKeySet> getTransportKeys(Transaction transaction,
|
||||||
TransportId t) throws DbException {
|
TransportId t) throws DbException {
|
||||||
@@ -800,7 +811,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.raiseSeenFlag(txn, c, m.getId());
|
db.raiseSeenFlag(txn, c, m.getId());
|
||||||
db.raiseAckFlag(txn, c, m.getId());
|
db.raiseAckFlag(txn, c, m.getId());
|
||||||
} else {
|
} else {
|
||||||
db.addMessage(txn, m, UNKNOWN, false, c);
|
db.addMessage(txn, m, UNKNOWN, false, false, c);
|
||||||
transaction.attach(new MessageAddedEvent(m, c));
|
transaction.attach(new MessageAddedEvent(m, c));
|
||||||
}
|
}
|
||||||
transaction.attach(new MessageToAckEvent(c));
|
transaction.attach(new MessageToAckEvent(c));
|
||||||
@@ -908,6 +919,14 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
transaction.attach(new PendingContactRemovedEvent(p));
|
transaction.attach(new PendingContactRemovedEvent(p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeTemporaryMessages(Transaction transaction)
|
||||||
|
throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
db.removeTemporaryMessages(txn);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeTransport(Transaction transaction, TransportId t)
|
public void removeTransport(Transaction transaction, TransportId t)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -967,6 +986,16 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMessagePermanent(Transaction transaction, MessageId m)
|
||||||
|
throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsMessage(txn, m))
|
||||||
|
throw new NoSuchMessageException();
|
||||||
|
db.setMessagePermanent(txn, m);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessageShared(Transaction transaction, MessageId m)
|
public void setMessageShared(Transaction transaction, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -975,8 +1004,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
if (!db.containsMessage(txn, m))
|
if (!db.containsMessage(txn, m))
|
||||||
throw new NoSuchMessageException();
|
throw new NoSuchMessageException();
|
||||||
if (db.getMessageState(txn, m) != DELIVERED)
|
if (db.getMessageState(txn, m) != DELIVERED)
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException("Shared undelivered message");
|
||||||
"Shared undelivered message");
|
|
||||||
db.setMessageShared(txn, m);
|
db.setMessageShared(txn, m);
|
||||||
transaction.attach(new MessageSharedEvent(m));
|
transaction.attach(new MessageSharedEvent(m));
|
||||||
}
|
}
|
||||||
@@ -1028,6 +1056,17 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
|||||||
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap);
|
db.setReorderingWindow(txn, k, t, timePeriod, base, bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSyncVersions(Transaction transaction, ContactId c,
|
||||||
|
List<Byte> supported) throws DbException {
|
||||||
|
if (transaction.isReadOnly()) throw new IllegalArgumentException();
|
||||||
|
T txn = unbox(transaction);
|
||||||
|
if (!db.containsContact(txn, c))
|
||||||
|
throw new NoSuchContactException();
|
||||||
|
db.setSyncVersions(txn, c, supported);
|
||||||
|
transaction.attach(new SyncVersionsUpdatedEvent(c, supported));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTransportKeysActive(Transaction transaction, TransportId t,
|
public void setTransportKeysActive(Transaction transaction, TransportId t,
|
||||||
KeySetId k) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
|
|
||||||
import static java.sql.Types.BINARY;
|
import static java.sql.Types.BINARY;
|
||||||
import static java.sql.Types.BOOLEAN;
|
import static java.sql.Types.BOOLEAN;
|
||||||
@@ -97,7 +98,7 @@ import static org.briarproject.bramble.util.LogUtils.now;
|
|||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
// Package access for testing
|
// Package access for testing
|
||||||
static final int CODE_SCHEMA_VERSION = 45;
|
static final int CODE_SCHEMA_VERSION = 47;
|
||||||
|
|
||||||
// Time period offsets for incoming transport keys
|
// Time period offsets for incoming transport keys
|
||||||
private static final int OFFSET_PREV = -1;
|
private static final int OFFSET_PREV = -1;
|
||||||
@@ -134,6 +135,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " handshakePublicKey _BINARY," // Null if key is unknown
|
+ " handshakePublicKey _BINARY," // Null if key is unknown
|
||||||
+ " localAuthorId _HASH NOT NULL,"
|
+ " localAuthorId _HASH NOT NULL,"
|
||||||
+ " verified BOOLEAN NOT NULL,"
|
+ " verified BOOLEAN NOT NULL,"
|
||||||
|
+ " syncVersions _BINARY DEFAULT '00' NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId),"
|
+ " PRIMARY KEY (contactId),"
|
||||||
+ " FOREIGN KEY (localAuthorId)"
|
+ " FOREIGN KEY (localAuthorId)"
|
||||||
+ " REFERENCES localAuthors (authorId)"
|
+ " REFERENCES localAuthors (authorId)"
|
||||||
@@ -177,6 +179,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " timestamp BIGINT NOT NULL,"
|
+ " timestamp BIGINT NOT NULL,"
|
||||||
+ " state INT NOT NULL,"
|
+ " state INT NOT NULL,"
|
||||||
+ " shared BOOLEAN NOT NULL,"
|
+ " shared BOOLEAN NOT NULL,"
|
||||||
|
+ " temporary BOOLEAN NOT NULL,"
|
||||||
+ " length INT NOT NULL,"
|
+ " length INT NOT NULL,"
|
||||||
+ " raw BLOB," // Null if message has been deleted
|
+ " raw BLOB," // Null if message has been deleted
|
||||||
+ " PRIMARY KEY (messageId),"
|
+ " PRIMARY KEY (messageId),"
|
||||||
@@ -336,25 +339,26 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(JdbcDatabase.class.getName());
|
getLogger(JdbcDatabase.class.getName());
|
||||||
|
|
||||||
// Different database libraries use different names for certain types
|
|
||||||
private final MessageFactory messageFactory;
|
private final MessageFactory messageFactory;
|
||||||
private final Clock clock;
|
private final Clock clock;
|
||||||
private final DatabaseTypes dbTypes;
|
private final DatabaseTypes dbTypes;
|
||||||
|
|
||||||
// Locking: connectionsLock
|
private final Lock connectionsLock = new ReentrantLock();
|
||||||
|
private final Condition connectionsChanged = connectionsLock.newCondition();
|
||||||
|
|
||||||
|
@GuardedBy("connectionsLock")
|
||||||
private final LinkedList<Connection> connections = new LinkedList<>();
|
private final LinkedList<Connection> connections = new LinkedList<>();
|
||||||
|
|
||||||
private int openConnections = 0; // Locking: connectionsLock
|
@GuardedBy("connectionsLock")
|
||||||
private boolean closed = false; // Locking: connectionsLock
|
private int openConnections = 0;
|
||||||
|
@GuardedBy("connectionsLock")
|
||||||
|
private boolean closed = false;
|
||||||
|
|
||||||
protected abstract Connection createConnection()
|
protected abstract Connection createConnection()
|
||||||
throws DbException, SQLException;
|
throws DbException, SQLException;
|
||||||
|
|
||||||
protected abstract void compactAndClose() throws DbException;
|
protected abstract void compactAndClose() throws DbException;
|
||||||
|
|
||||||
private final Lock connectionsLock = new ReentrantLock();
|
|
||||||
private final Condition connectionsChanged = connectionsLock.newCondition();
|
|
||||||
|
|
||||||
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
JdbcDatabase(DatabaseTypes databaseTypes, MessageFactory messageFactory,
|
||||||
Clock clock) {
|
Clock clock) {
|
||||||
this.dbTypes = databaseTypes;
|
this.dbTypes = databaseTypes;
|
||||||
@@ -457,7 +461,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
new Migration41_42(dbTypes),
|
new Migration41_42(dbTypes),
|
||||||
new Migration42_43(dbTypes),
|
new Migration42_43(dbTypes),
|
||||||
new Migration43_44(dbTypes),
|
new Migration43_44(dbTypes),
|
||||||
new Migration44_45()
|
new Migration44_45(),
|
||||||
|
new Migration45_46(),
|
||||||
|
new Migration46_47(dbTypes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,22 +783,23 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMessage(Connection txn, Message m, MessageState state,
|
public void addMessage(Connection txn, Message m, MessageState state,
|
||||||
boolean messageShared, @Nullable ContactId sender)
|
boolean shared, boolean temporary, @Nullable ContactId sender)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||||
+ " state, shared, length, raw)"
|
+ " state, shared, temporary, length, raw)"
|
||||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setBytes(1, m.getId().getBytes());
|
ps.setBytes(1, m.getId().getBytes());
|
||||||
ps.setBytes(2, m.getGroupId().getBytes());
|
ps.setBytes(2, m.getGroupId().getBytes());
|
||||||
ps.setLong(3, m.getTimestamp());
|
ps.setLong(3, m.getTimestamp());
|
||||||
ps.setInt(4, state.getValue());
|
ps.setInt(4, state.getValue());
|
||||||
ps.setBoolean(5, messageShared);
|
ps.setBoolean(5, shared);
|
||||||
|
ps.setBoolean(6, temporary);
|
||||||
byte[] raw = messageFactory.getRawMessage(m);
|
byte[] raw = messageFactory.getRawMessage(m);
|
||||||
ps.setInt(6, raw.length);
|
ps.setInt(7, raw.length);
|
||||||
ps.setBytes(7, raw);
|
ps.setBytes(8, raw);
|
||||||
int affected = ps.executeUpdate();
|
int affected = ps.executeUpdate();
|
||||||
if (affected != 1) throw new DbStateException();
|
if (affected != 1) throw new DbStateException();
|
||||||
ps.close();
|
ps.close();
|
||||||
@@ -804,8 +811,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
boolean offered = removeOfferedMessage(txn, c, m.getId());
|
boolean offered = removeOfferedMessage(txn, c, m.getId());
|
||||||
boolean seen = offered || c.equals(sender);
|
boolean seen = offered || c.equals(sender);
|
||||||
addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(),
|
addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(),
|
||||||
raw.length, state, e.getValue(), messageShared,
|
raw.length, state, e.getValue(), shared, false, seen);
|
||||||
false, seen);
|
|
||||||
}
|
}
|
||||||
// Update denormalised column in messageDependencies if dependency
|
// Update denormalised column in messageDependencies if dependency
|
||||||
// is in same group as dependent
|
// is in same group as dependent
|
||||||
@@ -2324,6 +2330,32 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Byte> getSyncVersions(Connection txn, ContactId c)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT syncVersions FROM contacts"
|
||||||
|
+ " WHERE contactId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setInt(1, c.getInt());
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
if (!rs.next()) throw new DbStateException();
|
||||||
|
byte[] bytes = rs.getBytes(1);
|
||||||
|
List<Byte> supported = new ArrayList<>(bytes.length);
|
||||||
|
for (byte b : bytes) supported.add(b);
|
||||||
|
if (rs.next()) throw new DbStateException();
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return supported;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(rs, LOG, WARNING);
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<TransportKeySet> getTransportKeys(Connection txn,
|
public Collection<TransportKeySet> getTransportKeys(Connection txn,
|
||||||
TransportId t) throws DbException {
|
TransportId t) throws DbException {
|
||||||
@@ -2876,6 +2908,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeTemporaryMessages(Connection txn) throws DbException {
|
||||||
|
Statement s = null;
|
||||||
|
try {
|
||||||
|
String sql = "DELETE FROM messages WHERE temporary = TRUE";
|
||||||
|
s = txn.createStatement();
|
||||||
|
int affected = s.executeUpdate(sql);
|
||||||
|
if (affected < 0) throw new DbStateException();
|
||||||
|
s.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(s, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeTransport(Connection txn, TransportId t)
|
public void removeTransport(Connection txn, TransportId t)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -3021,6 +3068,24 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMessagePermanent(Connection txn, MessageId m)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "UPDATE messages SET temporary = FALSE"
|
||||||
|
+ " WHERE messageId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.setBytes(1, m.getBytes());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessageShared(Connection txn, MessageId m)
|
public void setMessageShared(Connection txn, MessageId m)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
@@ -3124,6 +3189,29 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSyncVersions(Connection txn, ContactId c,
|
||||||
|
List<Byte> supported) throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
String sql = "UPDATE contacts SET syncVersions = ?"
|
||||||
|
+ " WHERE contactId = ?";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
byte[] bytes = new byte[supported.size()];
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
bytes[i] = supported.get(i);
|
||||||
|
}
|
||||||
|
ps.setBytes(1, bytes);
|
||||||
|
ps.setInt(2, c.getInt());
|
||||||
|
int affected = ps.executeUpdate();
|
||||||
|
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||||
|
ps.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(ps, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTransportKeysActive(Connection txn, TransportId t,
|
public void setTransportKeysActive(Connection txn, TransportId t,
|
||||||
KeySetId k) throws DbException {
|
KeySetId k) throws DbException {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
|
|
||||||
|
class Migration45_46 implements Migration<Connection> {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(Migration45_46.class.getName());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStartVersion() {
|
||||||
|
return 45;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEndVersion() {
|
||||||
|
return 46;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(Connection txn) throws DbException {
|
||||||
|
Statement s = null;
|
||||||
|
try {
|
||||||
|
s = txn.createStatement();
|
||||||
|
s.execute("ALTER TABLE messages"
|
||||||
|
+ " ADD COLUMN temporary BOOLEAN DEFAULT FALSE NOT NULL");
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(s, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package org.briarproject.bramble.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.db.JdbcUtils.tryToClose;
|
||||||
|
|
||||||
|
class Migration46_47 implements Migration<Connection> {
|
||||||
|
|
||||||
|
private static final Logger LOG = getLogger(Migration46_47.class.getName());
|
||||||
|
|
||||||
|
private final DatabaseTypes dbTypes;
|
||||||
|
|
||||||
|
Migration46_47(DatabaseTypes dbTypes) {
|
||||||
|
this.dbTypes = dbTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getStartVersion() {
|
||||||
|
return 46;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getEndVersion() {
|
||||||
|
return 47;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void migrate(Connection txn) throws DbException {
|
||||||
|
Statement s = null;
|
||||||
|
try {
|
||||||
|
s = txn.createStatement();
|
||||||
|
s.execute(dbTypes.replaceTypes("ALTER TABLE contacts"
|
||||||
|
+ " ADD COLUMN syncVersions"
|
||||||
|
+ " _BINARY DEFAULT '00' NOT NULL"));
|
||||||
|
} catch (SQLException e) {
|
||||||
|
tryToClose(s, LOG, WARNING);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,8 +107,11 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
|
|||||||
else logDuration(LOG, "Creating database", start);
|
else logDuration(LOG, "Creating database", start);
|
||||||
|
|
||||||
db.transaction(false, txn -> {
|
db.transaction(false, txn -> {
|
||||||
|
long start1 = now();
|
||||||
|
db.removeTemporaryMessages(txn);
|
||||||
|
logDuration(LOG, "Removing temporary messages", start1);
|
||||||
for (OpenDatabaseHook hook : openDatabaseHooks) {
|
for (OpenDatabaseHook hook : openDatabaseHooks) {
|
||||||
long start1 = now();
|
start1 = now();
|
||||||
hook.onDatabaseOpened(txn);
|
hook.onDatabaseOpened(txn);
|
||||||
if (LOG.isLoggable(FINE)) {
|
if (LOG.isLoggable(FINE)) {
|
||||||
logDuration(LOG, "Calling open database hook "
|
logDuration(LOG, "Calling open database hook "
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
|
|||||||
meta.put("transportId", t.getString());
|
meta.put("transportId", t.getString());
|
||||||
meta.put("version", version);
|
meta.put("version", version);
|
||||||
meta.put("local", local);
|
meta.put("local", local);
|
||||||
clientHelper.addLocalMessage(txn, m, meta, shared);
|
clientHelper.addLocalMessage(txn, m, meta, shared, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import static java.util.logging.Level.WARNING;
|
|||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
|
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
|
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
|
||||||
|
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNull;
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNull;
|
||||||
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
|
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
|
||||||
@@ -158,9 +159,7 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
|
|||||||
private void addPendingContact(PendingContact p) {
|
private void addPendingContact(PendingContact p) {
|
||||||
long now = clock.currentTimeMillis();
|
long now = clock.currentTimeMillis();
|
||||||
long expiry = p.getTimestamp() + RENDEZVOUS_TIMEOUT_MS;
|
long expiry = p.getTimestamp() + RENDEZVOUS_TIMEOUT_MS;
|
||||||
if (expiry > now) {
|
if (expiry <= now) {
|
||||||
broadcastState(p.getId(), WAITING_FOR_CONNECTION);
|
|
||||||
} else {
|
|
||||||
broadcastState(p.getId(), FAILED);
|
broadcastState(p.getId(), FAILED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -180,9 +179,13 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
|
|||||||
for (PluginState ps : pluginStates.values()) {
|
for (PluginState ps : pluginStates.values()) {
|
||||||
RendezvousEndpoint endpoint =
|
RendezvousEndpoint endpoint =
|
||||||
createEndpoint(ps.plugin, p.getId(), cs);
|
createEndpoint(ps.plugin, p.getId(), cs);
|
||||||
if (endpoint != null)
|
if (endpoint != null) {
|
||||||
requireNull(ps.endpoints.put(p.getId(), endpoint));
|
requireNull(ps.endpoints.put(p.getId(), endpoint));
|
||||||
|
cs.numEndpoints++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (cs.numEndpoints == 0) broadcastState(p.getId(), OFFLINE);
|
||||||
|
else broadcastState(p.getId(), WAITING_FOR_CONNECTION);
|
||||||
} catch (DbException | GeneralSecurityException e) {
|
} catch (DbException | GeneralSecurityException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -328,9 +331,14 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
|
|||||||
TransportId t = plugin.getId();
|
TransportId t = plugin.getId();
|
||||||
Map<PendingContactId, RendezvousEndpoint> endpoints = new HashMap<>();
|
Map<PendingContactId, RendezvousEndpoint> endpoints = new HashMap<>();
|
||||||
for (Entry<PendingContactId, CryptoState> e : cryptoStates.entrySet()) {
|
for (Entry<PendingContactId, CryptoState> e : cryptoStates.entrySet()) {
|
||||||
RendezvousEndpoint endpoint =
|
PendingContactId p = e.getKey();
|
||||||
createEndpoint(plugin, e.getKey(), e.getValue());
|
CryptoState cs = e.getValue();
|
||||||
if (endpoint != null) endpoints.put(e.getKey(), endpoint);
|
RendezvousEndpoint endpoint = createEndpoint(plugin, p, cs);
|
||||||
|
if (endpoint != null) {
|
||||||
|
endpoints.put(p, endpoint);
|
||||||
|
if (++cs.numEndpoints == 1)
|
||||||
|
broadcastState(p, WAITING_FOR_CONNECTION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
requireNull(pluginStates.put(t, new PluginState(plugin, endpoints)));
|
requireNull(pluginStates.put(t, new PluginState(plugin, endpoints)));
|
||||||
}
|
}
|
||||||
@@ -344,8 +352,11 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
|
|||||||
private void removeTransport(TransportId t) {
|
private void removeTransport(TransportId t) {
|
||||||
PluginState ps = pluginStates.remove(t);
|
PluginState ps = pluginStates.remove(t);
|
||||||
if (ps != null) {
|
if (ps != null) {
|
||||||
for (RendezvousEndpoint endpoint : ps.endpoints.values()) {
|
for (Entry<PendingContactId, RendezvousEndpoint> e :
|
||||||
tryToClose(endpoint, LOG, INFO);
|
ps.endpoints.entrySet()) {
|
||||||
|
tryToClose(e.getValue(), LOG, INFO);
|
||||||
|
CryptoState cs = cryptoStates.get(e.getKey());
|
||||||
|
if (--cs.numEndpoints == 0) broadcastState(e.getKey(), OFFLINE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,6 +402,8 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
|
|||||||
private final boolean alice;
|
private final boolean alice;
|
||||||
private final long expiry;
|
private final long expiry;
|
||||||
|
|
||||||
|
private int numEndpoints = 0;
|
||||||
|
|
||||||
private CryptoState(SecretKey rendezvousKey, boolean alice,
|
private CryptoState(SecretKey rendezvousKey, boolean alice,
|
||||||
long expiry) {
|
long expiry) {
|
||||||
this.rendezvousKey = rendezvousKey;
|
this.rendezvousKey = rendezvousKey;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.sync.Offer;
|
|||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
|
||||||
@@ -39,9 +40,11 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +58,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
class DuplexOutgoingSession implements SyncSession, EventListener {
|
class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
getLogger(DuplexOutgoingSession.class.getName());
|
||||||
|
|
||||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
||||||
};
|
};
|
||||||
@@ -103,6 +106,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
public void run() throws IOException {
|
public void run() throws IOException {
|
||||||
eventBus.addListener(this);
|
eventBus.addListener(this);
|
||||||
try {
|
try {
|
||||||
|
// Send our supported protocol versions
|
||||||
|
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
generateAck();
|
generateAck();
|
||||||
generateBatch();
|
generateBatch();
|
||||||
|
|||||||
@@ -18,14 +18,17 @@ import org.briarproject.bramble.api.sync.Offer;
|
|||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.concurrent.ThreadSafe;
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@@ -37,7 +40,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
class IncomingSession implements SyncSession, EventListener {
|
class IncomingSession implements SyncSession, EventListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(IncomingSession.class.getName());
|
getLogger(IncomingSession.class.getName());
|
||||||
|
|
||||||
private final DatabaseComponent db;
|
private final DatabaseComponent db;
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
@@ -80,6 +83,9 @@ class IncomingSession implements SyncSession, EventListener {
|
|||||||
} else if (recordReader.hasRequest()) {
|
} else if (recordReader.hasRequest()) {
|
||||||
Request r = recordReader.readRequest();
|
Request r = recordReader.readRequest();
|
||||||
dbExecutor.execute(new ReceiveRequest(r));
|
dbExecutor.execute(new ReceiveRequest(r));
|
||||||
|
} else if (recordReader.hasVersions()) {
|
||||||
|
Versions v = recordReader.readVersions();
|
||||||
|
dbExecutor.execute(new ReceiveVersions(v));
|
||||||
} else {
|
} else {
|
||||||
// unknown records are ignored in RecordReader#eof()
|
// unknown records are ignored in RecordReader#eof()
|
||||||
throw new FormatException();
|
throw new FormatException();
|
||||||
@@ -190,4 +196,26 @@ class IncomingSession implements SyncSession, EventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ReceiveVersions implements Runnable {
|
||||||
|
|
||||||
|
private final Versions versions;
|
||||||
|
|
||||||
|
private ReceiveVersions(Versions versions) {
|
||||||
|
this.versions = versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DatabaseExecutor
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
List<Byte> supported = versions.getSupportedVersions();
|
||||||
|
db.transaction(false,
|
||||||
|
txn -> db.setSyncVersions(txn, contactId, supported));
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.sync.Ack;
|
|||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
import org.briarproject.bramble.api.sync.SyncSession;
|
import org.briarproject.bramble.api.sync.SyncSession;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -29,9 +30,11 @@ import javax.annotation.concurrent.ThreadSafe;
|
|||||||
|
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
|
||||||
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTES;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +47,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
class SimplexOutgoingSession implements SyncSession, EventListener {
|
class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(SimplexOutgoingSession.class.getName());
|
getLogger(SimplexOutgoingSession.class.getName());
|
||||||
|
|
||||||
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
private static final ThrowingRunnable<IOException> CLOSE = () -> {
|
||||||
};
|
};
|
||||||
@@ -80,6 +83,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
|||||||
public void run() throws IOException {
|
public void run() throws IOException {
|
||||||
eventBus.addListener(this);
|
eventBus.addListener(this);
|
||||||
try {
|
try {
|
||||||
|
// Send our supported protocol versions
|
||||||
|
recordWriter.writeVersions(new Versions(SUPPORTED_VERSIONS));
|
||||||
// Start a query for each type of record
|
// Start a query for each type of record
|
||||||
dbExecutor.execute(new GenerateAck());
|
dbExecutor.execute(new GenerateAck());
|
||||||
dbExecutor.execute(new GenerateBatch());
|
dbExecutor.execute(new GenerateBatch());
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
|||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
import org.briarproject.bramble.util.ByteUtils;
|
import org.briarproject.bramble.util.ByteUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -26,6 +27,8 @@ 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.MESSAGE;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
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.RecordTypes.REQUEST;
|
||||||
|
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ class SyncRecordReaderImpl implements SyncRecordReader {
|
|||||||
|
|
||||||
private static boolean isKnownRecordType(byte type) {
|
private static boolean isKnownRecordType(byte type) {
|
||||||
return type == ACK || type == MESSAGE || type == OFFER ||
|
return type == ACK || type == MESSAGE || type == OFFER ||
|
||||||
type == REQUEST;
|
type == REQUEST || type == VERSIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final MessageFactory messageFactory;
|
private final MessageFactory messageFactory;
|
||||||
@@ -148,4 +151,27 @@ class SyncRecordReaderImpl implements SyncRecordReader {
|
|||||||
if (!hasRequest()) throw new FormatException();
|
if (!hasRequest()) throw new FormatException();
|
||||||
return new Request(readMessageIds());
|
return new Request(readMessageIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasVersions() throws IOException {
|
||||||
|
return !eof() && getNextRecordType() == VERSIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Versions readVersions() throws IOException {
|
||||||
|
if (!hasVersions()) throw new FormatException();
|
||||||
|
return new Versions(readSupportedVersions());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Byte> readSupportedVersions() throws IOException {
|
||||||
|
if (nextRecord == null) throw new AssertionError();
|
||||||
|
byte[] payload = nextRecord.getPayload();
|
||||||
|
if (payload.length == 0) throw new FormatException();
|
||||||
|
if (payload.length > MAX_SUPPORTED_VERSIONS)
|
||||||
|
throw new FormatException();
|
||||||
|
List<Byte> supported = new ArrayList<>(payload.length);
|
||||||
|
for (byte b : payload) supported.add(b);
|
||||||
|
nextRecord = null;
|
||||||
|
return supported;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.sync.MessageId;
|
|||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -20,6 +21,7 @@ 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.MESSAGE;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
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.RecordTypes.REQUEST;
|
||||||
|
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||||
|
|
||||||
@NotThreadSafe
|
@NotThreadSafe
|
||||||
@@ -65,6 +67,12 @@ class SyncRecordWriterImpl implements SyncRecordWriter {
|
|||||||
writeRecord(REQUEST);
|
writeRecord(REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeVersions(Versions v) throws IOException {
|
||||||
|
for (byte b : v.getSupportedVersions()) payload.write(b);
|
||||||
|
writeRecord(VERSIONS);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
writer.flush();
|
writer.flush();
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
|||||||
try {
|
try {
|
||||||
shareMsg = hook.incomingMessage(txn, m, meta);
|
shareMsg = hook.incomingMessage(txn, m, meta);
|
||||||
} catch (InvalidMessageException e) {
|
} catch (InvalidMessageException e) {
|
||||||
|
logException(LOG, INFO, e);
|
||||||
invalidateMessage(txn, m.getId());
|
invalidateMessage(txn, m.getId());
|
||||||
return new DeliveryResult(false, false);
|
return new DeliveryResult(false, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
try {
|
try {
|
||||||
Message m = clientHelper.createMessage(localGroup.getId(), now,
|
Message m = clientHelper.createMessage(localGroup.getId(), now,
|
||||||
body);
|
body);
|
||||||
db.addLocalMessage(txn, m, new Metadata(), false);
|
db.addLocalMessage(txn, m, new Metadata(), false, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
@@ -438,7 +438,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
|
|||||||
BdfDictionary meta = new BdfDictionary();
|
BdfDictionary meta = new BdfDictionary();
|
||||||
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
|
meta.put(MSG_KEY_UPDATE_VERSION, updateVersion);
|
||||||
meta.put(MSG_KEY_LOCAL, true);
|
meta.put(MSG_KEY_LOCAL, true);
|
||||||
clientHelper.addLocalMessage(txn, m, meta, true);
|
clientHelper.addLocalMessage(txn, m, meta, true, false);
|
||||||
} catch (FormatException e) {
|
} catch (FormatException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
|
|||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(metadataEncoder).encode(dictionary);
|
oneOf(metadataEncoder).encode(dictionary);
|
||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
oneOf(db).addLocalMessage(txn, message, metadata, shared);
|
oneOf(db).addLocalMessage(txn, message, metadata, shared, false);
|
||||||
}});
|
}});
|
||||||
|
|
||||||
clientHelper.addLocalMessage(message, dictionary, shared);
|
clientHelper.addLocalMessage(message, dictionary, shared);
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import org.briarproject.bramble.api.contact.ContactManager;
|
|||||||
import org.briarproject.bramble.api.contact.PendingContact;
|
import org.briarproject.bramble.api.contact.PendingContact;
|
||||||
import org.briarproject.bramble.api.contact.PendingContactState;
|
import org.briarproject.bramble.api.contact.PendingContactState;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
|
||||||
|
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
|
import org.briarproject.bramble.api.event.Event;
|
||||||
|
import org.briarproject.bramble.api.event.EventListener;
|
||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
import org.briarproject.bramble.test.TestDatabaseConfigModule;
|
||||||
import org.briarproject.bramble.test.TestDuplexTransportConnection;
|
import org.briarproject.bramble.test.TestDuplexTransportConnection;
|
||||||
@@ -27,7 +31,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
|||||||
import static junit.framework.TestCase.assertNotNull;
|
import static junit.framework.TestCase.assertNotNull;
|
||||||
import static junit.framework.TestCase.assertNull;
|
import static junit.framework.TestCase.assertNull;
|
||||||
import static junit.framework.TestCase.fail;
|
import static junit.framework.TestCase.fail;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
|
||||||
import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair;
|
import static org.briarproject.bramble.test.TestDuplexTransportConnection.createPair;
|
||||||
import static org.briarproject.bramble.test.TestPluginConfigModule.DUPLEX_TRANSPORT_ID;
|
import static org.briarproject.bramble.test.TestPluginConfigModule.DUPLEX_TRANSPORT_ID;
|
||||||
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
|
||||||
@@ -188,9 +192,14 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
|
|||||||
private PendingContact addPendingContact(
|
private PendingContact addPendingContact(
|
||||||
ContactExchangeIntegrationTestComponent local,
|
ContactExchangeIntegrationTestComponent local,
|
||||||
ContactExchangeIntegrationTestComponent remote) throws Exception {
|
ContactExchangeIntegrationTestComponent remote) throws Exception {
|
||||||
|
EventWaiter waiter = new EventWaiter();
|
||||||
|
local.getEventBus().addListener(waiter);
|
||||||
String link = remote.getContactManager().getHandshakeLink();
|
String link = remote.getContactManager().getHandshakeLink();
|
||||||
String alias = remote.getIdentityManager().getLocalAuthor().getName();
|
String alias = remote.getIdentityManager().getLocalAuthor().getName();
|
||||||
return local.getContactManager().addPendingContact(link, alias);
|
PendingContact pendingContact =
|
||||||
|
local.getContactManager().addPendingContact(link, alias);
|
||||||
|
waiter.latch.await(TIMEOUT, MILLISECONDS);
|
||||||
|
return pendingContact;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertContacts(boolean verified,
|
private void assertContacts(boolean verified,
|
||||||
@@ -237,7 +246,7 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
|
|||||||
assertEquals(1, pairs.size());
|
assertEquals(1, pairs.size());
|
||||||
Pair<PendingContact, PendingContactState> pair =
|
Pair<PendingContact, PendingContactState> pair =
|
||||||
pairs.iterator().next();
|
pairs.iterator().next();
|
||||||
assertEquals(WAITING_FOR_CONNECTION, pair.getSecond());
|
assertEquals(OFFLINE, pair.getSecond());
|
||||||
PendingContact pendingContact = pair.getFirst();
|
PendingContact pendingContact = pair.getFirst();
|
||||||
assertEquals(expectedIdentity.getLocalAuthor().getName(),
|
assertEquals(expectedIdentity.getLocalAuthor().getName(),
|
||||||
pendingContact.getAlias());
|
pendingContact.getAlias());
|
||||||
@@ -261,4 +270,19 @@ public class ContactExchangeIntegrationTest extends BrambleTestCase {
|
|||||||
tearDown(bob);
|
tearDown(bob);
|
||||||
deleteTestDirectory(testDir);
|
deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
private static class EventWaiter implements EventListener {
|
||||||
|
|
||||||
|
private final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventOccurred(Event e) {
|
||||||
|
if (e instanceof PendingContactStateChangedEvent) {
|
||||||
|
PendingContactStateChangedEvent p =
|
||||||
|
(PendingContactStateChangedEvent) e;
|
||||||
|
if (p.getPendingContactState() == OFFLINE) latch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,10 +61,12 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||||
@@ -120,6 +122,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
private final Contact contact;
|
private final Contact contact;
|
||||||
private final KeySetId keySetId;
|
private final KeySetId keySetId;
|
||||||
private final PendingContactId pendingContactId;
|
private final PendingContactId pendingContactId;
|
||||||
|
private final Random random = new Random();
|
||||||
|
private final boolean shared = random.nextBoolean();
|
||||||
|
private final boolean temporary = random.nextBoolean();
|
||||||
|
|
||||||
public DatabaseComponentImplTest() {
|
public DatabaseComponentImplTest() {
|
||||||
clientId = getClientId();
|
clientId = getClientId();
|
||||||
@@ -253,7 +258,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.addLocalMessage(transaction, message, metadata, true));
|
db.addLocalMessage(transaction, message, metadata, shared,
|
||||||
|
temporary));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -265,20 +271,23 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).containsMessage(txn, messageId);
|
oneOf(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
|
oneOf(database).addMessage(txn, message, DELIVERED, shared,
|
||||||
|
temporary, null);
|
||||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
// The message was added, so the listeners should be called
|
// The message was added, so the listeners should be called
|
||||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||||
oneOf(eventBus)
|
oneOf(eventBus).broadcast(with(any(
|
||||||
.broadcast(with(any(MessageStateChangedEvent.class)));
|
MessageStateChangedEvent.class)));
|
||||||
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
if (shared)
|
||||||
|
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
|
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.addLocalMessage(transaction, message, metadata, true));
|
db.addLocalMessage(transaction, message, metadata, shared,
|
||||||
|
temporary));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -286,11 +295,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the contact is in the DB (which it's not)
|
// Check whether the contact is in the DB (which it's not)
|
||||||
exactly(16).of(database).startTransaction();
|
exactly(18).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(16).of(database).containsContact(txn, contactId);
|
exactly(18).of(database).containsContact(txn, contactId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(16).of(database).abortTransaction(txn);
|
exactly(18).of(database).abortTransaction(txn);
|
||||||
}});
|
}});
|
||||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||||
eventExecutor, shutdownManager);
|
eventExecutor, shutdownManager);
|
||||||
@@ -368,6 +377,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.getSyncVersions(transaction, contactId));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Ack a = new Ack(singletonList(messageId));
|
Ack a = new Ack(singletonList(messageId));
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
@@ -427,6 +444,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
} catch (NoSuchContactException expected) {
|
} catch (NoSuchContactException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.setSyncVersions(transaction, contactId, emptyList()));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchContactException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -569,11 +594,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// Check whether the message is in the DB (which it's not)
|
// Check whether the message is in the DB (which it's not)
|
||||||
exactly(11).of(database).startTransaction();
|
exactly(12).of(database).startTransaction();
|
||||||
will(returnValue(txn));
|
will(returnValue(txn));
|
||||||
exactly(11).of(database).containsMessage(txn, messageId);
|
exactly(12).of(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
exactly(11).of(database).abortTransaction(txn);
|
exactly(12).of(database).abortTransaction(txn);
|
||||||
// Allow other checks to pass
|
// Allow other checks to pass
|
||||||
allowing(database).containsContact(txn, contactId);
|
allowing(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
@@ -637,6 +662,14 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(false, transaction ->
|
||||||
|
db.setMessagePermanent(transaction, message.getId()));
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchMessageException expected) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db.transaction(false, transaction ->
|
db.transaction(false, transaction ->
|
||||||
db.setMessageShared(transaction, message.getId()));
|
db.setMessageShared(transaction, message.getId()));
|
||||||
@@ -972,7 +1005,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(VISIBLE));
|
will(returnValue(VISIBLE));
|
||||||
oneOf(database).containsMessage(txn, messageId);
|
oneOf(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
|
oneOf(database).addMessage(txn, message, UNKNOWN, false, false,
|
||||||
|
contactId);
|
||||||
// Second time
|
// Second time
|
||||||
oneOf(database).containsContact(txn, contactId);
|
oneOf(database).containsContact(txn, contactId);
|
||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
@@ -1507,6 +1541,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
public void testMessageDependencies() throws Exception {
|
public void testMessageDependencies() throws Exception {
|
||||||
int shutdownHandle = 12345;
|
int shutdownHandle = 12345;
|
||||||
MessageId messageId2 = new MessageId(getRandomId());
|
MessageId messageId2 = new MessageId(getRandomId());
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
// open()
|
// open()
|
||||||
oneOf(database).open(key, null);
|
oneOf(database).open(key, null);
|
||||||
@@ -1521,7 +1556,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(true));
|
will(returnValue(true));
|
||||||
oneOf(database).containsMessage(txn, messageId);
|
oneOf(database).containsMessage(txn, messageId);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
|
oneOf(database).addMessage(txn, message, DELIVERED, shared,
|
||||||
|
temporary, null);
|
||||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||||
// addMessageDependencies()
|
// addMessageDependencies()
|
||||||
oneOf(database).containsMessage(txn, messageId);
|
oneOf(database).containsMessage(txn, messageId);
|
||||||
@@ -1544,7 +1580,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||||
oneOf(eventBus).broadcast(with(any(
|
oneOf(eventBus).broadcast(with(any(
|
||||||
MessageStateChangedEvent.class)));
|
MessageStateChangedEvent.class)));
|
||||||
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
if (shared)
|
||||||
|
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
||||||
// endTransaction()
|
// endTransaction()
|
||||||
oneOf(database).commitTransaction(txn);
|
oneOf(database).commitTransaction(txn);
|
||||||
// close()
|
// close()
|
||||||
@@ -1555,7 +1592,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
assertFalse(db.open(key, null));
|
assertFalse(db.open(key, null));
|
||||||
db.transaction(false, transaction -> {
|
db.transaction(false, transaction -> {
|
||||||
db.addLocalMessage(transaction, message, metadata, true);
|
db.addLocalMessage(transaction, message, metadata, shared,
|
||||||
|
temporary);
|
||||||
Collection<MessageId> dependencies = new ArrayList<>(2);
|
Collection<MessageId> dependencies = new ArrayList<>(2);
|
||||||
dependencies.add(messageId1);
|
dependencies.add(messageId1);
|
||||||
dependencies.add(messageId2);
|
dependencies.add(messageId2);
|
||||||
|
|||||||
@@ -567,8 +567,9 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
|||||||
MessageState state =
|
MessageState state =
|
||||||
MessageState.fromValue(random.nextInt(4));
|
MessageState.fromValue(random.nextInt(4));
|
||||||
boolean shared = random.nextBoolean();
|
boolean shared = random.nextBoolean();
|
||||||
|
boolean temporary = random.nextBoolean();
|
||||||
ContactId sender = random.nextBoolean() ? c : null;
|
ContactId sender = random.nextBoolean() ? c : null;
|
||||||
db.addMessage(txn, m, state, shared, sender);
|
db.addMessage(txn, m, state, shared, temporary, sender);
|
||||||
if (random.nextBoolean())
|
if (random.nextBoolean())
|
||||||
db.raiseRequestedFlag(txn, c, m.getId());
|
db.raiseRequestedFlag(txn, c, m.getId());
|
||||||
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
|
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
|
||||||
@@ -597,7 +598,8 @@ public abstract class DatabasePerformanceTest extends BrambleTestCase {
|
|||||||
for (int j = 0; j < MESSAGES_PER_GROUP; j++) {
|
for (int j = 0; j < MESSAGES_PER_GROUP; j++) {
|
||||||
Message m = getMessage(g.getId());
|
Message m = getMessage(g.getId());
|
||||||
messages.add(m);
|
messages.add(m);
|
||||||
db.addMessage(txn, m, DELIVERED, false, null);
|
boolean temporary = random.nextBoolean();
|
||||||
|
db.addMessage(txn, m, DELIVERED, false, temporary, null);
|
||||||
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
|
Metadata mm = getMetadata(METADATA_KEYS_PER_MESSAGE);
|
||||||
messageMeta.get(g.getId()).add(mm);
|
messageMeta.get(g.getId()).add(mm);
|
||||||
db.mergeMessageMetadata(txn, m.getId(), mm);
|
db.mergeMessageMetadata(txn, m.getId(), mm);
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import org.junit.Test;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -51,6 +50,7 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
@@ -153,7 +153,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
assertTrue(db.containsGroup(txn, groupId));
|
assertTrue(db.containsGroup(txn, groupId));
|
||||||
assertFalse(db.containsMessage(txn, messageId));
|
assertFalse(db.containsMessage(txn, messageId));
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
assertTrue(db.containsMessage(txn, messageId));
|
assertTrue(db.containsMessage(txn, messageId));
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -191,7 +191,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and a message
|
// Add a group and a message
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Removing the group should remove the message
|
// Removing the group should remove the message
|
||||||
assertTrue(db.containsMessage(txn, messageId));
|
assertTrue(db.containsMessage(txn, messageId));
|
||||||
@@ -213,7 +213,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The contact has not seen the message, so it should be sendable
|
// The contact has not seen the message, so it should be sendable
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
@@ -244,7 +244,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, UNKNOWN, true, null);
|
db.addMessage(txn, message, UNKNOWN, true, false, null);
|
||||||
|
|
||||||
// The message has not been validated, so it should not be sendable
|
// The message has not been validated, so it should not be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
@@ -288,7 +288,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(contactId,
|
assertEquals(contactId,
|
||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The group is invisible, so the message should not be sendable
|
// The group is invisible, so the message should not be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
@@ -340,7 +340,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, false, null);
|
db.addMessage(txn, message, DELIVERED, false, false, null);
|
||||||
|
|
||||||
// The message is not shared, so it should not be sendable
|
// The message is not shared, so it should not be sendable
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
@@ -371,7 +371,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The message is sendable, but too large to send
|
// The message is sendable, but too large to send
|
||||||
Collection<MessageId> ids =
|
Collection<MessageId> ids =
|
||||||
@@ -402,15 +402,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Add some messages to ack
|
// Add some messages to ack
|
||||||
Message message1 = getMessage(groupId);
|
Message message1 = getMessage(groupId);
|
||||||
MessageId messageId1 = message1.getId();
|
MessageId messageId1 = message1.getId();
|
||||||
db.addMessage(txn, message, DELIVERED, true, contactId);
|
db.addMessage(txn, message, DELIVERED, true, false, contactId);
|
||||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(Arrays.asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
// Remove both message IDs
|
// Remove both message IDs
|
||||||
db.lowerAckFlag(txn, contactId, Arrays.asList(messageId, messageId1));
|
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
|
||||||
|
|
||||||
// Both message IDs should have been removed
|
// Both message IDs should have been removed
|
||||||
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
assertEquals(emptyList(), db.getMessagesToAck(txn,
|
||||||
@@ -422,7 +422,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Both message IDs should be returned
|
// Both message IDs should be returned
|
||||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||||
assertEquals(Arrays.asList(messageId, messageId1), ids);
|
assertEquals(asList(messageId, messageId1), ids);
|
||||||
|
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
db.close();
|
db.close();
|
||||||
@@ -439,7 +439,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Retrieve the message from the database and mark it as sent
|
// Retrieve the message from the database and mark it as sent
|
||||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||||
@@ -608,7 +608,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(contactId,
|
assertEquals(contactId,
|
||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The group is not visible so the message should not be visible
|
// The group is not visible so the message should not be visible
|
||||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
@@ -1223,7 +1223,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and a message
|
// Add a group and a message
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Attach some metadata to the message
|
// Attach some metadata to the message
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
@@ -1294,7 +1294,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and a message
|
// Add a group and a message
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Attach some metadata to the message
|
// Attach some metadata to the message
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
@@ -1355,8 +1355,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and two messages
|
// Add a group and two messages
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
db.addMessage(txn, message1, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Attach some metadata to the messages
|
// Attach some metadata to the messages
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
@@ -1459,8 +1459,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and two messages
|
// Add a group and two messages
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
db.addMessage(txn, message1, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Attach some metadata to the messages
|
// Attach some metadata to the messages
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
@@ -1536,9 +1536,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and some messages
|
// Add a group and some messages
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, PENDING, true, contactId);
|
db.addMessage(txn, message, PENDING, true, false, contactId);
|
||||||
db.addMessage(txn, message1, PENDING, true, contactId);
|
db.addMessage(txn, message1, PENDING, true, false, contactId);
|
||||||
db.addMessage(txn, message2, INVALID, true, contactId);
|
db.addMessage(txn, message2, INVALID, true, false, contactId);
|
||||||
|
|
||||||
// Add dependencies
|
// Add dependencies
|
||||||
db.addMessageDependency(txn, message, messageId1, PENDING);
|
db.addMessageDependency(txn, message, messageId1, PENDING);
|
||||||
@@ -1589,7 +1589,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(0, dependents.size());
|
assertEquals(0, dependents.size());
|
||||||
|
|
||||||
// Add message 3
|
// Add message 3
|
||||||
db.addMessage(txn, message3, UNKNOWN, false, contactId);
|
db.addMessage(txn, message3, UNKNOWN, false, false, contactId);
|
||||||
|
|
||||||
// Message 3 has message 1 as a dependent
|
// Message 3 has message 1 as a dependent
|
||||||
dependents = db.getMessageDependents(txn, messageId3);
|
dependents = db.getMessageDependents(txn, messageId3);
|
||||||
@@ -1601,7 +1601,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(0, dependents.size());
|
assertEquals(0, dependents.size());
|
||||||
|
|
||||||
// Add message 4
|
// Add message 4
|
||||||
db.addMessage(txn, message4, UNKNOWN, false, contactId);
|
db.addMessage(txn, message4, UNKNOWN, false, false, contactId);
|
||||||
|
|
||||||
// Message 4 has message 2 as a dependent
|
// Message 4 has message 2 as a dependent
|
||||||
dependents = db.getMessageDependents(txn, messageId4);
|
dependents = db.getMessageDependents(txn, messageId4);
|
||||||
@@ -1619,7 +1619,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and a message
|
// Add a group and a message
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, PENDING, true, contactId);
|
db.addMessage(txn, message, PENDING, true, false, contactId);
|
||||||
|
|
||||||
// Add a second group
|
// Add a second group
|
||||||
Group group1 = getGroup(clientId, 123);
|
Group group1 = getGroup(clientId, 123);
|
||||||
@@ -1629,7 +1629,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Add a message to the second group
|
// Add a message to the second group
|
||||||
Message message1 = getMessage(groupId1);
|
Message message1 = getMessage(groupId1);
|
||||||
MessageId messageId1 = message1.getId();
|
MessageId messageId1 = message1.getId();
|
||||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
// Create an ID for a missing message
|
// Create an ID for a missing message
|
||||||
MessageId messageId2 = new MessageId(getRandomId());
|
MessageId messageId2 = new MessageId(getRandomId());
|
||||||
@@ -1637,7 +1637,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
// Add another message to the first group
|
// Add another message to the first group
|
||||||
Message message3 = getMessage(groupId);
|
Message message3 = getMessage(groupId);
|
||||||
MessageId messageId3 = message3.getId();
|
MessageId messageId3 = message3.getId();
|
||||||
db.addMessage(txn, message3, DELIVERED, true, contactId);
|
db.addMessage(txn, message3, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
// Add dependencies between the messages
|
// Add dependencies between the messages
|
||||||
db.addMessageDependency(txn, message, messageId1, PENDING);
|
db.addMessageDependency(txn, message, messageId1, PENDING);
|
||||||
@@ -1680,10 +1680,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and some messages with different states
|
// Add a group and some messages with different states
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message1, UNKNOWN, true, contactId);
|
db.addMessage(txn, message1, UNKNOWN, true, false, contactId);
|
||||||
db.addMessage(txn, message2, INVALID, true, contactId);
|
db.addMessage(txn, message2, INVALID, true, false, contactId);
|
||||||
db.addMessage(txn, message3, PENDING, true, contactId);
|
db.addMessage(txn, message3, PENDING, true, false, contactId);
|
||||||
db.addMessage(txn, message4, DELIVERED, true, contactId);
|
db.addMessage(txn, message4, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
Collection<MessageId> result;
|
Collection<MessageId> result;
|
||||||
|
|
||||||
@@ -1713,10 +1713,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and some messages
|
// Add a group and some messages
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
|
||||||
db.addMessage(txn, message2, DELIVERED, false, contactId);
|
db.addMessage(txn, message2, DELIVERED, false, false, contactId);
|
||||||
db.addMessage(txn, message3, DELIVERED, false, contactId);
|
db.addMessage(txn, message3, DELIVERED, false, false, contactId);
|
||||||
db.addMessage(txn, message4, DELIVERED, true, contactId);
|
db.addMessage(txn, message4, DELIVERED, true, false, contactId);
|
||||||
|
|
||||||
// Introduce dependencies between the messages
|
// Introduce dependencies between the messages
|
||||||
db.addMessageDependency(txn, message1, message2.getId(), DELIVERED);
|
db.addMessageDependency(txn, message1, message2.getId(), DELIVERED);
|
||||||
@@ -1744,7 +1744,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The message should not be sent or seen
|
// The message should not be sent or seen
|
||||||
MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
|
MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
|
||||||
@@ -1878,7 +1878,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// The message should be visible to the contact
|
// The message should be visible to the contact
|
||||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||||
@@ -1961,7 +1961,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
|
|
||||||
// Add a group and a message
|
// Add a group and a message
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, UNKNOWN, false, contactId);
|
db.addMessage(txn, message, UNKNOWN, false, false, contactId);
|
||||||
|
|
||||||
// Walk the message through the validation and delivery states
|
// Walk the message through the validation and delivery states
|
||||||
assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
|
assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
|
||||||
@@ -1988,7 +1988,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
assertEquals(contactId,
|
assertEquals(contactId,
|
||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addMessage(txn, message, UNKNOWN, false, null);
|
db.addMessage(txn, message, UNKNOWN, false, false, null);
|
||||||
|
|
||||||
// There should be no messages to send
|
// There should be no messages to send
|
||||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||||
@@ -2073,7 +2073,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Time: now
|
// Time: now
|
||||||
// Retrieve the message from the database
|
// Retrieve the message from the database
|
||||||
@@ -2118,7 +2118,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.addContact(txn, author, localAuthor.getId(), null, true));
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
db.addGroup(txn, group);
|
db.addGroup(txn, group);
|
||||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||||
db.addMessage(txn, message, DELIVERED, true, null);
|
db.addMessage(txn, message, DELIVERED, true, false, null);
|
||||||
|
|
||||||
// Time: now
|
// Time: now
|
||||||
// Retrieve the message from the database
|
// Retrieve the message from the database
|
||||||
@@ -2257,6 +2257,58 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
|
|||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTemporaryMessages() throws Exception {
|
||||||
|
Message message1 = getMessage(groupId);
|
||||||
|
MessageId messageId1 = message1.getId();
|
||||||
|
|
||||||
|
Database<Connection> db = open(false);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add a group and two temporary messages
|
||||||
|
db.addGroup(txn, group);
|
||||||
|
db.addMessage(txn, message, DELIVERED, false, true, null);
|
||||||
|
db.addMessage(txn, message1, DELIVERED, false, true, null);
|
||||||
|
|
||||||
|
// Mark one of the messages as permanent
|
||||||
|
db.setMessagePermanent(txn, messageId);
|
||||||
|
|
||||||
|
// Remove all temporary messages
|
||||||
|
db.removeTemporaryMessages(txn);
|
||||||
|
|
||||||
|
// The permanent message should not have been removed
|
||||||
|
assertTrue(db.containsMessage(txn, messageId));
|
||||||
|
|
||||||
|
// The temporary message should have been removed
|
||||||
|
assertFalse(db.containsMessage(txn, messageId1));
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSyncVersions() throws Exception {
|
||||||
|
Database<Connection> db = open(false);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
|
||||||
|
// Add a contact
|
||||||
|
db.addIdentity(txn, identity);
|
||||||
|
assertEquals(contactId,
|
||||||
|
db.addContact(txn, author, localAuthor.getId(), null, true));
|
||||||
|
|
||||||
|
// Only sync version 0 should be supported by default
|
||||||
|
List<Byte> defaultSupported = singletonList((byte) 0);
|
||||||
|
assertEquals(defaultSupported, db.getSyncVersions(txn, contactId));
|
||||||
|
|
||||||
|
// Set the supported versions and check that they're returned
|
||||||
|
List<Byte> supported = asList((byte) 0, (byte) 1);
|
||||||
|
db.setSyncVersions(txn, contactId, supported);
|
||||||
|
assertEquals(supported, db.getSyncVersions(txn, contactId));
|
||||||
|
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
private Database<Connection> open(boolean resume) throws Exception {
|
private Database<Connection> open(boolean resume) throws Exception {
|
||||||
return open(resume, new TestMessageFactory(), new SystemClock());
|
return open(resume, new TestMessageFactory(), new SystemClock());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(db).open(dbKey, lifecycleManager);
|
oneOf(db).open(dbKey, lifecycleManager);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
|
oneOf(db).removeTemporaryMessages(txn);
|
||||||
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
allowing(eventBus).broadcast(with(any(LifecycleEvent.class)));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
|||||||
@@ -637,7 +637,8 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(timestamp));
|
will(returnValue(timestamp));
|
||||||
oneOf(clientHelper).createMessage(g, timestamp, body);
|
oneOf(clientHelper).createMessage(g, timestamp, body);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, shared);
|
oneOf(clientHelper).addLocalMessage(txn, message, meta, shared,
|
||||||
|
false);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import static java.util.Collections.singletonList;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
|
import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CONTACT;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
|
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
|
||||||
|
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
|
||||||
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
|
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
|
||||||
import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS;
|
import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS;
|
||||||
@@ -120,7 +121,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(beforeExpiry));
|
will(returnValue(beforeExpiry));
|
||||||
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
|
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
|
||||||
PendingContactStateChangedEvent.class, e ->
|
PendingContactStateChangedEvent.class, e ->
|
||||||
e.getPendingContactState() == WAITING_FOR_CONNECTION)));
|
e.getPendingContactState() == OFFLINE)));
|
||||||
// Capture the poll task
|
// Capture the poll task
|
||||||
oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)),
|
oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)),
|
||||||
with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS),
|
with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS),
|
||||||
@@ -184,7 +185,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
// Add the pending contact - endpoint should be created and polled
|
// Add the pending contact - endpoint should be created and polled
|
||||||
expectAddUnexpiredPendingContact(beforeExpiry);
|
expectAddPendingContact(beforeExpiry, WAITING_FOR_CONNECTION);
|
||||||
expectDeriveRendezvousKey();
|
expectDeriveRendezvousKey();
|
||||||
expectCreateEndpoint();
|
expectCreateEndpoint();
|
||||||
|
|
||||||
@@ -205,9 +206,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
// Remove the pending contact - endpoint should be closed
|
// Remove the pending contact - endpoint should be closed
|
||||||
context.checking(new Expectations() {{
|
expectCloseEndpoint();
|
||||||
oneOf(rendezvousEndpoint).close();
|
|
||||||
}});
|
|
||||||
|
|
||||||
rendezvousPoller.eventOccurred(
|
rendezvousPoller.eventOccurred(
|
||||||
new PendingContactRemovedEvent(pendingContact.getId()));
|
new PendingContactRemovedEvent(pendingContact.getId()));
|
||||||
@@ -238,7 +237,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
// Add the pending contact - endpoint should be created and polled
|
// Add the pending contact - endpoint should be created and polled
|
||||||
expectAddUnexpiredPendingContact(beforeExpiry);
|
expectAddPendingContact(beforeExpiry, WAITING_FOR_CONNECTION);
|
||||||
expectDeriveRendezvousKey();
|
expectDeriveRendezvousKey();
|
||||||
expectCreateEndpoint();
|
expectCreateEndpoint();
|
||||||
|
|
||||||
@@ -260,10 +259,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
// Run the poll task - pending contact expires, endpoint is closed
|
// Run the poll task - pending contact expires, endpoint is closed
|
||||||
expectPendingContactExpires(afterExpiry);
|
expectPendingContactExpires(afterExpiry);
|
||||||
|
expectCloseEndpoint();
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(rendezvousEndpoint).close();
|
|
||||||
}});
|
|
||||||
|
|
||||||
capturePollTask.get().run();
|
capturePollTask.get().run();
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
@@ -289,7 +285,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
// Add the pending contact - no endpoints should be created yet
|
// Add the pending contact - no endpoints should be created yet
|
||||||
expectAddUnexpiredPendingContact(beforeExpiry);
|
expectAddPendingContact(beforeExpiry, OFFLINE);
|
||||||
expectDeriveRendezvousKey();
|
expectDeriveRendezvousKey();
|
||||||
|
|
||||||
rendezvousPoller.eventOccurred(
|
rendezvousPoller.eventOccurred(
|
||||||
@@ -299,14 +295,14 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
// Enable the transport - endpoint should be created
|
// Enable the transport - endpoint should be created
|
||||||
expectGetPlugin();
|
expectGetPlugin();
|
||||||
expectCreateEndpoint();
|
expectCreateEndpoint();
|
||||||
|
expectStateChangedEvent(WAITING_FOR_CONNECTION);
|
||||||
|
|
||||||
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId));
|
rendezvousPoller.eventOccurred(new TransportEnabledEvent(transportId));
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
|
|
||||||
// Disable the transport - endpoint should be closed
|
// Disable the transport - endpoint should be closed
|
||||||
context.checking(new Expectations() {{
|
expectCloseEndpoint();
|
||||||
oneOf(rendezvousEndpoint).close();
|
expectStateChangedEvent(OFFLINE);
|
||||||
}});
|
|
||||||
|
|
||||||
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId));
|
rendezvousPoller.eventOccurred(new TransportDisabledEvent(transportId));
|
||||||
context.assertIsSatisfied();
|
context.assertIsSatisfied();
|
||||||
@@ -482,13 +478,14 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
return capturePollTask;
|
return capturePollTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void expectAddUnexpiredPendingContact(long now) {
|
private void expectAddPendingContact(long now,
|
||||||
|
PendingContactState initialState) {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(clock).currentTimeMillis();
|
oneOf(clock).currentTimeMillis();
|
||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
|
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
|
||||||
PendingContactStateChangedEvent.class, e ->
|
PendingContactStateChangedEvent.class, e ->
|
||||||
e.getPendingContactState() == WAITING_FOR_CONNECTION)));
|
e.getPendingContactState() == initialState)));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,7 +543,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(now));
|
will(returnValue(now));
|
||||||
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
|
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
|
||||||
PendingContactStateChangedEvent.class, e ->
|
PendingContactStateChangedEvent.class, e ->
|
||||||
e.getPendingContactState() == WAITING_FOR_CONNECTION)));
|
e.getPendingContactState() == OFFLINE)));
|
||||||
// Capture the poll task
|
// Capture the poll task
|
||||||
oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)),
|
oneOf(scheduler).scheduleAtFixedRate(with(any(Runnable.class)),
|
||||||
with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS),
|
with(POLLING_INTERVAL_MS), with(POLLING_INTERVAL_MS),
|
||||||
@@ -576,4 +573,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
|
|||||||
e.getPendingContactState() == state)));
|
e.getPendingContactState() == state)));
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void expectCloseEndpoint() throws Exception {
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(rendezvousEndpoint).close();
|
||||||
|
}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.sync.GroupId;
|
|||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
import org.briarproject.bramble.api.sync.SyncRecordWriter;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
import org.briarproject.bramble.api.transport.StreamWriter;
|
import org.briarproject.bramble.api.transport.StreamWriter;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.bramble.test.DbExpectations;
|
import org.briarproject.bramble.test.DbExpectations;
|
||||||
@@ -49,6 +50,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
// Add listener
|
// Add listener
|
||||||
oneOf(eventBus).addListener(session);
|
oneOf(eventBus).addListener(session);
|
||||||
|
// Send the protocol versions
|
||||||
|
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
||||||
// No acks to send
|
// No acks to send
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(noAckTxn));
|
withNullableDbCallable(noAckTxn));
|
||||||
@@ -83,6 +86,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
|
|||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
// Add listener
|
// Add listener
|
||||||
oneOf(eventBus).addListener(session);
|
oneOf(eventBus).addListener(session);
|
||||||
|
// Send the protocol versions
|
||||||
|
oneOf(recordWriter).writeVersions(with(any(Versions.class)));
|
||||||
// One ack to send
|
// One ack to send
|
||||||
oneOf(db).transactionWithNullableResult(with(false),
|
oneOf(db).transactionWithNullableResult(with(false),
|
||||||
withNullableDbCallable(ackTxn));
|
withNullableDbCallable(ackTxn));
|
||||||
|
|||||||
@@ -10,11 +10,14 @@ import org.briarproject.bramble.api.sync.MessageFactory;
|
|||||||
import org.briarproject.bramble.api.sync.Offer;
|
import org.briarproject.bramble.api.sync.Offer;
|
||||||
import org.briarproject.bramble.api.sync.Request;
|
import org.briarproject.bramble.api.sync.Request;
|
||||||
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
import org.briarproject.bramble.api.sync.SyncRecordReader;
|
||||||
|
import org.briarproject.bramble.api.sync.Versions;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.jmock.Expectations;
|
import org.jmock.Expectations;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@@ -22,7 +25,9 @@ import static org.briarproject.bramble.api.record.Record.MAX_RECORD_PAYLOAD_BYTE
|
|||||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
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.OFFER;
|
||||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||||
|
import static org.briarproject.bramble.api.sync.RecordTypes.VERSIONS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||||
|
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_SUPPORTED_VERSIONS;
|
||||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@@ -35,12 +40,17 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
context.mock(MessageFactory.class);
|
context.mock(MessageFactory.class);
|
||||||
private final RecordReader recordReader = context.mock(RecordReader.class);
|
private final RecordReader recordReader = context.mock(RecordReader.class);
|
||||||
|
|
||||||
|
private SyncRecordReader reader;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
reader = new SyncRecordReaderImpl(messageFactory, recordReader);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||||
expectReadRecord(createAck());
|
expectReadRecord(createAck());
|
||||||
|
|
||||||
SyncRecordReader reader =
|
|
||||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
|
||||||
Ack ack = reader.readAck();
|
Ack ack = reader.readAck();
|
||||||
assertEquals(MAX_MESSAGE_IDS, ack.getMessageIds().size());
|
assertEquals(MAX_MESSAGE_IDS, ack.getMessageIds().size());
|
||||||
}
|
}
|
||||||
@@ -49,8 +59,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
public void testFormatExceptionIfAckIsEmpty() throws Exception {
|
public void testFormatExceptionIfAckIsEmpty() throws Exception {
|
||||||
expectReadRecord(createEmptyAck());
|
expectReadRecord(createEmptyAck());
|
||||||
|
|
||||||
SyncRecordReader reader =
|
|
||||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
|
||||||
reader.readAck();
|
reader.readAck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,8 +66,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
||||||
expectReadRecord(createOffer());
|
expectReadRecord(createOffer());
|
||||||
|
|
||||||
SyncRecordReader reader =
|
|
||||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
|
||||||
Offer offer = reader.readOffer();
|
Offer offer = reader.readOffer();
|
||||||
assertEquals(MAX_MESSAGE_IDS, offer.getMessageIds().size());
|
assertEquals(MAX_MESSAGE_IDS, offer.getMessageIds().size());
|
||||||
}
|
}
|
||||||
@@ -68,8 +74,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
|
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
|
||||||
expectReadRecord(createEmptyOffer());
|
expectReadRecord(createEmptyOffer());
|
||||||
|
|
||||||
SyncRecordReader reader =
|
|
||||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
|
||||||
reader.readOffer();
|
reader.readOffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,8 +81,6 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
||||||
expectReadRecord(createRequest());
|
expectReadRecord(createRequest());
|
||||||
|
|
||||||
SyncRecordReader reader =
|
|
||||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
|
||||||
Request request = reader.readRequest();
|
Request request = reader.readRequest();
|
||||||
assertEquals(MAX_MESSAGE_IDS, request.getMessageIds().size());
|
assertEquals(MAX_MESSAGE_IDS, request.getMessageIds().size());
|
||||||
}
|
}
|
||||||
@@ -87,11 +89,36 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
|
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
|
||||||
expectReadRecord(createEmptyRequest());
|
expectReadRecord(createEmptyRequest());
|
||||||
|
|
||||||
SyncRecordReader reader =
|
|
||||||
new SyncRecordReaderImpl(messageFactory, recordReader);
|
|
||||||
reader.readRequest();
|
reader.readRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoFormatExceptionIfVersionsIsMaximumSize()
|
||||||
|
throws Exception {
|
||||||
|
expectReadRecord(createVersions(MAX_SUPPORTED_VERSIONS));
|
||||||
|
|
||||||
|
Versions versions = reader.readVersions();
|
||||||
|
List<Byte> supported = versions.getSupportedVersions();
|
||||||
|
assertEquals(MAX_SUPPORTED_VERSIONS, supported.size());
|
||||||
|
for (int i = 0; i < supported.size(); i++) {
|
||||||
|
assertEquals(i, (int) supported.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testFormatExceptionIfVersionsIsEmpty() throws Exception {
|
||||||
|
expectReadRecord(createVersions(0));
|
||||||
|
|
||||||
|
reader.readVersions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testFormatExceptionIfVersionsIsTooLarge() throws Exception {
|
||||||
|
expectReadRecord(createVersions(MAX_SUPPORTED_VERSIONS + 1));
|
||||||
|
|
||||||
|
reader.readVersions();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
||||||
expectReadRecord(createAck());
|
expectReadRecord(createAck());
|
||||||
@@ -140,6 +167,12 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
|
|||||||
return new Record(PROTOCOL_VERSION, REQUEST, new byte[0]);
|
return new Record(PROTOCOL_VERSION, REQUEST, new byte[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Record createVersions(int numVersions) {
|
||||||
|
byte[] payload = new byte[numVersions];
|
||||||
|
for (int i = 0; i < payload.length; i++) payload[i] = (byte) i;
|
||||||
|
return new Record(PROTOCOL_VERSION, VERSIONS, payload);
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] createPayload() throws Exception {
|
private byte[] createPayload() throws Exception {
|
||||||
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
||||||
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_BYTES) {
|
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_BYTES) {
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static java.util.Collections.emptyMap;
|
import static java.util.Collections.emptyMap;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
@@ -80,24 +79,9 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testStartAndStop() throws Exception {
|
public void testStartAndStop() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
expectGetMessagesToValidate();
|
||||||
Transaction txn1 = new Transaction(null, true);
|
expectGetPendingMessages();
|
||||||
Transaction txn2 = new Transaction(null, true);
|
expectGetMessagesToShare();
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
|
||||||
// validateOutstandingMessages()
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
|
||||||
oneOf(db).getMessagesToValidate(txn);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
// deliverOutstandingMessages()
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
|
||||||
oneOf(db).getPendingMessages(txn1);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
// shareOutstandingMessages()
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn2));
|
|
||||||
oneOf(db).getMessagesToShare(txn2);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
}});
|
|
||||||
|
|
||||||
vm.startService();
|
vm.startService();
|
||||||
vm.stopService();
|
vm.stopService();
|
||||||
@@ -106,167 +90,134 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testMessagesAreValidatedAtStartup() throws Exception {
|
public void testMessagesAreValidatedAtStartup() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, true);
|
||||||
Transaction txn1 = new Transaction(null, true);
|
Transaction txn1 = new Transaction(null, false);
|
||||||
Transaction txn2 = new Transaction(null, false);
|
Transaction txn2 = new Transaction(null, true);
|
||||||
Transaction txn3 = new Transaction(null, true);
|
Transaction txn3 = new Transaction(null, false);
|
||||||
Transaction txn4 = new Transaction(null, false);
|
|
||||||
Transaction txn5 = new Transaction(null, true);
|
expectGetMessagesToValidate(messageId, messageId1);
|
||||||
Transaction txn6 = new Transaction(null, true);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
// Get messages to validate
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
|
||||||
oneOf(db).getMessagesToValidate(txn);
|
|
||||||
will(returnValue(asList(messageId, messageId1)));
|
|
||||||
// Load the first raw message and group
|
// Load the first raw message and group
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
oneOf(db).getMessage(txn1, messageId);
|
oneOf(db).getMessage(txn, messageId);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(db).getGroup(txn1, groupId);
|
oneOf(db).getGroup(txn, groupId);
|
||||||
will(returnValue(group));
|
will(returnValue(group));
|
||||||
// Validate the first message: valid
|
// Validate the first message: valid
|
||||||
oneOf(validator).validateMessage(message, group);
|
oneOf(validator).validateMessage(message, group);
|
||||||
will(returnValue(validResult));
|
will(returnValue(validResult));
|
||||||
// Store the validation result for the first message
|
// Store the validation result for the first message
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn2));
|
oneOf(db).transaction(with(false), withDbRunnable(txn1));
|
||||||
oneOf(db).mergeMessageMetadata(txn2, messageId, metadata);
|
oneOf(db).mergeMessageMetadata(txn1, messageId, metadata);
|
||||||
// Deliver the first message
|
// Deliver the first message
|
||||||
oneOf(hook).incomingMessage(txn2, message, metadata);
|
oneOf(hook).incomingMessage(txn1, message, metadata);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn2, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn2, messageId);
|
oneOf(db).getMessageDependents(txn1, messageId);
|
||||||
will(returnValue(emptyMap()));
|
will(returnValue(emptyMap()));
|
||||||
// Load the second raw message and group
|
// Load the second raw message and group
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn3));
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn2));
|
||||||
oneOf(db).getMessage(txn3, messageId1);
|
oneOf(db).getMessage(txn2, messageId1);
|
||||||
will(returnValue(message1));
|
will(returnValue(message1));
|
||||||
oneOf(db).getGroup(txn3, groupId);
|
oneOf(db).getGroup(txn2, groupId);
|
||||||
will(returnValue(group));
|
will(returnValue(group));
|
||||||
// Validate the second message: invalid
|
// Validate the second message: invalid
|
||||||
oneOf(validator).validateMessage(message1, group);
|
oneOf(validator).validateMessage(message1, group);
|
||||||
will(throwException(new InvalidMessageException()));
|
will(throwException(new InvalidMessageException()));
|
||||||
// Store the validation result for the second message
|
// Store the validation result for the second message
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn4));
|
oneOf(db).transaction(with(false), withDbRunnable(txn3));
|
||||||
oneOf(db).getMessageState(txn4, messageId1);
|
oneOf(db).getMessageState(txn3, messageId1);
|
||||||
will(returnValue(UNKNOWN));
|
will(returnValue(UNKNOWN));
|
||||||
oneOf(db).setMessageState(txn4, messageId1, INVALID);
|
oneOf(db).setMessageState(txn3, messageId1, INVALID);
|
||||||
oneOf(db).deleteMessage(txn4, messageId1);
|
oneOf(db).deleteMessage(txn3, messageId1);
|
||||||
oneOf(db).deleteMessageMetadata(txn4, messageId1);
|
oneOf(db).deleteMessageMetadata(txn3, messageId1);
|
||||||
// Recursively invalidate any dependents
|
// Recursively invalidate any dependents
|
||||||
oneOf(db).getMessageDependents(txn4, messageId1);
|
oneOf(db).getMessageDependents(txn3, messageId1);
|
||||||
will(returnValue(emptyMap()));
|
will(returnValue(emptyMap()));
|
||||||
// Get pending messages to deliver
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn5));
|
|
||||||
oneOf(db).getPendingMessages(txn5);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
// Get messages to share
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn6));
|
|
||||||
oneOf(db).getMessagesToShare(txn6);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
expectGetPendingMessages();
|
||||||
|
expectGetMessagesToShare();
|
||||||
|
|
||||||
vm.startService();
|
vm.startService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPendingMessagesAreDeliveredAtStartup() throws Exception {
|
public void testPendingMessagesAreDeliveredAtStartup() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, false);
|
||||||
Transaction txn1 = new Transaction(null, true);
|
Transaction txn1 = new Transaction(null, false);
|
||||||
Transaction txn2 = new Transaction(null, false);
|
|
||||||
Transaction txn3 = new Transaction(null, false);
|
expectGetMessagesToValidate();
|
||||||
Transaction txn4 = new Transaction(null, true);
|
expectGetPendingMessages(messageId);
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
// Get messages to validate
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
|
||||||
oneOf(db).getMessagesToValidate(txn);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
// Get pending messages to deliver
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
|
||||||
oneOf(db).getPendingMessages(txn1);
|
|
||||||
will(returnValue(singletonList(messageId)));
|
|
||||||
// Check whether the message is ready to deliver
|
// Check whether the message is ready to deliver
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn2));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(db).getMessageState(txn2, messageId);
|
oneOf(db).getMessageState(txn, messageId);
|
||||||
will(returnValue(PENDING));
|
will(returnValue(PENDING));
|
||||||
oneOf(db).getMessageDependencies(txn2, messageId);
|
oneOf(db).getMessageDependencies(txn, messageId);
|
||||||
will(returnValue(singletonMap(messageId1, DELIVERED)));
|
will(returnValue(singletonMap(messageId1, DELIVERED)));
|
||||||
// Get the message and its metadata to deliver
|
// Get the message and its metadata to deliver
|
||||||
oneOf(db).getMessage(txn2, messageId);
|
oneOf(db).getMessage(txn, messageId);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
oneOf(db).getGroup(txn2, groupId);
|
oneOf(db).getGroup(txn, groupId);
|
||||||
will(returnValue(group));
|
will(returnValue(group));
|
||||||
oneOf(db).getMessageMetadataForValidator(txn2, messageId);
|
oneOf(db).getMessageMetadataForValidator(txn, messageId);
|
||||||
will(returnValue(new Metadata()));
|
will(returnValue(new Metadata()));
|
||||||
// Deliver the message
|
// Deliver the message
|
||||||
oneOf(hook).incomingMessage(txn2, message, metadata);
|
oneOf(hook).incomingMessage(txn, message, metadata);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn2, messageId, DELIVERED);
|
oneOf(db).setMessageState(txn, messageId, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn2, messageId);
|
oneOf(db).getMessageDependents(txn, messageId);
|
||||||
will(returnValue(singletonMap(messageId2, PENDING)));
|
will(returnValue(singletonMap(messageId2, PENDING)));
|
||||||
// Check whether the dependent is ready to deliver
|
// Check whether the dependent is ready to deliver
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn3));
|
oneOf(db).transaction(with(false), withDbRunnable(txn1));
|
||||||
oneOf(db).getMessageState(txn3, messageId2);
|
oneOf(db).getMessageState(txn1, messageId2);
|
||||||
will(returnValue(PENDING));
|
will(returnValue(PENDING));
|
||||||
oneOf(db).getMessageDependencies(txn3, messageId2);
|
oneOf(db).getMessageDependencies(txn1, messageId2);
|
||||||
will(returnValue(singletonMap(messageId1, DELIVERED)));
|
will(returnValue(singletonMap(messageId1, DELIVERED)));
|
||||||
// Get the dependent and its metadata to deliver
|
// Get the dependent and its metadata to deliver
|
||||||
oneOf(db).getMessage(txn3, messageId2);
|
oneOf(db).getMessage(txn1, messageId2);
|
||||||
will(returnValue(message2));
|
will(returnValue(message2));
|
||||||
oneOf(db).getGroup(txn3, groupId);
|
oneOf(db).getGroup(txn1, groupId);
|
||||||
will(returnValue(group));
|
will(returnValue(group));
|
||||||
oneOf(db).getMessageMetadataForValidator(txn3, messageId2);
|
oneOf(db).getMessageMetadataForValidator(txn1, messageId2);
|
||||||
will(returnValue(metadata));
|
will(returnValue(metadata));
|
||||||
// Deliver the dependent
|
// Deliver the dependent
|
||||||
oneOf(hook).incomingMessage(txn3, message2, metadata);
|
oneOf(hook).incomingMessage(txn1, message2, metadata);
|
||||||
will(returnValue(false));
|
will(returnValue(false));
|
||||||
oneOf(db).setMessageState(txn3, messageId2, DELIVERED);
|
oneOf(db).setMessageState(txn1, messageId2, DELIVERED);
|
||||||
// Get any pending dependents
|
// Get any pending dependents
|
||||||
oneOf(db).getMessageDependents(txn3, messageId2);
|
oneOf(db).getMessageDependents(txn1, messageId2);
|
||||||
will(returnValue(emptyMap()));
|
will(returnValue(emptyMap()));
|
||||||
|
|
||||||
// Get messages to share
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn4));
|
|
||||||
oneOf(db).getMessagesToShare(txn4);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
expectGetMessagesToShare();
|
||||||
|
|
||||||
vm.startService();
|
vm.startService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMessagesAreSharedAtStartup() throws Exception {
|
public void testMessagesAreSharedAtStartup() throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, false);
|
||||||
Transaction txn1 = new Transaction(null, true);
|
Transaction txn1 = new Transaction(null, false);
|
||||||
Transaction txn2 = new Transaction(null, true);
|
|
||||||
Transaction txn3 = new Transaction(null, false);
|
expectGetMessagesToValidate();
|
||||||
Transaction txn4 = new Transaction(null, false);
|
expectGetPendingMessages();
|
||||||
|
expectGetMessagesToShare(messageId);
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
// No messages to validate
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
|
||||||
oneOf(db).getMessagesToValidate(txn);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
// No pending messages to deliver
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
|
||||||
oneOf(db).getPendingMessages(txn1);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
|
|
||||||
// Get messages to share
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn2));
|
|
||||||
oneOf(db).getMessagesToShare(txn2);
|
|
||||||
will(returnValue(singletonList(messageId)));
|
|
||||||
// Share message and get dependencies
|
// Share message and get dependencies
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn3));
|
oneOf(db).transaction(with(false), withDbRunnable(txn));
|
||||||
oneOf(db).setMessageShared(txn3, messageId);
|
oneOf(db).setMessageShared(txn, messageId);
|
||||||
oneOf(db).getMessageDependencies(txn3, messageId);
|
oneOf(db).getMessageDependencies(txn, messageId);
|
||||||
will(returnValue(singletonMap(messageId2, DELIVERED)));
|
will(returnValue(singletonMap(messageId2, DELIVERED)));
|
||||||
// Share dependency
|
// Share dependency
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn4));
|
oneOf(db).transaction(with(false), withDbRunnable(txn1));
|
||||||
oneOf(db).setMessageShared(txn4, messageId2);
|
oneOf(db).setMessageShared(txn1, messageId2);
|
||||||
oneOf(db).getMessageDependencies(txn4, messageId2);
|
oneOf(db).getMessageDependencies(txn1, messageId2);
|
||||||
will(returnValue(emptyMap()));
|
will(returnValue(emptyMap()));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -318,49 +269,39 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, true);
|
||||||
Transaction txn1 = new Transaction(null, true);
|
Transaction txn1 = new Transaction(null, true);
|
||||||
Transaction txn2 = new Transaction(null, true);
|
Transaction txn2 = new Transaction(null, false);
|
||||||
Transaction txn3 = new Transaction(null, false);
|
|
||||||
Transaction txn4 = new Transaction(null, true);
|
expectGetMessagesToValidate(messageId, messageId1);
|
||||||
Transaction txn5 = new Transaction(null, true);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
// Get messages to validate
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
|
||||||
oneOf(db).getMessagesToValidate(txn);
|
|
||||||
will(returnValue(asList(messageId, messageId1)));
|
|
||||||
// Load the first raw message - *gasp* it's gone!
|
// Load the first raw message - *gasp* it's gone!
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
oneOf(db).getMessage(txn1, messageId);
|
oneOf(db).getMessage(txn, messageId);
|
||||||
will(throwException(new NoSuchMessageException()));
|
will(throwException(new NoSuchMessageException()));
|
||||||
// Load the second raw message and group
|
// Load the second raw message and group
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn2));
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
||||||
oneOf(db).getMessage(txn2, messageId1);
|
oneOf(db).getMessage(txn1, messageId1);
|
||||||
will(returnValue(message1));
|
will(returnValue(message1));
|
||||||
oneOf(db).getGroup(txn2, groupId);
|
oneOf(db).getGroup(txn1, groupId);
|
||||||
will(returnValue(group));
|
will(returnValue(group));
|
||||||
// Validate the second message: invalid
|
// Validate the second message: invalid
|
||||||
oneOf(validator).validateMessage(message1, group);
|
oneOf(validator).validateMessage(message1, group);
|
||||||
will(throwException(new InvalidMessageException()));
|
will(throwException(new InvalidMessageException()));
|
||||||
// Invalidate the second message
|
// Invalidate the second message
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn3));
|
oneOf(db).transaction(with(false), withDbRunnable(txn2));
|
||||||
oneOf(db).getMessageState(txn3, messageId1);
|
oneOf(db).getMessageState(txn2, messageId1);
|
||||||
will(returnValue(UNKNOWN));
|
will(returnValue(UNKNOWN));
|
||||||
oneOf(db).setMessageState(txn3, messageId1, INVALID);
|
oneOf(db).setMessageState(txn2, messageId1, INVALID);
|
||||||
oneOf(db).deleteMessage(txn3, messageId1);
|
oneOf(db).deleteMessage(txn2, messageId1);
|
||||||
oneOf(db).deleteMessageMetadata(txn3, messageId1);
|
oneOf(db).deleteMessageMetadata(txn2, messageId1);
|
||||||
// Recursively invalidate dependents
|
// Recursively invalidate dependents
|
||||||
oneOf(db).getMessageDependents(txn3, messageId1);
|
oneOf(db).getMessageDependents(txn2, messageId1);
|
||||||
will(returnValue(emptyMap()));
|
will(returnValue(emptyMap()));
|
||||||
// Get pending messages to deliver
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn4));
|
|
||||||
oneOf(db).getPendingMessages(txn4);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
// Get messages to share
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn5));
|
|
||||||
oneOf(db).getMessagesToShare(txn5);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
expectGetPendingMessages();
|
||||||
|
expectGetMessagesToShare();
|
||||||
|
|
||||||
vm.startService();
|
vm.startService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,52 +310,42 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
Transaction txn = new Transaction(null, true);
|
Transaction txn = new Transaction(null, true);
|
||||||
Transaction txn1 = new Transaction(null, true);
|
Transaction txn1 = new Transaction(null, true);
|
||||||
Transaction txn2 = new Transaction(null, true);
|
Transaction txn2 = new Transaction(null, false);
|
||||||
Transaction txn3 = new Transaction(null, false);
|
|
||||||
Transaction txn4 = new Transaction(null, true);
|
expectGetMessagesToValidate(messageId, messageId1);
|
||||||
Transaction txn5 = new Transaction(null, true);
|
|
||||||
|
|
||||||
context.checking(new DbExpectations() {{
|
context.checking(new DbExpectations() {{
|
||||||
// Get messages to validate
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
|
||||||
oneOf(db).getMessagesToValidate(txn);
|
|
||||||
will(returnValue(asList(messageId, messageId1)));
|
|
||||||
// Load the first raw message
|
// Load the first raw message
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
oneOf(db).getMessage(txn1, messageId);
|
oneOf(db).getMessage(txn, messageId);
|
||||||
will(returnValue(message));
|
will(returnValue(message));
|
||||||
// Load the group - *gasp* it's gone!
|
// Load the group - *gasp* it's gone!
|
||||||
oneOf(db).getGroup(txn1, groupId);
|
oneOf(db).getGroup(txn, groupId);
|
||||||
will(throwException(new NoSuchGroupException()));
|
will(throwException(new NoSuchGroupException()));
|
||||||
// Load the second raw message and group
|
// Load the second raw message and group
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn2));
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn1));
|
||||||
oneOf(db).getMessage(txn2, messageId1);
|
oneOf(db).getMessage(txn1, messageId1);
|
||||||
will(returnValue(message1));
|
will(returnValue(message1));
|
||||||
oneOf(db).getGroup(txn2, groupId);
|
oneOf(db).getGroup(txn1, groupId);
|
||||||
will(returnValue(group));
|
will(returnValue(group));
|
||||||
// Validate the second message: invalid
|
// Validate the second message: invalid
|
||||||
oneOf(validator).validateMessage(message1, group);
|
oneOf(validator).validateMessage(message1, group);
|
||||||
will(throwException(new InvalidMessageException()));
|
will(throwException(new InvalidMessageException()));
|
||||||
// Store the validation result for the second message
|
// Store the validation result for the second message
|
||||||
oneOf(db).transaction(with(false), withDbRunnable(txn3));
|
oneOf(db).transaction(with(false), withDbRunnable(txn2));
|
||||||
oneOf(db).getMessageState(txn3, messageId1);
|
oneOf(db).getMessageState(txn2, messageId1);
|
||||||
will(returnValue(UNKNOWN));
|
will(returnValue(UNKNOWN));
|
||||||
oneOf(db).setMessageState(txn3, messageId1, INVALID);
|
oneOf(db).setMessageState(txn2, messageId1, INVALID);
|
||||||
oneOf(db).deleteMessage(txn3, messageId1);
|
oneOf(db).deleteMessage(txn2, messageId1);
|
||||||
oneOf(db).deleteMessageMetadata(txn3, messageId1);
|
oneOf(db).deleteMessageMetadata(txn2, messageId1);
|
||||||
// Recursively invalidate dependents
|
// Recursively invalidate dependents
|
||||||
oneOf(db).getMessageDependents(txn3, messageId1);
|
oneOf(db).getMessageDependents(txn2, messageId1);
|
||||||
will(returnValue(emptyMap()));
|
will(returnValue(emptyMap()));
|
||||||
// Get pending messages to deliver
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn4));
|
|
||||||
oneOf(db).getPendingMessages(txn4);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
// Get messages to share
|
|
||||||
oneOf(db).transactionWithResult(with(true), withDbCallable(txn5));
|
|
||||||
oneOf(db).getMessagesToShare(txn5);
|
|
||||||
will(returnValue(emptyList()));
|
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
expectGetPendingMessages();
|
||||||
|
expectGetMessagesToShare();
|
||||||
|
|
||||||
vm.startService();
|
vm.startService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,4 +732,35 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
vm.eventOccurred(new MessageAddedEvent(message, contactId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void expectGetMessagesToValidate(MessageId... ids)
|
||||||
|
throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
|
oneOf(db).getMessagesToValidate(txn);
|
||||||
|
will(returnValue(asList(ids)));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectGetPendingMessages(MessageId... ids) throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
|
oneOf(db).getPendingMessages(txn);
|
||||||
|
will(returnValue(asList(ids)));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectGetMessagesToShare(MessageId... ids) throws Exception {
|
||||||
|
Transaction txn = new Transaction(null, true);
|
||||||
|
|
||||||
|
context.checking(new DbExpectations() {{
|
||||||
|
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
|
||||||
|
oneOf(db).getMessagesToShare(txn);
|
||||||
|
will(returnValue(asList(ids)));
|
||||||
|
}});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package org.briarproject.bramble.test;
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
|
import org.briarproject.bramble.battery.DefaultBatteryManagerModule;
|
||||||
import org.briarproject.bramble.event.DefaultEventExecutorModule;
|
import org.briarproject.bramble.event.DefaultEventExecutorModule;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
|
||||||
@Module(includes = {
|
@Module(includes = {
|
||||||
DefaultBatteryManagerModule.class,
|
DefaultBatteryManagerModule.class,
|
||||||
@@ -13,4 +15,20 @@ import dagger.Module;
|
|||||||
TestSecureRandomModule.class
|
TestSecureRandomModule.class
|
||||||
})
|
})
|
||||||
public class BrambleCoreIntegrationTestModule {
|
public class BrambleCoreIntegrationTestModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
FeatureFlags provideFeatureFlags() {
|
||||||
|
return new FeatureFlags() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableImageAttachments() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableRemoteContacts() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
localUpdateBody);
|
localUpdateBody);
|
||||||
will(returnValue(localUpdate));
|
will(returnValue(localUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, localUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, localUpdate,
|
||||||
localUpdateMeta, true);
|
localUpdateMeta, true, false);
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
localVersionsBody);
|
localVersionsBody);
|
||||||
will(returnValue(localVersions));
|
will(returnValue(localVersions));
|
||||||
oneOf(db).addLocalMessage(txn, localVersions, new Metadata(),
|
oneOf(db).addLocalMessage(txn, localVersions, new Metadata(),
|
||||||
false);
|
false, false);
|
||||||
// Inform contacts that client versions have changed
|
// Inform contacts that client versions have changed
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
@@ -259,7 +259,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalVersionsBody);
|
newLocalVersionsBody);
|
||||||
will(returnValue(newLocalVersions));
|
will(returnValue(newLocalVersions));
|
||||||
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
|
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
|
||||||
false);
|
false, false);
|
||||||
// Inform contacts that client versions have changed
|
// Inform contacts that client versions have changed
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
@@ -284,7 +284,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// No visibilities have changed
|
// No visibilities have changed
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -355,7 +355,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalVersionsBody);
|
newLocalVersionsBody);
|
||||||
will(returnValue(newLocalVersions));
|
will(returnValue(newLocalVersions));
|
||||||
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
|
oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(),
|
||||||
false);
|
false, false);
|
||||||
// Inform contacts that client versions have changed
|
// Inform contacts that client versions have changed
|
||||||
oneOf(db).getContacts(txn);
|
oneOf(db).getContacts(txn);
|
||||||
will(returnValue(singletonList(contact)));
|
will(returnValue(singletonList(contact)));
|
||||||
@@ -382,7 +382,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
|
||||||
}});
|
}});
|
||||||
@@ -567,7 +567,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
@@ -640,7 +640,7 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
|
|||||||
newLocalUpdateBody);
|
newLocalUpdateBody);
|
||||||
will(returnValue(newLocalUpdate));
|
will(returnValue(newLocalUpdate));
|
||||||
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
|
||||||
newLocalUpdateMeta, true);
|
newLocalUpdateMeta, true, false);
|
||||||
// The client's visibility has changed
|
// The client's visibility has changed
|
||||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||||
contactGroup.getId());
|
contactGroup.getId());
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.support.test.InstrumentationRegistry;
|
import android.support.test.InstrumentationRegistry;
|
||||||
@@ -21,7 +21,7 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class AttachmentControllerIntegrationTest {
|
public class AttachmentRetrieverIntegrationTest {
|
||||||
|
|
||||||
private static final String smallKitten =
|
private static final String smallKitten =
|
||||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg";
|
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg";
|
||||||
@@ -47,15 +47,15 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
);
|
);
|
||||||
private final MessageId msgId = new MessageId(getRandomId());
|
private final MessageId msgId = new MessageId(getRandomId());
|
||||||
|
|
||||||
private final AttachmentController controller =
|
private final AttachmentRetriever retriever =
|
||||||
new AttachmentController(null, dimensions);
|
new AttachmentRetriever(null, dimensions);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSmallJpegImage() throws Exception {
|
public void testSmallJpegImage() throws Exception {
|
||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(smallKitten);
|
InputStream is = getUrlInputStream(smallKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(160, item.getWidth());
|
assertEquals(160, item.getWidth());
|
||||||
assertEquals(240, item.getHeight());
|
assertEquals(240, item.getHeight());
|
||||||
@@ -71,7 +71,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(originalKitten);
|
InputStream is = getUrlInputStream(originalKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(1728, item.getWidth());
|
assertEquals(1728, item.getWidth());
|
||||||
assertEquals(2592, item.getHeight());
|
assertEquals(2592, item.getHeight());
|
||||||
@@ -87,7 +87,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
|
||||||
InputStream is = getUrlInputStream(pngKitten);
|
InputStream is = getUrlInputStream(pngKitten);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(msgId, item.getMessageId());
|
assertEquals(msgId, item.getMessageId());
|
||||||
assertEquals(737, item.getWidth());
|
assertEquals(737, item.getWidth());
|
||||||
assertEquals(510, item.getHeight());
|
assertEquals(510, item.getHeight());
|
||||||
@@ -103,7 +103,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(uberGif);
|
InputStream is = getUrlInputStream(uberGif);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -118,7 +118,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(lottaPixel);
|
InputStream is = getUrlInputStream(lottaPixel);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(64250, item.getWidth());
|
assertEquals(64250, item.getWidth());
|
||||||
assertEquals(64250, item.getHeight());
|
assertEquals(64250, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -133,7 +133,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(imageIoCrash);
|
InputStream is = getUrlInputStream(imageIoCrash);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(1184, item.getWidth());
|
assertEquals(1184, item.getWidth());
|
||||||
assertEquals(448, item.getHeight());
|
assertEquals(448, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -148,7 +148,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(gimpCrash);
|
InputStream is = getUrlInputStream(gimpCrash);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -163,7 +163,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(optiPngAfl);
|
InputStream is = getUrlInputStream(optiPngAfl);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(32, item.getWidth());
|
assertEquals(32, item.getWidth());
|
||||||
assertEquals(32, item.getHeight());
|
assertEquals(32, item.getHeight());
|
||||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||||
@@ -178,7 +178,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getUrlInputStream(librawError);
|
InputStream is = getUrlInputStream(librawError);
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertTrue(item.hasError());
|
assertTrue(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated.gif");
|
InputStream is = getAssetInputStream("animated.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(65535, item.getWidth());
|
assertEquals(65535, item.getWidth());
|
||||||
assertEquals(65535, item.getHeight());
|
assertEquals(65535, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -202,7 +202,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("animated2.gif");
|
InputStream is = getAssetInputStream("animated2.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(10000, item.getWidth());
|
assertEquals(10000, item.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -217,7 +217,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
|
||||||
InputStream is = getAssetInputStream("error_large.gif");
|
InputStream is = getAssetInputStream("error_large.gif");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(16384, item.getWidth());
|
assertEquals(16384, item.getWidth());
|
||||||
assertEquals(16384, item.getHeight());
|
assertEquals(16384, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -232,7 +232,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_high.jpg");
|
InputStream is = getAssetInputStream("error_high.jpg");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(1, item.getWidth());
|
assertEquals(1, item.getWidth());
|
||||||
assertEquals(10000, item.getHeight());
|
assertEquals(10000, item.getHeight());
|
||||||
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
|
||||||
@@ -247,7 +247,7 @@ public class AttachmentControllerIntegrationTest {
|
|||||||
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
|
||||||
InputStream is = getAssetInputStream("error_wide.jpg");
|
InputStream is = getAssetInputStream("error_wide.jpg");
|
||||||
Attachment a = new Attachment(is);
|
Attachment a = new Attachment(is);
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, a, true);
|
AttachmentItem item = retriever.getAttachmentItem(h, a, true);
|
||||||
assertEquals(1920, item.getWidth());
|
assertEquals(1920, item.getWidth());
|
||||||
assertEquals(1, item.getHeight());
|
assertEquals(1, item.getHeight());
|
||||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||||
@@ -7,6 +7,7 @@ import org.briarproject.bramble.BrambleAndroidModule;
|
|||||||
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
import org.briarproject.bramble.BrambleCoreEagerSingletons;
|
||||||
import org.briarproject.bramble.BrambleCoreModule;
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
import org.briarproject.bramble.account.BriarAccountModule;
|
import org.briarproject.bramble.account.BriarAccountModule;
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.account.AccountManager;
|
import org.briarproject.bramble.api.account.AccountManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
import org.briarproject.bramble.api.contact.ContactExchangeManager;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
@@ -161,6 +162,8 @@ public interface AndroidComponent
|
|||||||
|
|
||||||
ViewModelProvider.Factory viewModelFactory();
|
ViewModelProvider.Factory viewModelFactory();
|
||||||
|
|
||||||
|
FeatureFlags featureFlags();
|
||||||
|
|
||||||
void inject(SignInReminderReceiver briarService);
|
void inject(SignInReminderReceiver briarService);
|
||||||
|
|
||||||
void inject(BriarService briarService);
|
void inject(BriarService briarService);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.os.StrictMode;
|
|||||||
|
|
||||||
import com.vanniktech.emoji.RecentEmoji;
|
import com.vanniktech.emoji.RecentEmoji;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.battery.BatteryManager;
|
import org.briarproject.bramble.api.battery.BatteryManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
@@ -59,6 +60,7 @@ import static java.util.Arrays.asList;
|
|||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
||||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
|
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
|
||||||
|
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||||
|
|
||||||
@Module(includes = {ContactExchangeModule.class, ViewModelModule.class})
|
@Module(includes = {ContactExchangeModule.class, ViewModelModule.class})
|
||||||
public class AppModule {
|
public class AppModule {
|
||||||
@@ -230,4 +232,20 @@ public class AppModule {
|
|||||||
lifecycleManager.registerOpenDatabaseHook(recentEmoji);
|
lifecycleManager.registerOpenDatabaseHook(recentEmoji);
|
||||||
return recentEmoji;
|
return recentEmoji;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
FeatureFlags provideFeatureFlags() {
|
||||||
|
return new FeatureFlags() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableImageAttachments() {
|
||||||
|
return IS_DEBUG_BUILD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldEnableRemoteContacts() {
|
||||||
|
return IS_DEBUG_BUILD;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import org.acra.annotation.ReportsCrashes;
|
|||||||
import org.briarproject.bramble.BrambleAndroidModule;
|
import org.briarproject.bramble.BrambleAndroidModule;
|
||||||
import org.briarproject.bramble.BrambleCoreModule;
|
import org.briarproject.bramble.BrambleCoreModule;
|
||||||
import org.briarproject.briar.BriarCoreModule;
|
import org.briarproject.briar.BriarCoreModule;
|
||||||
|
import org.briarproject.briar.BuildConfig;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.logging.CachingLogHandler;
|
import org.briarproject.briar.android.logging.CachingLogHandler;
|
||||||
import org.briarproject.briar.android.reporting.BriarReportPrimer;
|
import org.briarproject.briar.android.reporting.BriarReportPrimer;
|
||||||
@@ -64,6 +65,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
|||||||
reportDialogClass = DevReportActivity.class,
|
reportDialogClass = DevReportActivity.class,
|
||||||
resDialogOkToast = R.string.dev_report_saved,
|
resDialogOkToast = R.string.dev_report_saved,
|
||||||
deleteOldUnsentReportsOnApplicationStart = false,
|
deleteOldUnsentReportsOnApplicationStart = false,
|
||||||
|
buildConfigClass = BuildConfig.class,
|
||||||
customReportContent = {
|
customReportContent = {
|
||||||
REPORT_ID,
|
REPORT_ID,
|
||||||
APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME,
|
APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME,
|
||||||
|
|||||||
@@ -30,15 +30,4 @@ public interface TestingConstants {
|
|||||||
long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ?
|
long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ?
|
||||||
BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L :
|
BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L :
|
||||||
Long.MAX_VALUE;
|
Long.MAX_VALUE;
|
||||||
|
|
||||||
/**
|
|
||||||
* Feature flag for enabling image attachments.
|
|
||||||
*/
|
|
||||||
boolean FEATURE_FLAG_IMAGE_ATTACHMENTS = IS_DEBUG_BUILD;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feature flag for enabling adding contacts at a distance.
|
|
||||||
*/
|
|
||||||
boolean FEATURE_FLAG_REMOTE_CONTACTS = IS_DEBUG_BUILD;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.arch.lifecycle.Observer;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
import org.jsoup.UnsupportedMimeTypeException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static java.util.logging.Level.INFO;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||||
|
|
||||||
|
@ThreadSafe
|
||||||
|
@NotNullByDefault
|
||||||
|
class AttachmentCreationTask {
|
||||||
|
|
||||||
|
private static Logger LOG =
|
||||||
|
getLogger(AttachmentCreationTask.class.getName());
|
||||||
|
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final ContentResolver contentResolver;
|
||||||
|
private final AttachmentRetriever retriever;
|
||||||
|
private final GroupId groupId;
|
||||||
|
private final Collection<Uri> uris;
|
||||||
|
private final boolean needsSize;
|
||||||
|
private final MutableLiveData<AttachmentResult> result;
|
||||||
|
|
||||||
|
private volatile boolean canceled = false;
|
||||||
|
|
||||||
|
AttachmentCreationTask(Executor ioExecutor,
|
||||||
|
MessagingManager messagingManager, ContentResolver contentResolver,
|
||||||
|
AttachmentRetriever retriever, GroupId groupId,
|
||||||
|
Collection<Uri> uris, boolean needsSize) {
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.contentResolver = contentResolver;
|
||||||
|
this.retriever = retriever;
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.uris = uris;
|
||||||
|
this.needsSize = needsSize;
|
||||||
|
result = new MutableLiveData<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
LiveData<AttachmentResult> getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the task, asynchronously waits for it to finish, and deletes any
|
||||||
|
* created attachments.
|
||||||
|
*/
|
||||||
|
void cancel() {
|
||||||
|
canceled = true;
|
||||||
|
// Observe the task until it finishes (which may already have happened)
|
||||||
|
result.observeForever(new Observer<AttachmentResult>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable AttachmentResult attachmentResult) {
|
||||||
|
requireNonNull(attachmentResult);
|
||||||
|
if (attachmentResult.isFinished()) {
|
||||||
|
deleteUnsentAttachments(attachmentResult.getItemResults());
|
||||||
|
result.removeObserver(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously creates and stores the attachments.
|
||||||
|
*/
|
||||||
|
void storeAttachments() {
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Storing " + uris.size() + " attachments");
|
||||||
|
List<AttachmentItemResult> results = new ArrayList<>();
|
||||||
|
for (Uri uri : uris) {
|
||||||
|
if (canceled) break;
|
||||||
|
results.add(processUri(uri));
|
||||||
|
result.postValue(new AttachmentResult(new ArrayList<>(results),
|
||||||
|
false, false));
|
||||||
|
}
|
||||||
|
result.postValue(new AttachmentResult(new ArrayList<>(results),
|
||||||
|
true, !canceled));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private AttachmentItemResult processUri(Uri uri) {
|
||||||
|
AttachmentHeader header = null;
|
||||||
|
try {
|
||||||
|
header = storeAttachment(uri);
|
||||||
|
Attachment a = retriever.getMessageAttachment(header);
|
||||||
|
AttachmentItem item =
|
||||||
|
retriever.getAttachmentItem(header, a, needsSize);
|
||||||
|
if (item.hasError()) throw new IOException();
|
||||||
|
if (needsSize) retriever.cachePut(item);
|
||||||
|
return new AttachmentItemResult(uri, item);
|
||||||
|
} catch (DbException | IOException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
// If the attachment was already stored, delete it
|
||||||
|
tryToRemove(header);
|
||||||
|
canceled = true;
|
||||||
|
return new AttachmentItemResult(uri, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IoExecutor
|
||||||
|
private AttachmentHeader storeAttachment(Uri uri)
|
||||||
|
throws IOException, DbException {
|
||||||
|
long start = now();
|
||||||
|
String contentType = contentResolver.getType(uri);
|
||||||
|
if (contentType == null) throw new IOException("null content type");
|
||||||
|
if (!isValidMimeType(contentType)) {
|
||||||
|
String uriString = uri.toString();
|
||||||
|
throw new UnsupportedMimeTypeException("", contentType, uriString);
|
||||||
|
}
|
||||||
|
InputStream is = contentResolver.openInputStream(uri);
|
||||||
|
if (is == null) throw new IOException();
|
||||||
|
long timestamp = System.currentTimeMillis();
|
||||||
|
AttachmentHeader h = messagingManager
|
||||||
|
.addLocalAttachment(groupId, timestamp, contentType, is);
|
||||||
|
tryToClose(is, LOG, WARNING);
|
||||||
|
logDuration(LOG, "Storing attachment", start);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidMimeType(String mimeType) {
|
||||||
|
for (String supportedType : IMAGE_MIME_TYPES) {
|
||||||
|
if (supportedType.equals(mimeType)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tryToRemove(@Nullable AttachmentHeader h) {
|
||||||
|
try {
|
||||||
|
if (h != null) messagingManager.removeAttachment(h);
|
||||||
|
} catch (DbException e1) {
|
||||||
|
logException(LOG, WARNING, e1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteUnsentAttachments(
|
||||||
|
Collection<AttachmentItemResult> itemResults) {
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>(itemResults.size());
|
||||||
|
for (AttachmentItemResult itemResult : itemResults) {
|
||||||
|
AttachmentItem item = itemResult.getItem();
|
||||||
|
if (item != null) headers.add(item.getHeader());
|
||||||
|
}
|
||||||
|
if (LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Deleting " + headers.size() + " unsent attachments");
|
||||||
|
ioExecutor.execute(() -> {
|
||||||
|
for (AttachmentHeader header : headers) {
|
||||||
|
try {
|
||||||
|
messagingManager.removeAttachment(header);
|
||||||
|
} catch (DbException e) {
|
||||||
|
logException(LOG, WARNING, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentCreator {
|
||||||
|
|
||||||
|
private final Application app;
|
||||||
|
@IoExecutor
|
||||||
|
private final Executor ioExecutor;
|
||||||
|
private final MessagingManager messagingManager;
|
||||||
|
private final AttachmentRetriever retriever;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private AttachmentCreationTask task;
|
||||||
|
|
||||||
|
public AttachmentCreator(Application app, @IoExecutor Executor ioExecutor,
|
||||||
|
MessagingManager messagingManager, AttachmentRetriever retriever) {
|
||||||
|
this.app = app;
|
||||||
|
this.ioExecutor = ioExecutor;
|
||||||
|
this.messagingManager = messagingManager;
|
||||||
|
this.retriever = retriever;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a background task to create attachments from the given URIs and
|
||||||
|
* returns a LiveData to monitor the progress of the task.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> storeAttachments(GroupId groupId,
|
||||||
|
Collection<Uri> uris) {
|
||||||
|
if (task != null) throw new IllegalStateException();
|
||||||
|
boolean needsSize = uris.size() == 1;
|
||||||
|
task = new AttachmentCreationTask(ioExecutor, messagingManager,
|
||||||
|
app.getContentResolver(), retriever, groupId, uris, needsSize);
|
||||||
|
task.storeAttachments();
|
||||||
|
return task.getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be only called after configuration changes.
|
||||||
|
* In this case we should not create new attachments.
|
||||||
|
* They are already being created and returned by the existing LiveData.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||||
|
if (task == null) throw new IllegalStateException();
|
||||||
|
// A task is already running. It will update the result LiveData.
|
||||||
|
// So nothing more to do here.
|
||||||
|
return task.getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the headers of any attachments created by
|
||||||
|
* {@link #storeAttachments(GroupId, Collection)}, unless
|
||||||
|
* {@link #onAttachmentsSent()} or {@link #cancel()} has been called.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
||||||
|
if (task == null) return emptyList();
|
||||||
|
AttachmentResult result = task.getResult().getValue();
|
||||||
|
if (result == null) return emptyList();
|
||||||
|
List<AttachmentHeader> headers = new ArrayList<>();
|
||||||
|
for (AttachmentItemResult itemResult : result.getItemResults()) {
|
||||||
|
AttachmentItem item = itemResult.getItem();
|
||||||
|
if (item != null) headers.add(item.getHeader());
|
||||||
|
}
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the AttachmentCreator that the attachments created by
|
||||||
|
* {@link #storeAttachments(GroupId, Collection)} will be sent.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
public void onAttachmentsSent() {
|
||||||
|
task = null; // Prevent cancel() from cancelling the task
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the task started by
|
||||||
|
* {@link #storeAttachments(GroupId, Collection)}, if any, unless
|
||||||
|
* {@link #onAttachmentsSent()} has been called.
|
||||||
|
*/
|
||||||
|
@UiThread
|
||||||
|
public void cancel() {
|
||||||
|
if (task != null) {
|
||||||
|
task.cancel();
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
class AttachmentDimensions {
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentDimensions {
|
||||||
|
|
||||||
final int defaultSize;
|
final int defaultSize;
|
||||||
final int minWidth, maxWidth;
|
final int minWidth, maxWidth;
|
||||||
@@ -21,7 +26,7 @@ class AttachmentDimensions {
|
|||||||
this.maxHeight = maxHeight;
|
this.maxHeight = maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
public static AttachmentDimensions getAttachmentDimensions(Resources res) {
|
||||||
int defaultSize =
|
int defaultSize =
|
||||||
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
|
||||||
int minWidth = res.getDimensionPixelSize(
|
int minWidth = res.getDimensionPixelSize(
|
||||||
@@ -33,7 +38,7 @@ class AttachmentDimensions {
|
|||||||
int maxHeight = res.getDimensionPixelSize(
|
int maxHeight = res.getDimensionPixelSize(
|
||||||
R.dimen.message_bubble_image_max_height);
|
R.dimen.message_bubble_image_max_height);
|
||||||
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
|
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
|
||||||
minHeight, minHeight);
|
minHeight, maxHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
@@ -6,18 +6,21 @@ import android.support.annotation.Nullable;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentItem implements Parcelable {
|
public class AttachmentItem implements Parcelable {
|
||||||
|
|
||||||
private final MessageId messageId;
|
private final AttachmentHeader header;
|
||||||
private final int width, height;
|
private final int width, height;
|
||||||
private final String mimeType, extension;
|
private final String extension;
|
||||||
private final int thumbnailWidth, thumbnailHeight;
|
private final int thumbnailWidth, thumbnailHeight;
|
||||||
private final boolean hasError;
|
private final boolean hasError;
|
||||||
private final long instanceId;
|
private final long instanceId;
|
||||||
@@ -37,13 +40,12 @@ public class AttachmentItem implements Parcelable {
|
|||||||
|
|
||||||
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
|
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
|
||||||
|
|
||||||
AttachmentItem(MessageId messageId, int width, int height, String mimeType,
|
AttachmentItem(AttachmentHeader header, int width, int height,
|
||||||
String extension, int thumbnailWidth, int thumbnailHeight,
|
String extension, int thumbnailWidth, int thumbnailHeight,
|
||||||
boolean hasError) {
|
boolean hasError) {
|
||||||
this.messageId = messageId;
|
this.header = header;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.mimeType = mimeType;
|
|
||||||
this.extension = extension;
|
this.extension = extension;
|
||||||
this.thumbnailWidth = thumbnailWidth;
|
this.thumbnailWidth = thumbnailWidth;
|
||||||
this.thumbnailHeight = thumbnailHeight;
|
this.thumbnailHeight = thumbnailHeight;
|
||||||
@@ -54,19 +56,24 @@ public class AttachmentItem implements Parcelable {
|
|||||||
protected AttachmentItem(Parcel in) {
|
protected AttachmentItem(Parcel in) {
|
||||||
byte[] messageIdByte = new byte[MessageId.LENGTH];
|
byte[] messageIdByte = new byte[MessageId.LENGTH];
|
||||||
in.readByteArray(messageIdByte);
|
in.readByteArray(messageIdByte);
|
||||||
messageId = new MessageId(messageIdByte);
|
MessageId messageId = new MessageId(messageIdByte);
|
||||||
width = in.readInt();
|
width = in.readInt();
|
||||||
height = in.readInt();
|
height = in.readInt();
|
||||||
mimeType = in.readString();
|
String mimeType = requireNonNull(in.readString());
|
||||||
extension = in.readString();
|
extension = requireNonNull(in.readString());
|
||||||
thumbnailWidth = in.readInt();
|
thumbnailWidth = in.readInt();
|
||||||
thumbnailHeight = in.readInt();
|
thumbnailHeight = in.readInt();
|
||||||
hasError = in.readByte() != 0;
|
hasError = in.readByte() != 0;
|
||||||
instanceId = in.readLong();
|
instanceId = in.readLong();
|
||||||
|
header = new AttachmentHeader(messageId, mimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentHeader getHeader() {
|
||||||
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageId getMessageId() {
|
public MessageId getMessageId() {
|
||||||
return messageId;
|
return header.getMessageId();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getWidth() {
|
int getWidth() {
|
||||||
@@ -77,27 +84,27 @@ public class AttachmentItem implements Parcelable {
|
|||||||
return height;
|
return height;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getMimeType() {
|
public String getMimeType() {
|
||||||
return mimeType;
|
return header.getContentType();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getExtension() {
|
public String getExtension() {
|
||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getThumbnailWidth() {
|
public int getThumbnailWidth() {
|
||||||
return thumbnailWidth;
|
return thumbnailWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getThumbnailHeight() {
|
public int getThumbnailHeight() {
|
||||||
return thumbnailHeight;
|
return thumbnailHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasError() {
|
public boolean hasError() {
|
||||||
return hasError;
|
return hasError;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTransitionName() {
|
public String getTransitionName() {
|
||||||
return String.valueOf(instanceId);
|
return String.valueOf(instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,10 +115,10 @@ public class AttachmentItem implements Parcelable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeByteArray(messageId.getBytes());
|
dest.writeByteArray(header.getMessageId().getBytes());
|
||||||
dest.writeInt(width);
|
dest.writeInt(width);
|
||||||
dest.writeInt(height);
|
dest.writeInt(height);
|
||||||
dest.writeString(mimeType);
|
dest.writeString(header.getContentType());
|
||||||
dest.writeString(extension);
|
dest.writeString(extension);
|
||||||
dest.writeInt(thumbnailWidth);
|
dest.writeInt(thumbnailWidth);
|
||||||
dest.writeInt(thumbnailHeight);
|
dest.writeInt(thumbnailHeight);
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentItemResult {
|
||||||
|
|
||||||
|
private final Uri uri;
|
||||||
|
@Nullable
|
||||||
|
private final AttachmentItem item;
|
||||||
|
@Nullable
|
||||||
|
private final Exception exception;
|
||||||
|
|
||||||
|
AttachmentItemResult(Uri uri, AttachmentItem item) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.item = item;
|
||||||
|
this.exception = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachmentItemResult(Uri uri, Exception exception) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.item = null;
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public AttachmentItem getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasError() {
|
||||||
|
return item == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Exception getException() {
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
public interface AttachmentManager {
|
||||||
|
|
||||||
|
LiveData<AttachmentResult> storeAttachments(Collection<Uri> uri,
|
||||||
|
boolean restart);
|
||||||
|
|
||||||
|
List<AttachmentHeader> getAttachmentHeadersForSending();
|
||||||
|
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
@NotNullByDefault
|
||||||
|
public class AttachmentResult {
|
||||||
|
|
||||||
|
private final Collection<AttachmentItemResult> itemResults;
|
||||||
|
private final boolean finished;
|
||||||
|
private final boolean success;
|
||||||
|
|
||||||
|
AttachmentResult(Collection<AttachmentItemResult> itemResults,
|
||||||
|
boolean finished, boolean success) {
|
||||||
|
this.itemResults = itemResults;
|
||||||
|
this.finished = finished;
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<AttachmentItemResult> getItemResults() {
|
||||||
|
return itemResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFinished() {
|
||||||
|
return finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.BitmapFactory.Options;
|
import android.graphics.BitmapFactory.Options;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.support.media.ExifInterface;
|
import android.support.media.ExifInterface;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
|
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
@@ -42,10 +43,11 @@ import static org.briarproject.bramble.util.LogUtils.logException;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AttachmentController {
|
public class AttachmentRetriever {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
getLogger(AttachmentController.class.getName());
|
getLogger(AttachmentRetriever.class.getName());
|
||||||
|
|
||||||
private static final int READ_LIMIT = 1024 * 8192;
|
private static final int READ_LIMIT = 1024 * 8192;
|
||||||
|
|
||||||
private final MessagingManager messagingManager;
|
private final MessagingManager messagingManager;
|
||||||
@@ -54,10 +56,11 @@ class AttachmentController {
|
|||||||
private final int minWidth, maxWidth;
|
private final int minWidth, maxWidth;
|
||||||
private final int minHeight, maxHeight;
|
private final int minHeight, maxHeight;
|
||||||
|
|
||||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
private final Map<MessageId, AttachmentItem> attachmentCache =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
AttachmentController(MessagingManager messagingManager,
|
@VisibleForTesting
|
||||||
|
AttachmentRetriever(MessagingManager messagingManager,
|
||||||
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
AttachmentDimensions dimensions, ImageHelper imageHelper) {
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.imageHelper = imageHelper;
|
this.imageHelper = imageHelper;
|
||||||
@@ -68,7 +71,7 @@ class AttachmentController {
|
|||||||
maxHeight = dimensions.maxHeight;
|
maxHeight = dimensions.maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachmentController(MessagingManager messagingManager,
|
public AttachmentRetriever(MessagingManager messagingManager,
|
||||||
AttachmentDimensions dimensions) {
|
AttachmentDimensions dimensions) {
|
||||||
this(messagingManager, dimensions, new ImageHelper() {
|
this(messagingManager, dimensions, new ImageHelper() {
|
||||||
@Override
|
@Override
|
||||||
@@ -91,17 +94,17 @@ class AttachmentController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void put(MessageId messageId, List<AttachmentItem> attachments) {
|
public void cachePut(AttachmentItem item) {
|
||||||
attachmentCache.put(messageId, attachments);
|
attachmentCache.put(item.getMessageId(), item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
List<AttachmentItem> get(MessageId messageId) {
|
public AttachmentItem cacheGet(MessageId attachmentId) {
|
||||||
return attachmentCache.get(messageId);
|
return attachmentCache.get(attachmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
public List<Pair<AttachmentHeader, Attachment>> getMessageAttachments(
|
||||||
List<AttachmentHeader> headers) throws DbException {
|
List<AttachmentHeader> headers) throws DbException {
|
||||||
long start = now();
|
long start = now();
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
@@ -110,16 +113,20 @@ class AttachmentController {
|
|||||||
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
Attachment a = messagingManager.getAttachment(h.getMessageId());
|
||||||
attachments.add(new Pair<>(h, a));
|
attachments.add(new Pair<>(h, a));
|
||||||
}
|
}
|
||||||
logDuration(LOG, "Loading attachment", start);
|
logDuration(LOG, "Loading attachments", start);
|
||||||
return attachments;
|
return attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Attachment getMessageAttachment(AttachmentHeader h) throws DbException {
|
||||||
|
return messagingManager.getAttachment(h.getMessageId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
|
||||||
* <p>
|
* <p>
|
||||||
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
* Note: This closes the {@link Attachment}'s {@link InputStream}.
|
||||||
*/
|
*/
|
||||||
List<AttachmentItem> getAttachmentItems(
|
public List<AttachmentItem> getAttachmentItems(
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
List<Pair<AttachmentHeader, Attachment>> attachments) {
|
||||||
boolean needsSize = attachments.size() == 1;
|
boolean needsSize = attachments.size() == 1;
|
||||||
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
List<AttachmentItem> items = new ArrayList<>(attachments.size());
|
||||||
@@ -137,17 +144,15 @@ class AttachmentController {
|
|||||||
*/
|
*/
|
||||||
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
|
||||||
boolean needsSize) {
|
boolean needsSize) {
|
||||||
MessageId messageId = h.getMessageId();
|
|
||||||
if (!needsSize) {
|
if (!needsSize) {
|
||||||
String mimeType = h.getContentType();
|
String extension =
|
||||||
String extension = imageHelper.getExtensionFromMimeType(mimeType);
|
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||||
boolean hasError = false;
|
boolean hasError = false;
|
||||||
if (extension == null) {
|
if (extension == null) {
|
||||||
extension = "";
|
extension = "";
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
return new AttachmentItem(messageId, 0, 0, mimeType, extension, 0,
|
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||||
0, hasError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Size size = new Size();
|
Size size = new Size();
|
||||||
@@ -185,10 +190,17 @@ class AttachmentController {
|
|||||||
// get file extension
|
// get file extension
|
||||||
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
|
||||||
boolean hasError = extension == null || size.error;
|
boolean hasError = extension == null || size.error;
|
||||||
|
if (!h.getContentType().equals(size.mimeType)) {
|
||||||
|
if (LOG.isLoggable(WARNING)) {
|
||||||
|
LOG.warning("Header has different mime type (" +
|
||||||
|
h.getContentType() + ") than image (" + size.mimeType +
|
||||||
|
").");
|
||||||
|
}
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
if (extension == null) extension = "";
|
if (extension == null) extension = "";
|
||||||
return new AttachmentItem(messageId, size.width, size.height,
|
return new AttachmentItem(h, size.width, size.height, extension,
|
||||||
size.mimeType, extension, thumbnailSize.width,
|
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||||
thumbnailSize.height, hasError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -21,6 +20,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -121,7 +121,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
ui.input.hideSoftKeyboard();
|
ui.input.hideSoftKeyboard();
|
||||||
feedController.repeatPost(item, text,
|
feedController.repeatPost(item, text,
|
||||||
new UiExceptionHandler<DbException>(this) {
|
new UiExceptionHandler<DbException>(this) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.blog;
|
package org.briarproject.briar.android.blog;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -27,6 +26,7 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
|
|||||||
import org.briarproject.briar.api.blog.BlogManager;
|
import org.briarproject.briar.api.blog.BlogManager;
|
||||||
import org.briarproject.briar.api.blog.BlogPost;
|
import org.briarproject.briar.api.blog.BlogPost;
|
||||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -120,7 +120,8 @@ public class WriteBlogPostActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
if (isNullOrEmpty(text)) throw new AssertionError();
|
if (isNullOrEmpty(text)) throw new AssertionError();
|
||||||
|
|
||||||
// hide publish button, show progress bar
|
// hide publish button, show progress bar
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
@@ -67,7 +68,6 @@ import static java.util.logging.Level.WARNING;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_REMOTE_CONTACTS;
|
|
||||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
|
|
||||||
@@ -85,6 +85,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
EventBus eventBus;
|
EventBus eventBus;
|
||||||
@Inject
|
@Inject
|
||||||
AndroidNotificationManager notificationManager;
|
AndroidNotificationManager notificationManager;
|
||||||
|
@Inject
|
||||||
|
FeatureFlags featureFlags;
|
||||||
|
|
||||||
private ContactListAdapter adapter;
|
private ContactListAdapter adapter;
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
@@ -124,7 +126,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
|||||||
container, false);
|
container, false);
|
||||||
|
|
||||||
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
|
FabSpeedDial speedDial = contentView.findViewById(R.id.speedDial);
|
||||||
if (FEATURE_FLAG_REMOTE_CONTACTS) {
|
if (featureFlags.shouldEnableRemoteContacts()) {
|
||||||
speedDial.addOnMenuItemClickListener(this);
|
speedDial.addOnMenuItemClickListener(this);
|
||||||
} else {
|
} else {
|
||||||
speedDial.setMenu(new FabSpeedDialMenu(contentView.getContext()));
|
speedDial.setMenu(new FabSpeedDialMenu(contentView.getContext()));
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.arch.lifecycle.ViewModelProvider;
|
|||||||
import android.arch.lifecycle.ViewModelProviders;
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
@@ -15,6 +16,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -22,6 +24,7 @@ import java.util.Collection;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
|
||||||
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
|
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
|
||||||
import static org.briarproject.briar.android.contact.add.remote.PendingContactItem.POLL_DURATION_MS;
|
import static org.briarproject.briar.android.contact.add.remote.PendingContactItem.POLL_DURATION_MS;
|
||||||
|
|
||||||
@@ -36,6 +39,7 @@ public class PendingContactListActivity extends BriarActivity
|
|||||||
private PendingContactListViewModel viewModel;
|
private PendingContactListViewModel viewModel;
|
||||||
private PendingContactListAdapter adapter;
|
private PendingContactListAdapter adapter;
|
||||||
private BriarRecyclerView list;
|
private BriarRecyclerView list;
|
||||||
|
private Snackbar offlineSnackbar;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void injectActivity(ActivityComponent component) {
|
public void injectActivity(ActivityComponent component) {
|
||||||
@@ -58,6 +62,8 @@ public class PendingContactListActivity extends BriarActivity
|
|||||||
viewModel.onCreate();
|
viewModel.onCreate();
|
||||||
viewModel.getPendingContacts()
|
viewModel.getPendingContacts()
|
||||||
.observe(this, this::onPendingContactsChanged);
|
.observe(this, this::onPendingContactsChanged);
|
||||||
|
viewModel.getHasInternetConnection()
|
||||||
|
.observe(this, this::onInternetConnectionChanged);
|
||||||
|
|
||||||
adapter = new PendingContactListAdapter(this, this,
|
adapter = new PendingContactListAdapter(this, this,
|
||||||
PendingContactItem.class);
|
PendingContactItem.class);
|
||||||
@@ -66,6 +72,10 @@ public class PendingContactListActivity extends BriarActivity
|
|||||||
list.setLayoutManager(new LinearLayoutManager(this));
|
list.setLayoutManager(new LinearLayoutManager(this));
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.showProgressBar();
|
list.showProgressBar();
|
||||||
|
|
||||||
|
offlineSnackbar = new BriarSnackbarBuilder()
|
||||||
|
.setBackgroundColor(R.color.briar_red)
|
||||||
|
.make(list, R.string.offline_state, LENGTH_INDEFINITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -130,4 +140,9 @@ public class PendingContactListActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onInternetConnectionChanged(boolean online) {
|
||||||
|
if (online) offlineSnackbar.dismiss();
|
||||||
|
else offlineSnackbar.show();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import javax.inject.Inject;
|
|||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
@@ -48,6 +49,8 @@ public class PendingContactListViewModel extends AndroidViewModel
|
|||||||
|
|
||||||
private final MutableLiveData<Collection<PendingContactItem>>
|
private final MutableLiveData<Collection<PendingContactItem>>
|
||||||
pendingContacts = new MutableLiveData<>();
|
pendingContacts = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> hasInternetConnection =
|
||||||
|
new MutableLiveData<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
PendingContactListViewModel(Application application,
|
PendingContactListViewModel(Application application,
|
||||||
@@ -88,13 +91,16 @@ public class PendingContactListViewModel extends AndroidViewModel
|
|||||||
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
Collection<Pair<PendingContact, PendingContactState>> pairs =
|
||||||
contactManager.getPendingContacts();
|
contactManager.getPendingContacts();
|
||||||
List<PendingContactItem> items = new ArrayList<>(pairs.size());
|
List<PendingContactItem> items = new ArrayList<>(pairs.size());
|
||||||
|
boolean online = false;
|
||||||
for (Pair<PendingContact, PendingContactState> pair : pairs) {
|
for (Pair<PendingContact, PendingContactState> pair : pairs) {
|
||||||
PendingContact p = pair.getFirst();
|
PendingContact p = pair.getFirst();
|
||||||
|
PendingContactState state = pair.getSecond();
|
||||||
long lastPoll = rendezvousPoller.getLastPollTime(p.getId());
|
long lastPoll = rendezvousPoller.getLastPollTime(p.getId());
|
||||||
items.add(new PendingContactItem(p, pair.getSecond(),
|
items.add(new PendingContactItem(p, state, lastPoll));
|
||||||
lastPoll));
|
online = online || state != OFFLINE;
|
||||||
}
|
}
|
||||||
pendingContacts.postValue(items);
|
pendingContacts.postValue(items);
|
||||||
|
hasInternetConnection.postValue(online);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -115,4 +121,8 @@ public class PendingContactListViewModel extends AndroidViewModel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LiveData<Boolean> getHasInternetConnection() {
|
||||||
|
return hasInternetConnection;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ class PendingContactViewHolder extends ViewHolder {
|
|||||||
.getColor(status.getContext(), R.color.briar_yellow);
|
.getColor(status.getContext(), R.color.briar_yellow);
|
||||||
status.setText(R.string.waiting_for_contact_to_come_online);
|
status.setText(R.string.waiting_for_contact_to_come_online);
|
||||||
break;
|
break;
|
||||||
|
case OFFLINE:
|
||||||
|
color = ContextCompat
|
||||||
|
.getColor(status.getContext(), R.color.briar_yellow);
|
||||||
|
status.setText("");
|
||||||
|
break;
|
||||||
case CONNECTING:
|
case CONNECTING:
|
||||||
status.setText(R.string.connecting);
|
status.setText(R.string.connecting);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.arch.lifecycle.ViewModelProvider;
|
|||||||
import android.arch.lifecycle.ViewModelProviders;
|
import android.arch.lifecycle.ViewModelProviders;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
@@ -30,11 +29,11 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FeatureFlags;
|
||||||
import org.briarproject.bramble.api.Pair;
|
import org.briarproject.bramble.api.Pair;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
@@ -46,13 +45,14 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
|
||||||
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
|
||||||
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentRetriever;
|
||||||
import org.briarproject.briar.android.blog.BlogActivity;
|
import org.briarproject.briar.android.blog.BlogActivity;
|
||||||
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
||||||
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
||||||
@@ -63,10 +63,9 @@ import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
|||||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||||
import org.briarproject.briar.android.view.ImagePreview;
|
import org.briarproject.briar.android.view.ImagePreview;
|
||||||
import org.briarproject.briar.android.view.TextAttachmentController;
|
import org.briarproject.briar.android.view.TextAttachmentController;
|
||||||
import org.briarproject.briar.android.view.TextAttachmentController.AttachImageListener;
|
import org.briarproject.briar.android.view.TextAttachmentController.AttachmentListener;
|
||||||
import org.briarproject.briar.android.view.TextInputView;
|
import org.briarproject.briar.android.view.TextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
|
||||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||||
@@ -81,7 +80,6 @@ import org.briarproject.briar.api.introduction.IntroductionManager;
|
|||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||||
|
|
||||||
@@ -92,7 +90,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -118,7 +115,6 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||||
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_IMAGE_ATTACHMENTS;
|
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
||||||
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
|
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
|
||||||
@@ -128,6 +124,7 @@ import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
|||||||
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
||||||
@@ -135,8 +132,8 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
|
|||||||
@MethodsNotNullByDefault
|
@MethodsNotNullByDefault
|
||||||
@ParametersNotNullByDefault
|
@ParametersNotNullByDefault
|
||||||
public class ConversationActivity extends BriarActivity
|
public class ConversationActivity extends BriarActivity
|
||||||
implements EventListener, ConversationListener, SendListener,
|
implements EventListener, ConversationListener, TextCache,
|
||||||
TextCache, AttachmentCache, AttachImageListener {
|
AttachmentCache, AttachmentListener {
|
||||||
|
|
||||||
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
public static final String CONTACT_ID = "briar.CONTACT_ID";
|
||||||
|
|
||||||
@@ -151,10 +148,9 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Inject
|
@Inject
|
||||||
ConnectionRegistry connectionRegistry;
|
ConnectionRegistry connectionRegistry;
|
||||||
@Inject
|
@Inject
|
||||||
@CryptoExecutor
|
|
||||||
Executor cryptoExecutor;
|
|
||||||
@Inject
|
|
||||||
ViewModelProvider.Factory viewModelFactory;
|
ViewModelProvider.Factory viewModelFactory;
|
||||||
|
@Inject
|
||||||
|
FeatureFlags featureFlags;
|
||||||
|
|
||||||
// Fields that are accessed from background threads must be volatile
|
// Fields that are accessed from background threads must be volatile
|
||||||
@Inject
|
@Inject
|
||||||
@@ -166,10 +162,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Inject
|
@Inject
|
||||||
volatile EventBus eventBus;
|
volatile EventBus eventBus;
|
||||||
@Inject
|
@Inject
|
||||||
volatile SettingsManager settingsManager;
|
|
||||||
@Inject
|
|
||||||
volatile PrivateMessageFactory privateMessageFactory;
|
|
||||||
@Inject
|
|
||||||
volatile IntroductionManager introductionManager;
|
volatile IntroductionManager introductionManager;
|
||||||
@Inject
|
@Inject
|
||||||
volatile ForumSharingManager forumSharingManager;
|
volatile ForumSharingManager forumSharingManager;
|
||||||
@@ -184,7 +176,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
loadMessages();
|
loadMessages();
|
||||||
};
|
};
|
||||||
|
|
||||||
private AttachmentController attachmentController;
|
private AttachmentRetriever attachmentRetriever;
|
||||||
private ConversationViewModel viewModel;
|
private ConversationViewModel viewModel;
|
||||||
private ConversationVisitor visitor;
|
private ConversationVisitor visitor;
|
||||||
private ConversationAdapter adapter;
|
private ConversationAdapter adapter;
|
||||||
@@ -219,7 +211,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
viewModel = ViewModelProviders.of(this, viewModelFactory)
|
||||||
.get(ConversationViewModel.class);
|
.get(ConversationViewModel.class);
|
||||||
attachmentController = viewModel.getAttachmentController();
|
attachmentRetriever = viewModel.getAttachmentRetriever();
|
||||||
|
|
||||||
setContentView(R.layout.activity_conversation);
|
setContentView(R.layout.activity_conversation);
|
||||||
|
|
||||||
@@ -242,7 +234,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
requireNonNull(deleted);
|
requireNonNull(deleted);
|
||||||
if (deleted) finish();
|
if (deleted) finish();
|
||||||
});
|
});
|
||||||
viewModel.getAddedPrivateMessage().observe(this,
|
viewModel.getAddedPrivateMessage().observeEvent(this,
|
||||||
this::onAddedPrivateMessage);
|
this::onAddedPrivateMessage);
|
||||||
|
|
||||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||||
@@ -261,13 +253,13 @@ public class ConversationActivity extends BriarActivity
|
|||||||
list.getRecyclerView().addOnScrollListener(scrollListener);
|
list.getRecyclerView().addOnScrollListener(scrollListener);
|
||||||
|
|
||||||
textInputView = findViewById(R.id.text_input_container);
|
textInputView = findViewById(R.id.text_input_container);
|
||||||
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
|
if (featureFlags.shouldEnableImageAttachments()) {
|
||||||
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
ImagePreview imagePreview = findViewById(R.id.imagePreview);
|
||||||
sendController = new TextAttachmentController(textInputView,
|
sendController = new TextAttachmentController(textInputView,
|
||||||
imagePreview, this, this);
|
imagePreview, this, viewModel);
|
||||||
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
|
||||||
if (hasSupport != null && hasSupport) {
|
if (hasSupport != null && hasSupport) {
|
||||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
// TODO: remove cast when removing feature flag
|
||||||
((TextAttachmentController) sendController)
|
((TextAttachmentController) sendController)
|
||||||
.setImagesSupported();
|
.setImagesSupported();
|
||||||
}
|
}
|
||||||
@@ -302,7 +294,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
Snackbar.LENGTH_SHORT)
|
Snackbar.LENGTH_SHORT)
|
||||||
.show();
|
.show();
|
||||||
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
|
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
|
||||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
// TODO: remove cast when removing feature flag
|
||||||
((TextAttachmentController) sendController).onImageReceived(data);
|
((TextAttachmentController) sendController).onImageReceived(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,19 +443,21 @@ public class ConversationActivity extends BriarActivity
|
|||||||
if (text == null) {
|
if (text == null) {
|
||||||
LOG.info("Eagerly loading text for latest message");
|
LOG.info("Eagerly loading text for latest message");
|
||||||
text = messagingManager.getMessageText(id);
|
text = messagingManager.getMessageText(id);
|
||||||
textCache.put(id, text);
|
textCache.put(id, requireNonNull(text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the message has a single image, load its size - for multiple
|
// If the message has a single image, load its size - for multiple
|
||||||
// images we use a grid so the size is fixed
|
// images we use a grid so the size is fixed
|
||||||
if (h.getAttachmentHeaders().size() == 1) {
|
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||||
List<AttachmentItem> items = attachmentController.get(id);
|
if (headers.size() == 1) {
|
||||||
if (items == null) {
|
MessageId attachmentId = headers.get(0).getMessageId();
|
||||||
|
AttachmentItem item = attachmentRetriever.cacheGet(attachmentId);
|
||||||
|
if (item == null) {
|
||||||
LOG.info("Eagerly loading image size for latest message");
|
LOG.info("Eagerly loading image size for latest message");
|
||||||
items = attachmentController.getAttachmentItems(
|
item = attachmentRetriever.getAttachmentItems(
|
||||||
attachmentController.getMessageAttachments(
|
attachmentRetriever.getMessageAttachments(headers))
|
||||||
h.getAttachmentHeaders()));
|
.get(0);
|
||||||
attachmentController.put(id, items);
|
attachmentRetriever.cachePut(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,8 +469,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
adapter.incrementRevision();
|
adapter.incrementRevision();
|
||||||
textInputView.setReady(true);
|
textInputView.setReady(true);
|
||||||
// start observing onboarding after enabling
|
// start observing onboarding after enabling
|
||||||
viewModel.showImageOnboarding().observeEvent(this,
|
if (featureFlags.shouldEnableImageAttachments()) {
|
||||||
this::showImageOnboarding);
|
viewModel.showImageOnboarding().observeEvent(this,
|
||||||
|
this::showImageOnboarding);
|
||||||
|
}
|
||||||
List<ConversationItem> items = createItems(headers);
|
List<ConversationItem> items = createItems(headers);
|
||||||
adapter.addAll(items);
|
adapter.addAll(items);
|
||||||
list.showData();
|
list.showData();
|
||||||
@@ -512,7 +508,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
long start = now();
|
long start = now();
|
||||||
String text = messagingManager.getMessageText(m);
|
String text = messagingManager.getMessageText(m);
|
||||||
logDuration(LOG, "Loading text", start);
|
logDuration(LOG, "Loading text", start);
|
||||||
displayMessageText(m, text);
|
displayMessageText(m, requireNonNull(text));
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
@@ -545,10 +541,12 @@ public class ConversationActivity extends BriarActivity
|
|||||||
runOnDbThread(() -> {
|
runOnDbThread(() -> {
|
||||||
try {
|
try {
|
||||||
List<Pair<AttachmentHeader, Attachment>> attachments =
|
List<Pair<AttachmentHeader, Attachment>> attachments =
|
||||||
attachmentController.getMessageAttachments(headers);
|
attachmentRetriever.getMessageAttachments(headers);
|
||||||
// TODO move getting the items off to IoExecutor, if size == 1
|
// TODO move getting the items off to IoExecutor, if size == 1
|
||||||
List<AttachmentItem> items =
|
List<AttachmentItem> items =
|
||||||
attachmentController.getAttachmentItems(attachments);
|
attachmentRetriever.getAttachmentItems(attachments);
|
||||||
|
if (items.size() == 1)
|
||||||
|
attachmentRetriever.cachePut(items.get(0));
|
||||||
displayMessageAttachments(messageId, items);
|
displayMessageAttachments(messageId, items);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
@@ -559,7 +557,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void displayMessageAttachments(MessageId m,
|
private void displayMessageAttachments(MessageId m,
|
||||||
List<AttachmentItem> items) {
|
List<AttachmentItem> items) {
|
||||||
runOnUiThreadUnlessDestroyed(() -> {
|
runOnUiThreadUnlessDestroyed(() -> {
|
||||||
attachmentController.put(m, items);
|
|
||||||
Pair<Integer, ConversationMessageItem> pair =
|
Pair<Integer, ConversationMessageItem> pair =
|
||||||
adapter.getMessageItem(m);
|
adapter.getMessageItem(m);
|
||||||
if (pair != null) {
|
if (pair != null) {
|
||||||
@@ -658,12 +655,21 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onTooManyAttachments() {
|
||||||
if (isNullOrEmpty(text) && imageUris.isEmpty())
|
String format = getResources().getString(
|
||||||
|
R.string.messaging_too_many_attachments_toast);
|
||||||
|
String warning = String.format(format, MAX_ATTACHMENTS_PER_MESSAGE);
|
||||||
|
Toast.makeText(this, warning, LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> attachmentHeaders) {
|
||||||
|
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
|
||||||
viewModel.sendMessage(text, imageUris, timestamp);
|
viewModel.sendMessage(text, attachmentHeaders, timestamp);
|
||||||
textInputView.clearText();
|
textInputView.clearText();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +682,6 @@ public class ConversationActivity extends BriarActivity
|
|||||||
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
|
||||||
if (h == null) return;
|
if (h == null) return;
|
||||||
addConversationItem(h.accept(visitor));
|
addConversationItem(h.accept(visitor));
|
||||||
viewModel.onAddedPrivateMessageSeen();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void askToRemoveContact() {
|
private void askToRemoveContact() {
|
||||||
@@ -726,7 +731,7 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showImageOnboarding() {
|
private void showImageOnboarding() {
|
||||||
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
|
// TODO: remove cast when removing feature flag
|
||||||
((TextAttachmentController) sendController)
|
((TextAttachmentController) sendController)
|
||||||
.showImageOnboarding(this, () ->
|
.showImageOnboarding(this, () ->
|
||||||
viewModel.onImageOnboardingSeen());
|
viewModel.onImageOnboardingSeen());
|
||||||
@@ -903,12 +908,17 @@ public class ConversationActivity extends BriarActivity
|
|||||||
@Override
|
@Override
|
||||||
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
public List<AttachmentItem> getAttachmentItems(MessageId m,
|
||||||
List<AttachmentHeader> headers) {
|
List<AttachmentHeader> headers) {
|
||||||
List<AttachmentItem> attachments = attachmentController.get(m);
|
List<AttachmentItem> items = new ArrayList<>(headers.size());
|
||||||
if (attachments == null) {
|
for (AttachmentHeader header : headers) {
|
||||||
loadMessageAttachments(m, headers);
|
AttachmentItem item =
|
||||||
return emptyList();
|
attachmentRetriever.cacheGet(header.getMessageId());
|
||||||
|
if (item == null) {
|
||||||
|
loadMessageAttachments(m, headers);
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
items.add(item);
|
||||||
}
|
}
|
||||||
return attachments;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.support.annotation.UiThread;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.conversation;
|
|||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
|
||||||
import static android.support.v4.content.ContextCompat.getColor;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
|||||||
@@ -4,42 +4,41 @@ import android.app.Application;
|
|||||||
import android.arch.lifecycle.AndroidViewModel;
|
import android.arch.lifecycle.AndroidViewModel;
|
||||||
import android.arch.lifecycle.LiveData;
|
import android.arch.lifecycle.LiveData;
|
||||||
import android.arch.lifecycle.MutableLiveData;
|
import android.arch.lifecycle.MutableLiveData;
|
||||||
|
import android.arch.lifecycle.Observer;
|
||||||
import android.arch.lifecycle.Transformations;
|
import android.arch.lifecycle.Transformations;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.FormatException;
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.api.Pair;
|
|
||||||
import org.briarproject.bramble.api.contact.Contact;
|
import org.briarproject.bramble.api.contact.Contact;
|
||||||
import org.briarproject.bramble.api.contact.ContactId;
|
import org.briarproject.bramble.api.contact.ContactId;
|
||||||
import org.briarproject.bramble.api.contact.ContactManager;
|
import org.briarproject.bramble.api.contact.ContactManager;
|
||||||
import org.briarproject.bramble.api.crypto.CryptoExecutor;
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.db.NoSuchContactException;
|
import org.briarproject.bramble.api.db.NoSuchContactException;
|
||||||
import org.briarproject.bramble.api.db.TransactionManager;
|
import org.briarproject.bramble.api.db.TransactionManager;
|
||||||
import org.briarproject.bramble.api.identity.AuthorId;
|
import org.briarproject.bramble.api.identity.AuthorId;
|
||||||
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.settings.Settings;
|
import org.briarproject.bramble.api.settings.Settings;
|
||||||
import org.briarproject.bramble.api.settings.SettingsManager;
|
import org.briarproject.bramble.api.settings.SettingsManager;
|
||||||
import org.briarproject.bramble.api.sync.GroupId;
|
import org.briarproject.bramble.api.sync.GroupId;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentCreator;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentManager;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentResult;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentRetriever;
|
||||||
import org.briarproject.briar.android.util.UiUtils;
|
import org.briarproject.briar.android.util.UiUtils;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
@@ -50,19 +49,20 @@ import javax.inject.Inject;
|
|||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static java.util.logging.Logger.getLogger;
|
import static java.util.logging.Logger.getLogger;
|
||||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.conversation.AttachmentDimensions.getAttachmentDimensions;
|
import static org.briarproject.briar.android.attachment.AttachmentDimensions.getAttachmentDimensions;
|
||||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class ConversationViewModel extends AndroidViewModel {
|
public class ConversationViewModel extends AndroidViewModel
|
||||||
|
implements AttachmentManager {
|
||||||
|
|
||||||
private static Logger LOG =
|
private static Logger LOG =
|
||||||
getLogger(ConversationViewModel.class.getName());
|
getLogger(ConversationViewModel.class.getName());
|
||||||
|
|
||||||
private static final String SHOW_ONBOARDING_IMAGE =
|
private static final String SHOW_ONBOARDING_IMAGE =
|
||||||
"showOnboardingImage";
|
"showOnboardingImage";
|
||||||
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||||
@@ -70,14 +70,13 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private final Executor dbExecutor;
|
private final Executor dbExecutor;
|
||||||
@CryptoExecutor
|
|
||||||
private final Executor cryptoExecutor;
|
|
||||||
private final TransactionManager db;
|
private final TransactionManager db;
|
||||||
private final MessagingManager messagingManager;
|
private final MessagingManager messagingManager;
|
||||||
private final ContactManager contactManager;
|
private final ContactManager contactManager;
|
||||||
private final SettingsManager settingsManager;
|
private final SettingsManager settingsManager;
|
||||||
private final PrivateMessageFactory privateMessageFactory;
|
private final PrivateMessageFactory privateMessageFactory;
|
||||||
private final AttachmentController attachmentController;
|
private final AttachmentRetriever attachmentRetriever;
|
||||||
|
private final AttachmentCreator attachmentCreator;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ContactId contactId = null;
|
private ContactId contactId = null;
|
||||||
@@ -86,6 +85,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
Transformations.map(contact, c -> c.getAuthor().getId());
|
Transformations.map(contact, c -> c.getAuthor().getId());
|
||||||
private final LiveData<String> contactName =
|
private final LiveData<String> contactName =
|
||||||
Transformations.map(contact, UiUtils::getContactDisplayName);
|
Transformations.map(contact, UiUtils::getContactDisplayName);
|
||||||
|
private final LiveData<GroupId> messagingGroupId;
|
||||||
private final MutableLiveData<Boolean> imageSupport =
|
private final MutableLiveData<Boolean> imageSupport =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveEvent<Boolean> showImageOnboarding =
|
private final MutableLiveEvent<Boolean> showImageOnboarding =
|
||||||
@@ -96,31 +96,38 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveData<Boolean> contactDeleted =
|
private final MutableLiveData<Boolean> contactDeleted =
|
||||||
new MutableLiveData<>();
|
new MutableLiveData<>();
|
||||||
private final MutableLiveData<GroupId> messagingGroupId =
|
private final MutableLiveEvent<PrivateMessageHeader> addedHeader =
|
||||||
new MutableLiveData<>();
|
new MutableLiveEvent<>();
|
||||||
private final MutableLiveData<PrivateMessageHeader> addedHeader =
|
|
||||||
new MutableLiveData<>();
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ConversationViewModel(Application application,
|
ConversationViewModel(Application application,
|
||||||
@DatabaseExecutor Executor dbExecutor,
|
@DatabaseExecutor Executor dbExecutor,
|
||||||
@CryptoExecutor Executor cryptoExecutor, TransactionManager db,
|
@IoExecutor Executor ioExecutor, TransactionManager db,
|
||||||
MessagingManager messagingManager, ContactManager contactManager,
|
MessagingManager messagingManager, ContactManager contactManager,
|
||||||
SettingsManager settingsManager,
|
SettingsManager settingsManager,
|
||||||
PrivateMessageFactory privateMessageFactory) {
|
PrivateMessageFactory privateMessageFactory) {
|
||||||
super(application);
|
super(application);
|
||||||
this.dbExecutor = dbExecutor;
|
this.dbExecutor = dbExecutor;
|
||||||
this.cryptoExecutor = cryptoExecutor;
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.messagingManager = messagingManager;
|
this.messagingManager = messagingManager;
|
||||||
this.contactManager = contactManager;
|
this.contactManager = contactManager;
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.privateMessageFactory = privateMessageFactory;
|
this.privateMessageFactory = privateMessageFactory;
|
||||||
this.attachmentController = new AttachmentController(messagingManager,
|
this.attachmentRetriever = new AttachmentRetriever(messagingManager,
|
||||||
getAttachmentDimensions(application.getResources()));
|
getAttachmentDimensions(application.getResources()));
|
||||||
|
this.attachmentCreator = new AttachmentCreator(getApplication(),
|
||||||
|
ioExecutor, messagingManager, attachmentRetriever);
|
||||||
|
messagingGroupId = Transformations
|
||||||
|
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||||
contactDeleted.setValue(false);
|
contactDeleted.setValue(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCleared() {
|
||||||
|
super.onCleared();
|
||||||
|
attachmentCreator.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setting the {@link ContactId} automatically triggers loading of other
|
* Setting the {@link ContactId} automatically triggers loading of other
|
||||||
* data.
|
* data.
|
||||||
@@ -176,25 +183,53 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMessage(@Nullable String text, List<Uri> uris, long timestamp) {
|
@UiThread
|
||||||
if (messagingGroupId.getValue() == null) loadGroupId();
|
void sendMessage(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers, long timestamp) {
|
||||||
|
// messagingGroupId is loaded with the contact
|
||||||
observeForeverOnce(messagingGroupId, groupId -> {
|
observeForeverOnce(messagingGroupId, groupId -> {
|
||||||
if (groupId == null) return;
|
requireNonNull(groupId);
|
||||||
// calls through to creating and storing the message
|
observeForeverOnce(imageSupport, hasImageSupport -> {
|
||||||
storeAttachments(groupId, text, uris, timestamp);
|
requireNonNull(hasImageSupport);
|
||||||
|
createMessage(groupId, text, headers, timestamp,
|
||||||
|
hasImageSupport);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadGroupId() {
|
@Override
|
||||||
if (contactId == null) throw new IllegalStateException();
|
@UiThread
|
||||||
dbExecutor.execute(() -> {
|
public LiveData<AttachmentResult> storeAttachments(Collection<Uri> uris,
|
||||||
try {
|
boolean restart) {
|
||||||
messagingGroupId.postValue(
|
MutableLiveData<AttachmentResult> delegate = new MutableLiveData<>();
|
||||||
messagingManager.getConversationId(contactId));
|
// messagingGroupId is loaded with the contact
|
||||||
} catch (DbException e) {
|
observeForeverOnce(messagingGroupId, groupId -> {
|
||||||
logException(LOG, WARNING, e);
|
requireNonNull(groupId);
|
||||||
}
|
LiveData<AttachmentResult> result;
|
||||||
|
if (restart) result = attachmentCreator.getLiveAttachments();
|
||||||
|
else result = attachmentCreator.storeAttachments(groupId, uris);
|
||||||
|
result.observeForever(new Observer<AttachmentResult>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable AttachmentResult value) {
|
||||||
|
requireNonNull(value);
|
||||||
|
if (value.isFinished()) result.removeObserver(this);
|
||||||
|
delegate.setValue(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public List<AttachmentHeader> getAttachmentHeadersForSending() {
|
||||||
|
return attachmentCreator.getAttachmentHeadersForSending();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@UiThread
|
||||||
|
public void cancel() {
|
||||||
|
attachmentCreator.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
@@ -252,75 +287,26 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeAttachments(GroupId groupId, @Nullable String text,
|
private void createMessage(GroupId groupId, @Nullable String text,
|
||||||
List<Uri> uris, long timestamp) {
|
List<AttachmentHeader> headers, long timestamp,
|
||||||
dbExecutor.execute(() -> {
|
boolean hasImageSupport) {
|
||||||
long start = now();
|
|
||||||
List<AttachmentHeader> attachments = new ArrayList<>();
|
|
||||||
List<AttachmentItem> items = new ArrayList<>();
|
|
||||||
boolean needsSize = uris.size() == 1;
|
|
||||||
for (Uri uri : uris) {
|
|
||||||
Pair<AttachmentHeader, AttachmentItem> pair =
|
|
||||||
createAttachmentHeader(groupId, uri, timestamp,
|
|
||||||
needsSize);
|
|
||||||
if (pair == null) continue;
|
|
||||||
attachments.add(pair.getFirst());
|
|
||||||
items.add(pair.getSecond());
|
|
||||||
}
|
|
||||||
logDuration(LOG, "Storing attachments", start);
|
|
||||||
createMessage(groupId, text, attachments, items, timestamp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@DatabaseExecutor
|
|
||||||
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
|
|
||||||
GroupId groupId, Uri uri, long timestamp, boolean needsSize) {
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
try {
|
||||||
ContentResolver contentResolver =
|
PrivateMessage pm;
|
||||||
getApplication().getContentResolver();
|
if (hasImageSupport) {
|
||||||
is = contentResolver.openInputStream(uri);
|
pm = privateMessageFactory.createPrivateMessage(groupId,
|
||||||
if (is == null) throw new IOException();
|
timestamp, text, headers);
|
||||||
String contentType = contentResolver.getType(uri);
|
} else {
|
||||||
if (contentType == null) throw new IOException("null content type");
|
pm = privateMessageFactory.createLegacyPrivateMessage(
|
||||||
AttachmentHeader h = messagingManager
|
groupId, timestamp, requireNonNull(text));
|
||||||
.addLocalAttachment(groupId, timestamp, contentType, is);
|
}
|
||||||
is.close();
|
storeMessage(pm);
|
||||||
// re-open stream to get AttachmentItem
|
} catch (FormatException e) {
|
||||||
is = contentResolver.openInputStream(uri);
|
throw new AssertionError(e);
|
||||||
if (is == null) throw new IOException();
|
|
||||||
AttachmentItem item = attachmentController
|
|
||||||
.getAttachmentItem(h, new Attachment(is), needsSize);
|
|
||||||
return new Pair<>(h, item);
|
|
||||||
} catch (DbException | IOException e) {
|
|
||||||
logException(LOG, WARNING, e);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
if (is != null) tryToClose(is, LOG, WARNING);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createMessage(GroupId groupId, @Nullable String text,
|
private void storeMessage(PrivateMessage m) {
|
||||||
List<AttachmentHeader> attachments, List<AttachmentItem> aItems,
|
attachmentCreator.onAttachmentsSent();
|
||||||
long timestamp) {
|
|
||||||
cryptoExecutor.execute(() -> {
|
|
||||||
try {
|
|
||||||
// TODO remove when text can be null in the backend
|
|
||||||
String msgText = text == null ? "null" : text;
|
|
||||||
PrivateMessage pm = privateMessageFactory
|
|
||||||
.createPrivateMessage(groupId, timestamp, msgText,
|
|
||||||
attachments);
|
|
||||||
attachmentController.put(pm.getMessage().getId(), aItems);
|
|
||||||
storeMessage(pm, msgText, attachments);
|
|
||||||
} catch (FormatException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void storeMessage(PrivateMessage m, @Nullable String text,
|
|
||||||
List<AttachmentHeader> attachments) {
|
|
||||||
dbExecutor.execute(() -> {
|
dbExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
long start = now();
|
long start = now();
|
||||||
@@ -330,22 +316,17 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
PrivateMessageHeader h = new PrivateMessageHeader(
|
PrivateMessageHeader h = new PrivateMessageHeader(
|
||||||
message.getId(), message.getGroupId(),
|
message.getId(), message.getGroupId(),
|
||||||
message.getTimestamp(), true, true, false, false,
|
message.getTimestamp(), true, true, false, false,
|
||||||
text != null, attachments);
|
m.hasText(), m.getAttachmentHeaders());
|
||||||
// TODO add text to cache when available here
|
// TODO add text to cache when available here
|
||||||
addedHeader.postValue(h);
|
addedHeader.postEvent(h);
|
||||||
} catch (DbException e) {
|
} catch (DbException e) {
|
||||||
logException(LOG, WARNING, e);
|
logException(LOG, WARNING, e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@UiThread
|
AttachmentRetriever getAttachmentRetriever() {
|
||||||
void onAddedPrivateMessageSeen() {
|
return attachmentRetriever;
|
||||||
addedHeader.setValue(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
AttachmentController getAttachmentController() {
|
|
||||||
return attachmentController;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<Contact> getContact() {
|
LiveData<Contact> getContact() {
|
||||||
@@ -380,7 +361,7 @@ public class ConversationViewModel extends AndroidViewModel {
|
|||||||
return contactDeleted;
|
return contactDeleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
LiveData<PrivateMessageHeader> getAddedPrivateMessage() {
|
LiveEvent<PrivateMessageHeader> getAddedPrivateMessage() {
|
||||||
return addedHeader;
|
return addedHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.support.annotation.UiThread;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.api.blog.BlogInvitationRequest;
|
import org.briarproject.briar.api.blog.BlogInvitationRequest;
|
||||||
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
||||||
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
|
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
|||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
import org.briarproject.briar.android.activity.BriarActivity;
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||||
import org.briarproject.briar.android.view.PullDownLayout;
|
import org.briarproject.briar.android.view.PullDownLayout;
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import android.view.WindowManager;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.github.chrisbanes.photoview.PhotoView;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.activity.BaseActivity;
|
import org.briarproject.briar.android.activity.BaseActivity;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
|
|
||||||
import javax.annotation.ParametersAreNonnullByDefault;
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import com.bumptech.glide.load.Transformation;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
|
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
|
||||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
import org.briarproject.briar.android.conversation.glide.Radii;
|
import org.briarproject.briar.android.conversation.glide.Radii;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.db.DbException;
|
|||||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
|
|||||||
import org.briarproject.bramble.api.db.DbException;
|
import org.briarproject.bramble.api.db.DbException;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.briarproject.briar.android.conversation.glide;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import com.bumptech.glide.module.AppGlideModule;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import com.bumptech.glide.signature.ObjectKey;
|
|||||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.android.BriarApplication;
|
import org.briarproject.briar.android.BriarApplication;
|
||||||
import org.briarproject.briar.android.conversation.AttachmentItem;
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.introduction;
|
package org.briarproject.briar.android.introduction;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
@@ -26,6 +25,7 @@ import org.briarproject.briar.android.view.TextInputView;
|
|||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -193,7 +193,8 @@ public class IntroductionMessageFragment extends BaseFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
// disable button to prevent accidental double invitations
|
// disable button to prevent accidental double invitations
|
||||||
ui.message.setReady(false);
|
ui.message.setReady(false);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package org.briarproject.briar.android.reporting;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class CrashFragment extends Fragment {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
View v = inflater
|
||||||
|
.inflate(R.layout.fragment_crash, container, false);
|
||||||
|
|
||||||
|
v.findViewById(R.id.acceptButton).setOnClickListener(view ->
|
||||||
|
getDevReportActivity().displayFragment(true));
|
||||||
|
v.findViewById(R.id.declineButton).setOnClickListener(view ->
|
||||||
|
getDevReportActivity().closeReport());
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DevReportActivity getDevReportActivity() {
|
||||||
|
return (DevReportActivity) requireNonNull(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,87 +1,38 @@
|
|||||||
package org.briarproject.briar.android.reporting;
|
package org.briarproject.briar.android.reporting;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v7.app.AppCompatDelegate;
|
import android.support.v7.app.AppCompatDelegate;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.acra.ReportField;
|
|
||||||
import org.acra.collector.CrashReportData;
|
|
||||||
import org.acra.dialog.BaseCrashReportDialog;
|
import org.acra.dialog.BaseCrashReportDialog;
|
||||||
import org.acra.file.CrashReportPersister;
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
import org.acra.model.Element;
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.Localizer;
|
import org.briarproject.briar.android.Localizer;
|
||||||
|
import org.briarproject.briar.android.logout.HideUiActivity;
|
||||||
import org.briarproject.briar.android.util.UserFeedback;
|
import org.briarproject.briar.android.util.UserFeedback;
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
|
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.view.View.GONE;
|
|
||||||
import static android.view.View.INVISIBLE;
|
|
||||||
import static android.view.View.VISIBLE;
|
|
||||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||||
import static android.view.inputmethod.InputMethodManager.SHOW_FORCED;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static java.util.logging.Level.WARNING;
|
|
||||||
import static org.acra.ACRAConstants.EXTRA_REPORT_FILE;
|
import static org.acra.ACRAConstants.EXTRA_REPORT_FILE;
|
||||||
import static org.acra.ReportField.ANDROID_VERSION;
|
|
||||||
import static org.acra.ReportField.APP_VERSION_CODE;
|
|
||||||
import static org.acra.ReportField.APP_VERSION_NAME;
|
|
||||||
import static org.acra.ReportField.PACKAGE_NAME;
|
|
||||||
import static org.acra.ReportField.REPORT_ID;
|
|
||||||
import static org.acra.ReportField.STACK_TRACE;
|
|
||||||
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||||
|
|
||||||
public class DevReportActivity extends BaseCrashReportDialog
|
@MethodsNotNullByDefault
|
||||||
implements CompoundButton.OnCheckedChangeListener {
|
@ParametersNotNullByDefault
|
||||||
|
public class DevReportActivity extends BaseCrashReportDialog {
|
||||||
private static final Logger LOG =
|
|
||||||
Logger.getLogger(DevReportActivity.class.getName());
|
|
||||||
|
|
||||||
private static final String STATE_REVIEWING = "reviewing";
|
|
||||||
private static final Set<ReportField> requiredFields = new HashSet<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
requiredFields.add(REPORT_ID);
|
|
||||||
requiredFields.add(APP_VERSION_CODE);
|
|
||||||
requiredFields.add(APP_VERSION_NAME);
|
|
||||||
requiredFields.add(PACKAGE_NAME);
|
|
||||||
requiredFields.add(ANDROID_VERSION);
|
|
||||||
requiredFields.add(STACK_TRACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AppCompatDelegate delegate;
|
private AppCompatDelegate delegate;
|
||||||
private Set<ReportField> excludedFields = new HashSet<>();
|
|
||||||
private EditText userCommentView = null;
|
|
||||||
private EditText userEmailView = null;
|
|
||||||
private CheckBox includeDebugReport = null;
|
|
||||||
private Button chevron = null;
|
|
||||||
private LinearLayout report = null;
|
|
||||||
private View progress = null;
|
|
||||||
private MenuItem sendReport = null;
|
|
||||||
private boolean reviewing = false;
|
|
||||||
|
|
||||||
private AppCompatDelegate getDelegate() {
|
private AppCompatDelegate getDelegate() {
|
||||||
if (delegate == null) {
|
if (delegate == null) {
|
||||||
@@ -110,68 +61,21 @@ public class DevReportActivity extends BaseCrashReportDialog
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Bundle state) {
|
public void init(@Nullable Bundle state) {
|
||||||
super.init(state);
|
super.init(state);
|
||||||
|
|
||||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||||
|
|
||||||
getDelegate().setContentView(R.layout.activity_dev_report);
|
getDelegate().setContentView(R.layout.activity_dev_report);
|
||||||
|
|
||||||
Toolbar tb = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
getDelegate().setSupportActionBar(tb);
|
getDelegate().setSupportActionBar(toolbar);
|
||||||
|
|
||||||
View requestReport = findViewById(R.id.request_report);
|
String title = getString(isFeedback() ? R.string.feedback_title :
|
||||||
View reportForm = findViewById(R.id.report_form);
|
R.string.crash_report_title);
|
||||||
userCommentView = findViewById(R.id.user_comment);
|
requireNonNull(getDelegate().getSupportActionBar()).setTitle(title);
|
||||||
userEmailView = findViewById(R.id.user_email);
|
|
||||||
includeDebugReport = findViewById(R.id.include_debug_report);
|
|
||||||
chevron = findViewById(R.id.chevron);
|
|
||||||
report = findViewById(R.id.report_content);
|
|
||||||
progress = findViewById(R.id.progress_wheel);
|
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
if (state == null) displayFragment(isFeedback());
|
||||||
getDelegate().getSupportActionBar().setTitle(
|
|
||||||
isFeedback() ? R.string.feedback_title :
|
|
||||||
R.string.crash_report_title);
|
|
||||||
userCommentView.setHint(isFeedback() ? R.string.enter_feedback :
|
|
||||||
R.string.describe_crash);
|
|
||||||
|
|
||||||
if (isFeedback()) {
|
|
||||||
includeDebugReport
|
|
||||||
.setText(getString(R.string.include_debug_report_feedback));
|
|
||||||
reportForm.setVisibility(VISIBLE);
|
|
||||||
requestReport.setVisibility(INVISIBLE);
|
|
||||||
} else {
|
|
||||||
includeDebugReport.setChecked(true);
|
|
||||||
reportForm.setVisibility(INVISIBLE);
|
|
||||||
requestReport.setVisibility(VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
findViewById(R.id.acceptButton).setOnClickListener(v -> {
|
|
||||||
reviewing = true;
|
|
||||||
reportForm.setVisibility(VISIBLE);
|
|
||||||
requestReport.setVisibility(INVISIBLE);
|
|
||||||
((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
|
|
||||||
.showSoftInput(userCommentView, SHOW_FORCED);
|
|
||||||
});
|
|
||||||
findViewById(R.id.declineButton).setOnClickListener(v -> closeReport());
|
|
||||||
chevron.setOnClickListener(v -> {
|
|
||||||
boolean show =
|
|
||||||
chevron.getText().equals(getString(R.string.show));
|
|
||||||
if (show) {
|
|
||||||
chevron.setText(R.string.hide);
|
|
||||||
refresh();
|
|
||||||
} else {
|
|
||||||
chevron.setText(R.string.show);
|
|
||||||
report.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (state != null)
|
|
||||||
reviewing = state.getBoolean(STATE_REVIEWING, isFeedback());
|
|
||||||
|
|
||||||
if (!isFeedback() && !reviewing)
|
|
||||||
requestReport.setVisibility(VISIBLE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -181,47 +85,17 @@ public class DevReportActivity extends BaseCrashReportDialog
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPostCreate(Bundle state) {
|
public void onPostCreate(@Nullable Bundle state) {
|
||||||
super.onPostCreate(state);
|
super.onPostCreate(state);
|
||||||
getDelegate().onPostCreate(state);
|
getDelegate().onPostCreate(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
if (chevron.isSelected()) refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostResume() {
|
protected void onPostResume() {
|
||||||
super.onPostResume();
|
super.onPostResume();
|
||||||
getDelegate().onPostResume();
|
getDelegate().onPostResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
// Inflate the menu items for use in the action bar
|
|
||||||
MenuInflater inflater = getDelegate().getMenuInflater();
|
|
||||||
inflater.inflate(R.menu.dev_report_actions, menu);
|
|
||||||
sendReport = menu.findItem(R.id.action_send_report);
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
// Handle presses on the action bar items
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case android.R.id.home:
|
|
||||||
onBackPressed();
|
|
||||||
return true;
|
|
||||||
case R.id.action_send_report:
|
|
||||||
processReport();
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTitleChanged(CharSequence title, int color) {
|
public void onTitleChanged(CharSequence title, int color) {
|
||||||
super.onTitleChanged(title, color);
|
super.onTitleChanged(title, color);
|
||||||
@@ -234,12 +108,6 @@ public class DevReportActivity extends BaseCrashReportDialog
|
|||||||
getDelegate().onConfigurationChanged(newConfig);
|
getDelegate().onConfigurationChanged(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle state) {
|
|
||||||
super.onSaveInstanceState(state);
|
|
||||||
state.putBoolean(STATE_REVIEWING, reviewing);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
@@ -257,132 +125,51 @@ public class DevReportActivity extends BaseCrashReportDialog
|
|||||||
closeReport();
|
closeReport();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
void sendCrashReport(String comment, String email) {
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
sendCrash(comment, email);
|
||||||
ReportField field = (ReportField) buttonView.getTag();
|
|
||||||
if (field != null) {
|
|
||||||
if (isChecked) excludedFields.remove(field);
|
|
||||||
else excludedFields.add(field);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
|
|
||||||
private boolean isFeedback() {
|
private boolean isFeedback() {
|
||||||
return getException() instanceof UserFeedback;
|
return getException() instanceof UserFeedback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refresh() {
|
void displayFragment(boolean showReportForm) {
|
||||||
report.setVisibility(INVISIBLE);
|
Fragment f;
|
||||||
progress.setVisibility(VISIBLE);
|
if (showReportForm) {
|
||||||
report.removeAllViews();
|
File file =
|
||||||
new AsyncTask<Void, Void, CrashReportData>() {
|
(File) getIntent().getSerializableExtra(EXTRA_REPORT_FILE);
|
||||||
|
f = ReportFormFragment.newInstance(isFeedback(), file);
|
||||||
|
requireNonNull(getDelegate().getSupportActionBar()).show();
|
||||||
|
} else {
|
||||||
|
f = new CrashFragment();
|
||||||
|
requireNonNull(getDelegate().getSupportActionBar()).hide();
|
||||||
|
}
|
||||||
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.fragmentContainer, f, f.getTag())
|
||||||
|
.commit();
|
||||||
|
|
||||||
@Override
|
|
||||||
protected CrashReportData doInBackground(Void... args) {
|
|
||||||
File reportFile = (File) getIntent().getSerializableExtra(
|
|
||||||
EXTRA_REPORT_FILE);
|
|
||||||
CrashReportPersister persister = new CrashReportPersister();
|
|
||||||
try {
|
|
||||||
return persister.load(reportFile);
|
|
||||||
} catch (IOException | JSONException e) {
|
|
||||||
LOG.log(WARNING, "Could not load report file", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(CrashReportData crashData) {
|
|
||||||
LayoutInflater inflater = getLayoutInflater();
|
|
||||||
if (crashData != null) {
|
|
||||||
for (Entry<ReportField, Element> e : crashData.entrySet()) {
|
|
||||||
ReportField field = e.getKey();
|
|
||||||
String value = e.getValue().toString()
|
|
||||||
.replaceAll("\\\\n", "\n");
|
|
||||||
boolean required = requiredFields.contains(field);
|
|
||||||
boolean excluded = excludedFields.contains(field);
|
|
||||||
View v = inflater.inflate(R.layout.list_item_crash,
|
|
||||||
report, false);
|
|
||||||
CheckBox cb = v.findViewById(R.id.include_in_report);
|
|
||||||
cb.setTag(field);
|
|
||||||
cb.setChecked(required || !excluded);
|
|
||||||
cb.setEnabled(!required);
|
|
||||||
cb.setOnCheckedChangeListener(DevReportActivity.this);
|
|
||||||
cb.setText(field.toString());
|
|
||||||
TextView content = v.findViewById(R.id.content);
|
|
||||||
content.setText(value);
|
|
||||||
report.addView(v);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
View v = inflater.inflate(
|
|
||||||
android.R.layout.simple_list_item_1, report, false);
|
|
||||||
TextView error = v.findViewById(android.R.id.text1);
|
|
||||||
error.setText(R.string.could_not_load_report_data);
|
|
||||||
report.addView(v);
|
|
||||||
}
|
|
||||||
report.setVisibility(VISIBLE);
|
|
||||||
progress.setVisibility(GONE);
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processReport() {
|
@Override
|
||||||
userCommentView.setEnabled(false);
|
public void invalidateOptionsMenu() {
|
||||||
userEmailView.setEnabled(false);
|
super.invalidateOptionsMenu();
|
||||||
sendReport.setEnabled(false);
|
getDelegate().invalidateOptionsMenu();
|
||||||
progress.setVisibility(VISIBLE);
|
|
||||||
boolean includeReport = !isFeedback() || includeDebugReport.isChecked();
|
|
||||||
new AsyncTask<Void, Void, Boolean>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(Void... args) {
|
|
||||||
File reportFile = (File) getIntent().getSerializableExtra(
|
|
||||||
EXTRA_REPORT_FILE);
|
|
||||||
CrashReportPersister persister = new CrashReportPersister();
|
|
||||||
try {
|
|
||||||
CrashReportData data = persister.load(reportFile);
|
|
||||||
if (includeReport) {
|
|
||||||
for (ReportField field : excludedFields) {
|
|
||||||
LOG.info("Removing field " + field.name());
|
|
||||||
data.remove(field);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Iterator<Entry<ReportField, Element>> iter =
|
|
||||||
data.entrySet().iterator();
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
Entry<ReportField, Element> e = iter.next();
|
|
||||||
if (!requiredFields.contains(e.getKey())) {
|
|
||||||
iter.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
persister.store(data, reportFile);
|
|
||||||
return true;
|
|
||||||
} catch (IOException | JSONException e) {
|
|
||||||
LOG.log(WARNING, "Error processing report file", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Boolean success) {
|
|
||||||
if (success) {
|
|
||||||
// Retrieve user's comment and email address, if any
|
|
||||||
String comment = "";
|
|
||||||
if (userCommentView != null)
|
|
||||||
comment = userCommentView.getText().toString();
|
|
||||||
String email = "";
|
|
||||||
if (userEmailView != null) {
|
|
||||||
email = userEmailView.getText().toString();
|
|
||||||
}
|
|
||||||
sendCrash(comment, email);
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeReport() {
|
void closeReport() {
|
||||||
cancelReports();
|
cancelReports();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit() {
|
||||||
|
if (!isFeedback()) {
|
||||||
|
Intent i = new Intent(this, HideUiActivity.class);
|
||||||
|
i.addFlags(FLAG_ACTIVITY_NEW_TASK
|
||||||
|
| FLAG_ACTIVITY_NO_ANIMATION
|
||||||
|
| FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
package org.briarproject.briar.android.reporting;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.acra.ReportField;
|
||||||
|
import org.acra.collector.CrashReportData;
|
||||||
|
import org.acra.file.CrashReportPersister;
|
||||||
|
import org.acra.model.Element;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.json.JSONException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static android.view.MenuItem.SHOW_AS_ACTION_ALWAYS;
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.INVISIBLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static java.util.logging.Level.WARNING;
|
||||||
|
import static java.util.logging.Logger.getLogger;
|
||||||
|
import static org.acra.ACRAConstants.EXTRA_REPORT_FILE;
|
||||||
|
import static org.acra.ReportField.ANDROID_VERSION;
|
||||||
|
import static org.acra.ReportField.APP_VERSION_CODE;
|
||||||
|
import static org.acra.ReportField.APP_VERSION_NAME;
|
||||||
|
import static org.acra.ReportField.PACKAGE_NAME;
|
||||||
|
import static org.acra.ReportField.REPORT_ID;
|
||||||
|
import static org.acra.ReportField.STACK_TRACE;
|
||||||
|
|
||||||
|
@MethodsNotNullByDefault
|
||||||
|
@ParametersNotNullByDefault
|
||||||
|
public class ReportFormFragment extends Fragment
|
||||||
|
implements OnCheckedChangeListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
getLogger(ReportFormFragment.class.getName());
|
||||||
|
private static final String IS_FEEDBACK = "isFeedback";
|
||||||
|
private static final Set<ReportField> requiredFields = new HashSet<>();
|
||||||
|
private static final Set<ReportField> excludedFields = new HashSet<>();
|
||||||
|
static {
|
||||||
|
requiredFields.add(REPORT_ID);
|
||||||
|
requiredFields.add(APP_VERSION_CODE);
|
||||||
|
requiredFields.add(APP_VERSION_NAME);
|
||||||
|
requiredFields.add(PACKAGE_NAME);
|
||||||
|
requiredFields.add(ANDROID_VERSION);
|
||||||
|
requiredFields.add(STACK_TRACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isFeedback;
|
||||||
|
private File reportFile;
|
||||||
|
|
||||||
|
private EditText userCommentView;
|
||||||
|
private EditText userEmailView;
|
||||||
|
private CheckBox includeDebugReport;
|
||||||
|
private Button chevron;
|
||||||
|
private LinearLayout report;
|
||||||
|
private View progress;
|
||||||
|
@Nullable
|
||||||
|
private MenuItem sendReport;
|
||||||
|
|
||||||
|
static ReportFormFragment newInstance(boolean isFeedback,
|
||||||
|
File reportFile) {
|
||||||
|
ReportFormFragment f = new ReportFormFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putBoolean(IS_FEEDBACK, isFeedback);
|
||||||
|
args.putSerializable(EXTRA_REPORT_FILE, reportFile);
|
||||||
|
f.setArguments(args);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
@Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_report_form, container,
|
||||||
|
false);
|
||||||
|
|
||||||
|
userCommentView = v.findViewById(R.id.user_comment);
|
||||||
|
userEmailView = v.findViewById(R.id.user_email);
|
||||||
|
includeDebugReport = v.findViewById(R.id.include_debug_report);
|
||||||
|
chevron = v.findViewById(R.id.chevron);
|
||||||
|
report = v.findViewById(R.id.report_content);
|
||||||
|
progress = v.findViewById(R.id.progress_wheel);
|
||||||
|
|
||||||
|
Bundle args = requireNonNull(getArguments());
|
||||||
|
isFeedback = args.getBoolean(IS_FEEDBACK);
|
||||||
|
reportFile =
|
||||||
|
(File) requireNonNull(args.getSerializable(EXTRA_REPORT_FILE));
|
||||||
|
|
||||||
|
if (isFeedback) {
|
||||||
|
includeDebugReport
|
||||||
|
.setText(getString(R.string.include_debug_report_feedback));
|
||||||
|
userCommentView.setHint(R.string.enter_feedback);
|
||||||
|
} else {
|
||||||
|
includeDebugReport.setChecked(true);
|
||||||
|
userCommentView.setHint(R.string.describe_crash);
|
||||||
|
}
|
||||||
|
|
||||||
|
chevron.setOnClickListener(view -> {
|
||||||
|
boolean show = chevron.getText().equals(getString(R.string.show));
|
||||||
|
if (show) {
|
||||||
|
chevron.setText(R.string.hide);
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
chevron.setText(R.string.show);
|
||||||
|
report.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
if (chevron.isSelected()) refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.dev_report_actions, menu);
|
||||||
|
sendReport = menu.findItem(R.id.action_send_report);
|
||||||
|
// calling setShowAsAction() shouldn't be needed, but for some reason is
|
||||||
|
sendReport.setShowAsAction(SHOW_AS_ACTION_ALWAYS);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.action_send_report) {
|
||||||
|
processReport();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
ReportField field = (ReportField) buttonView.getTag();
|
||||||
|
if (field != null) {
|
||||||
|
if (isChecked) excludedFields.remove(field);
|
||||||
|
else excludedFields.add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
report.setVisibility(INVISIBLE);
|
||||||
|
progress.setVisibility(VISIBLE);
|
||||||
|
report.removeAllViews();
|
||||||
|
new AsyncTask<Void, Void, CrashReportData>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CrashReportData doInBackground(Void... args) {
|
||||||
|
CrashReportPersister persister = new CrashReportPersister();
|
||||||
|
try {
|
||||||
|
return persister.load(reportFile);
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
LOG.log(WARNING, "Could not load report file", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(CrashReportData crashData) {
|
||||||
|
LayoutInflater inflater = getLayoutInflater();
|
||||||
|
if (crashData != null) {
|
||||||
|
for (Map.Entry<ReportField, Element> e : crashData
|
||||||
|
.entrySet()) {
|
||||||
|
ReportField field = e.getKey();
|
||||||
|
StringBuilder valueBuilder = new StringBuilder();
|
||||||
|
for (String pair : e.getValue().flatten()) {
|
||||||
|
valueBuilder.append(pair).append("\n");
|
||||||
|
}
|
||||||
|
String value = valueBuilder.toString();
|
||||||
|
boolean required = requiredFields.contains(field);
|
||||||
|
boolean excluded = excludedFields.contains(field);
|
||||||
|
View v = inflater.inflate(R.layout.list_item_crash,
|
||||||
|
report, false);
|
||||||
|
CheckBox cb = v.findViewById(R.id.include_in_report);
|
||||||
|
cb.setTag(field);
|
||||||
|
cb.setChecked(required || !excluded);
|
||||||
|
cb.setEnabled(!required);
|
||||||
|
cb.setOnCheckedChangeListener(ReportFormFragment.this);
|
||||||
|
cb.setText(field.toString());
|
||||||
|
TextView content = v.findViewById(R.id.content);
|
||||||
|
content.setText(value);
|
||||||
|
report.addView(v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
View v = inflater.inflate(
|
||||||
|
android.R.layout.simple_list_item_1, report, false);
|
||||||
|
TextView error = v.findViewById(android.R.id.text1);
|
||||||
|
error.setText(R.string.could_not_load_report_data);
|
||||||
|
report.addView(v);
|
||||||
|
}
|
||||||
|
report.setVisibility(VISIBLE);
|
||||||
|
progress.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processReport() {
|
||||||
|
userCommentView.setEnabled(false);
|
||||||
|
userEmailView.setEnabled(false);
|
||||||
|
requireNonNull(sendReport).setEnabled(false);
|
||||||
|
progress.setVisibility(VISIBLE);
|
||||||
|
boolean includeReport = !isFeedback || includeDebugReport.isChecked();
|
||||||
|
new AsyncTask<Void, Void, Boolean>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... args) {
|
||||||
|
CrashReportPersister persister = new CrashReportPersister();
|
||||||
|
try {
|
||||||
|
CrashReportData data = persister.load(reportFile);
|
||||||
|
if (includeReport) {
|
||||||
|
for (ReportField field : excludedFields) {
|
||||||
|
LOG.info("Removing field " + field.name());
|
||||||
|
data.remove(field);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Iterator<Map.Entry<ReportField, Element>> iter =
|
||||||
|
data.entrySet().iterator();
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
Map.Entry<ReportField, Element> e = iter.next();
|
||||||
|
if (!requiredFields.contains(e.getKey())) {
|
||||||
|
iter.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
persister.store(data, reportFile);
|
||||||
|
return true;
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
LOG.log(WARNING, "Error processing report file", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean success) {
|
||||||
|
if (success) {
|
||||||
|
// Retrieve user's comment and email address, if any
|
||||||
|
String comment = "";
|
||||||
|
if (userCommentView != null)
|
||||||
|
comment = userCommentView.getText().toString();
|
||||||
|
String email = "";
|
||||||
|
if (userEmailView != null) {
|
||||||
|
email = userEmailView.getText().toString();
|
||||||
|
}
|
||||||
|
getDevReportActivity().sendCrashReport(comment, email);
|
||||||
|
}
|
||||||
|
if (getActivity() != null) getDevReportActivity().exit();
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DevReportActivity getDevReportActivity() {
|
||||||
|
return (DevReportActivity) requireNonNull(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.sharing;
|
package org.briarproject.briar.android.sharing;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.StringRes;
|
import android.support.annotation.StringRes;
|
||||||
@@ -19,6 +18,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
|
|||||||
import org.briarproject.briar.android.view.LargeTextInputView;
|
import org.briarproject.briar.android.view.LargeTextInputView;
|
||||||
import org.briarproject.briar.android.view.TextSendController;
|
import org.briarproject.briar.android.view.TextSendController;
|
||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -83,7 +83,8 @@ public abstract class BaseMessageFragment extends BaseFragment
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
// disable button to prevent accidental double actions
|
// disable button to prevent accidental double actions
|
||||||
sendController.setReady(false);
|
sendController.setReady(false);
|
||||||
message.hideSoftKeyboard();
|
message.hideSoftKeyboard();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.threaded;
|
package org.briarproject.briar.android.threaded;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.CallSuper;
|
import android.support.annotation.CallSuper;
|
||||||
@@ -34,6 +33,7 @@ import org.briarproject.briar.android.view.TextSendController;
|
|||||||
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
import org.briarproject.briar.android.view.TextSendController.SendListener;
|
||||||
import org.briarproject.briar.android.view.UnreadMessageButton;
|
import org.briarproject.briar.android.view.UnreadMessageButton;
|
||||||
import org.briarproject.briar.api.client.NamedGroup;
|
import org.briarproject.briar.api.client.NamedGroup;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -341,7 +341,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
|
public void onSendClick(@Nullable String text,
|
||||||
|
List<AttachmentHeader> headers) {
|
||||||
if (isNullOrEmpty(text)) throw new AssertionError();
|
if (isNullOrEmpty(text)) throw new AssertionError();
|
||||||
|
|
||||||
I replyItem = adapter.getHighlightedItem();
|
I replyItem = adapter.getHighlightedItem();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.constraint.ConstraintLayout;
|
import android.support.constraint.ConstraintLayout;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
@@ -10,11 +9,13 @@ import android.view.LayoutInflater;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItemResult;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||||
import static android.support.v4.content.ContextCompat.getColor;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
|
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@@ -60,34 +61,28 @@ public class ImagePreview extends ConstraintLayout {
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showPreview(Collection<Uri> imageUris) {
|
void showPreview(Collection<ImagePreviewItem> items) {
|
||||||
if (listener == null) throw new IllegalStateException();
|
if (listener == null) throw new IllegalStateException();
|
||||||
if (imageUris.size() == 1) {
|
if (items.size() == 1) {
|
||||||
LayoutParams params = (LayoutParams) imageList.getLayoutParams();
|
LayoutParams params = (LayoutParams) imageList.getLayoutParams();
|
||||||
params.width = MATCH_PARENT;
|
params.width = MATCH_PARENT;
|
||||||
imageList.setLayoutParams(params);
|
imageList.setLayoutParams(params);
|
||||||
}
|
}
|
||||||
setVisibility(VISIBLE);
|
setVisibility(VISIBLE);
|
||||||
imageList.setAdapter(new ImagePreviewAdapter(imageUris, listener));
|
ImagePreviewAdapter adapter = new ImagePreviewAdapter(items);
|
||||||
|
imageList.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeUri(Uri uri) {
|
void loadPreviewImage(AttachmentItemResult result) {
|
||||||
ImagePreviewAdapter adapter =
|
ImagePreviewAdapter adapter =
|
||||||
(ImagePreviewAdapter) imageList.getAdapter();
|
((ImagePreviewAdapter) imageList.getAdapter());
|
||||||
requireNonNull(adapter).removeUri(uri);
|
int pos = requireNonNull(adapter).loadItemPreview(result);
|
||||||
|
if (pos != NO_POSITION) {
|
||||||
|
imageList.smoothScrollToPosition(pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImagePreviewListener {
|
interface ImagePreviewListener {
|
||||||
|
|
||||||
void onPreviewLoaded();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when Glide can't load a preview image.
|
|
||||||
*
|
|
||||||
* Warning: Glide may call this multiple times.
|
|
||||||
*/
|
|
||||||
void onUriError(Uri uri);
|
|
||||||
|
|
||||||
void onCancel();
|
void onCancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.LayoutRes;
|
import android.support.annotation.LayoutRes;
|
||||||
import android.support.v7.widget.RecyclerView.Adapter;
|
import android.support.v7.widget.RecyclerView.Adapter;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -9,25 +8,24 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
import org.briarproject.briar.android.attachment.AttachmentItemResult;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
||||||
|
|
||||||
private final List<Uri> items;
|
private final List<ImagePreviewItem> items;
|
||||||
private final ImagePreviewListener listener;
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
private final int layout;
|
private final int layout;
|
||||||
|
|
||||||
ImagePreviewAdapter(Collection<Uri> items, ImagePreviewListener listener) {
|
ImagePreviewAdapter(Collection<ImagePreviewItem> items) {
|
||||||
this.items = new ArrayList<>(items);
|
this.items = new ArrayList<>(items);
|
||||||
this.listener = listener;
|
|
||||||
this.layout = items.size() == 1 ?
|
this.layout = items.size() == 1 ?
|
||||||
R.layout.list_item_image_preview_single :
|
R.layout.list_item_image_preview_single :
|
||||||
R.layout.list_item_image_preview;
|
R.layout.list_item_image_preview;
|
||||||
@@ -38,7 +36,7 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
|||||||
int type) {
|
int type) {
|
||||||
View v = LayoutInflater.from(viewGroup.getContext())
|
View v = LayoutInflater.from(viewGroup.getContext())
|
||||||
.inflate(layout, viewGroup, false);
|
.inflate(layout, viewGroup, false);
|
||||||
return new ImagePreviewViewHolder(v, requireNonNull(listener));
|
return new ImagePreviewViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -52,11 +50,17 @@ class ImagePreviewAdapter extends Adapter<ImagePreviewViewHolder> {
|
|||||||
return items.size();
|
return items.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeUri(Uri uri) {
|
int loadItemPreview(AttachmentItemResult result) {
|
||||||
int pos = items.indexOf(uri);
|
ImagePreviewItem newItem = new ImagePreviewItem(result.getUri());
|
||||||
if (pos == -1) return;
|
int pos = items.indexOf(newItem);
|
||||||
items.remove(uri);
|
if (pos == NO_POSITION) throw new AssertionError();
|
||||||
notifyItemRemoved(pos);
|
ImagePreviewItem item = items.get(pos);
|
||||||
|
if (item.getItem() == null) {
|
||||||
|
item.setItem(requireNonNull(result.getItem()));
|
||||||
|
notifyItemChanged(pos, item);
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
return NO_POSITION;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
class ImagePreviewItem {
|
||||||
|
|
||||||
|
private final Uri uri;
|
||||||
|
@Nullable
|
||||||
|
private AttachmentItem item;
|
||||||
|
|
||||||
|
ImagePreviewItem(Uri uri) {
|
||||||
|
this.uri = uri;
|
||||||
|
this.item = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<ImagePreviewItem> fromUris(Collection<Uri> uris) {
|
||||||
|
List<ImagePreviewItem> items = new ArrayList<>(uris.size());
|
||||||
|
for (Uri uri : uris) {
|
||||||
|
items.add(new ImagePreviewItem(uri));
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItem(AttachmentItem item) {
|
||||||
|
this.item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public AttachmentItem getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
return o instanceof ImagePreviewItem &&
|
||||||
|
uri.equals(((ImagePreviewItem) o).uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return uri.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
|
||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||||
@@ -17,9 +16,9 @@ import com.bumptech.glide.request.target.Target;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
|
||||||
|
|
||||||
import static android.view.View.INVISIBLE;
|
import static android.view.View.INVISIBLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||||
import static com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.FIT_CENTER;
|
import static com.bumptech.glide.load.resource.bitmap.DownsampleStrategy.FIT_CENTER;
|
||||||
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||||
@@ -30,45 +29,47 @@ class ImagePreviewViewHolder extends ViewHolder {
|
|||||||
@DrawableRes
|
@DrawableRes
|
||||||
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
private static final int ERROR_RES = R.drawable.ic_image_broken;
|
||||||
|
|
||||||
private final ImagePreviewListener listener;
|
|
||||||
|
|
||||||
private final ImageView imageView;
|
private final ImageView imageView;
|
||||||
private final ProgressBar progressBar;
|
private final ProgressBar progressBar;
|
||||||
|
|
||||||
ImagePreviewViewHolder(View v, ImagePreviewListener listener) {
|
ImagePreviewViewHolder(View v) {
|
||||||
super(v);
|
super(v);
|
||||||
this.listener = listener;
|
|
||||||
this.imageView = v.findViewById(R.id.imageView);
|
this.imageView = v.findViewById(R.id.imageView);
|
||||||
this.progressBar = v.findViewById(R.id.progressBar);
|
this.progressBar = v.findViewById(R.id.progressBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
void bind(Uri uri) {
|
void bind(ImagePreviewItem item) {
|
||||||
GlideApp.with(imageView)
|
if (item.getItem() == null) {
|
||||||
.load(uri)
|
progressBar.setVisibility(VISIBLE);
|
||||||
.diskCacheStrategy(NONE)
|
GlideApp.with(imageView)
|
||||||
.error(ERROR_RES)
|
.clear(imageView);
|
||||||
.downsample(FIT_CENTER)
|
} else {
|
||||||
.transition(withCrossFade())
|
GlideApp.with(imageView)
|
||||||
.addListener(new RequestListener<Drawable>() {
|
.load(item.getItem())
|
||||||
@Override
|
.diskCacheStrategy(NONE)
|
||||||
public boolean onLoadFailed(@Nullable GlideException e,
|
.error(ERROR_RES)
|
||||||
Object model, Target<Drawable> target,
|
.downsample(FIT_CENTER)
|
||||||
boolean isFirstResource) {
|
.transition(withCrossFade())
|
||||||
progressBar.setVisibility(INVISIBLE);
|
.addListener(new RequestListener<Drawable>() {
|
||||||
listener.onUriError(uri);
|
@Override
|
||||||
return false;
|
public boolean onLoadFailed(@Nullable GlideException e,
|
||||||
}
|
Object model, Target<Drawable> target,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
progressBar.setVisibility(INVISIBLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onResourceReady(Drawable resource,
|
public boolean onResourceReady(Drawable resource,
|
||||||
Object model, Target<Drawable> target,
|
Object model, Target<Drawable> target,
|
||||||
DataSource dataSource, boolean isFirstResource) {
|
DataSource dataSource,
|
||||||
progressBar.setVisibility(INVISIBLE);
|
boolean isFirstResource) {
|
||||||
listener.onPreviewLoaded();
|
progressBar.setVisibility(INVISIBLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into(imageView);
|
.into(imageView);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.arch.lifecycle.LifecycleOwner;
|
||||||
|
import android.arch.lifecycle.LiveData;
|
||||||
|
import android.arch.lifecycle.Observer;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -15,26 +18,36 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentItemResult;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentManager;
|
||||||
|
import org.briarproject.briar.android.attachment.AttachmentResult;
|
||||||
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
import org.briarproject.briar.android.view.ImagePreview.ImagePreviewListener;
|
||||||
|
import org.briarproject.briar.api.messaging.FileTooBigException;
|
||||||
|
import org.jsoup.UnsupportedMimeTypeException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
||||||
|
|
||||||
|
import static android.arch.lifecycle.Lifecycle.State.DESTROYED;
|
||||||
import static android.content.Intent.ACTION_GET_CONTENT;
|
import static android.content.Intent.ACTION_GET_CONTENT;
|
||||||
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
import static android.content.Intent.ACTION_OPEN_DOCUMENT;
|
||||||
import static android.content.Intent.CATEGORY_OPENABLE;
|
import static android.content.Intent.CATEGORY_OPENABLE;
|
||||||
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
import static android.content.Intent.EXTRA_ALLOW_MULTIPLE;
|
||||||
|
import static android.content.Intent.EXTRA_MIME_TYPES;
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
import static android.support.v4.content.ContextCompat.getColor;
|
import static android.support.v4.content.ContextCompat.getColor;
|
||||||
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
import static android.support.v4.view.AbsSavedState.EMPTY_STATE;
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
import static android.widget.Toast.LENGTH_LONG;
|
import static android.widget.Toast.LENGTH_LONG;
|
||||||
import static java.util.Collections.emptyList;
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.IMAGE_MIME_TYPES;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
|
||||||
|
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_DISMISSED;
|
||||||
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.STATE_FINISHED;
|
||||||
|
|
||||||
@@ -44,19 +57,21 @@ public class TextAttachmentController extends TextSendController
|
|||||||
implements ImagePreviewListener {
|
implements ImagePreviewListener {
|
||||||
|
|
||||||
private final ImagePreview imagePreview;
|
private final ImagePreview imagePreview;
|
||||||
private final AttachImageListener imageListener;
|
private final AttachmentListener attachmentListener;
|
||||||
private final CompositeSendButton sendButton;
|
private final CompositeSendButton sendButton;
|
||||||
|
private final AttachmentManager attachmentManager;
|
||||||
|
|
||||||
private CharSequence textHint;
|
private final List<Uri> imageUris = new ArrayList<>();
|
||||||
private List<Uri> imageUris = emptyList();
|
private final CharSequence textHint;
|
||||||
private int previewsLoaded = 0;
|
private boolean loadingUris = false;
|
||||||
private boolean loadingPreviews = false;
|
|
||||||
|
|
||||||
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
public TextAttachmentController(TextInputView v, ImagePreview imagePreview,
|
||||||
SendListener listener, AttachImageListener imageListener) {
|
AttachmentListener attachmentListener,
|
||||||
super(v, listener, false);
|
AttachmentManager attachmentManager) {
|
||||||
this.imageListener = imageListener;
|
super(v, attachmentListener, false);
|
||||||
|
this.attachmentListener = attachmentListener;
|
||||||
this.imagePreview = imagePreview;
|
this.imagePreview = imagePreview;
|
||||||
|
this.attachmentManager = attachmentManager;
|
||||||
this.imagePreview.setImagePreviewListener(this);
|
this.imagePreview.setImagePreviewListener(this);
|
||||||
|
|
||||||
sendButton = (CompositeSendButton) compositeSendButton;
|
sendButton = (CompositeSendButton) compositeSendButton;
|
||||||
@@ -67,10 +82,10 @@ public class TextAttachmentController extends TextSendController
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateViewState() {
|
protected void updateViewState() {
|
||||||
textInput.setEnabled(ready && !loadingPreviews);
|
textInput.setEnabled(ready && !loadingUris);
|
||||||
boolean sendEnabled = ready && !loadingPreviews &&
|
boolean sendEnabled = ready && !loadingUris &&
|
||||||
(!textIsEmpty || canSendEmptyText());
|
(!textIsEmpty || canSendEmptyText());
|
||||||
if (loadingPreviews) {
|
if (loadingUris) {
|
||||||
sendButton.showProgress(true);
|
sendButton.showProgress(true);
|
||||||
} else if (imageUris.isEmpty()) {
|
} else if (imageUris.isEmpty()) {
|
||||||
sendButton.showProgress(false);
|
sendButton.showProgress(false);
|
||||||
@@ -84,7 +99,9 @@ public class TextAttachmentController extends TextSendController
|
|||||||
@Override
|
@Override
|
||||||
public void onSendEvent() {
|
public void onSendEvent() {
|
||||||
if (canSend()) {
|
if (canSend()) {
|
||||||
listener.onSendClick(textInput.getText(), imageUris);
|
if (loadingUris) throw new AssertionError();
|
||||||
|
listener.onSendClick(textInput.getText(),
|
||||||
|
attachmentManager.getAttachmentHeadersForSending());
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,36 +127,101 @@ public class TextAttachmentController extends TextSendController
|
|||||||
builder.show();
|
builder.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Intent intent = new Intent(SDK_INT >= 19 ?
|
Intent intent = getAttachFileIntent();
|
||||||
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
if (attachmentListener.getLifecycle().getCurrentState() != DESTROYED) {
|
||||||
intent.addCategory(CATEGORY_OPENABLE);
|
attachmentListener.onAttachImage(intent);
|
||||||
intent.setType("image/*");
|
|
||||||
if (SDK_INT >= 18) intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
|
|
||||||
requireNonNull(imageListener).onAttachImage(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onImageReceived(@Nullable Intent resultData) {
|
|
||||||
if (resultData == null) return;
|
|
||||||
if (resultData.getData() != null) {
|
|
||||||
imageUris = new ArrayList<>(1);
|
|
||||||
imageUris.add(resultData.getData());
|
|
||||||
onNewUris();
|
|
||||||
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
|
|
||||||
ClipData clipData = resultData.getClipData();
|
|
||||||
imageUris = new ArrayList<>(clipData.getItemCount());
|
|
||||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
|
||||||
imageUris.add(clipData.getItemAt(i).getUri());
|
|
||||||
}
|
|
||||||
onNewUris();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onNewUris() {
|
private Intent getAttachFileIntent() {
|
||||||
if (imageUris.isEmpty()) return;
|
Intent intent = new Intent(SDK_INT >= 19 ?
|
||||||
loadingPreviews = true;
|
ACTION_OPEN_DOCUMENT : ACTION_GET_CONTENT);
|
||||||
|
intent.setType("image/*");
|
||||||
|
intent.addCategory(CATEGORY_OPENABLE);
|
||||||
|
if (SDK_INT >= 19) intent.putExtra(EXTRA_MIME_TYPES, IMAGE_MIME_TYPES);
|
||||||
|
if (SDK_INT >= 18) intent.putExtra(EXTRA_ALLOW_MULTIPLE, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called with the result Intent
|
||||||
|
* returned by the Activity started with {@link #getAttachFileIntent()}.
|
||||||
|
* <p>
|
||||||
|
* This method must be called at most once per call to
|
||||||
|
* {@link AttachmentListener#onAttachImage(Intent)}.
|
||||||
|
* Normally, this is true if called from
|
||||||
|
* {@link Activity#onActivityResult(int, int, Intent)} since this is called
|
||||||
|
* at most once per call to
|
||||||
|
* {@link Activity#startActivityForResult(Intent, int)}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("JavadocReference")
|
||||||
|
public void onImageReceived(@Nullable Intent resultData) {
|
||||||
|
if (resultData == null) return;
|
||||||
|
if (loadingUris || !imageUris.isEmpty()) throw new AssertionError();
|
||||||
|
List<Uri> newUris = new ArrayList<>();
|
||||||
|
if (resultData.getData() != null) {
|
||||||
|
newUris.add(resultData.getData());
|
||||||
|
onNewUris(false, newUris);
|
||||||
|
} else if (SDK_INT >= 18 && resultData.getClipData() != null) {
|
||||||
|
ClipData clipData = resultData.getClipData();
|
||||||
|
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||||
|
newUris.add(clipData.getItemAt(i).getUri());
|
||||||
|
}
|
||||||
|
onNewUris(false, newUris);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNewUris(boolean restart, List<Uri> newUris) {
|
||||||
|
if (newUris.isEmpty()) return;
|
||||||
|
if (loadingUris) throw new AssertionError();
|
||||||
|
loadingUris = true;
|
||||||
|
if (newUris.size() > MAX_ATTACHMENTS_PER_MESSAGE) {
|
||||||
|
newUris = newUris.subList(0, MAX_ATTACHMENTS_PER_MESSAGE);
|
||||||
|
attachmentListener.onTooManyAttachments();
|
||||||
|
}
|
||||||
|
imageUris.addAll(newUris);
|
||||||
updateViewState();
|
updateViewState();
|
||||||
textInput.setHint(R.string.image_caption_hint);
|
textInput.setHint(R.string.image_caption_hint);
|
||||||
imagePreview.showPreview(imageUris);
|
List<ImagePreviewItem> items = ImagePreviewItem.fromUris(imageUris);
|
||||||
|
imagePreview.showPreview(items);
|
||||||
|
// store attachments and show preview when successful
|
||||||
|
LiveData<AttachmentResult> result =
|
||||||
|
attachmentManager.storeAttachments(imageUris, restart);
|
||||||
|
result.observe(attachmentListener, new Observer<AttachmentResult>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable AttachmentResult attachmentResult) {
|
||||||
|
requireNonNull(attachmentResult);
|
||||||
|
boolean finished = attachmentResult.isFinished();
|
||||||
|
boolean success = attachmentResult.isSuccess();
|
||||||
|
if (finished) {
|
||||||
|
result.removeObserver(this);
|
||||||
|
if (!success) return;
|
||||||
|
}
|
||||||
|
boolean noError = onNewAttachmentItemResults(
|
||||||
|
attachmentResult.getItemResults());
|
||||||
|
if (noError && success) onAllAttachmentsCreated();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onNewAttachmentItemResults(
|
||||||
|
Collection<AttachmentItemResult> itemResults) {
|
||||||
|
if (!loadingUris) throw new AssertionError();
|
||||||
|
for (AttachmentItemResult result : itemResults) {
|
||||||
|
if (result.hasError()) {
|
||||||
|
onError(requireNonNull(result.getException()));
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
imagePreview.loadPreviewImage(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAllAttachmentsCreated() {
|
||||||
|
if (!loadingUris) throw new AssertionError();
|
||||||
|
loadingUris = false;
|
||||||
|
updateViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
private void reset() {
|
||||||
@@ -148,10 +230,9 @@ public class TextAttachmentController extends TextSendController
|
|||||||
// hide image layout
|
// hide image layout
|
||||||
imagePreview.setVisibility(GONE);
|
imagePreview.setVisibility(GONE);
|
||||||
// reset image URIs
|
// reset image URIs
|
||||||
imageUris = emptyList();
|
imageUris.clear();
|
||||||
// no preview has been loaded
|
// definitely not loading anymore
|
||||||
previewsLoaded = 0;
|
loadingUris = false;
|
||||||
loadingPreviews = false;
|
|
||||||
// show the image button again, so images can get attached
|
// show the image button again, so images can get attached
|
||||||
updateViewState();
|
updateViewState();
|
||||||
}
|
}
|
||||||
@@ -168,45 +249,36 @@ public class TextAttachmentController extends TextSendController
|
|||||||
@Nullable
|
@Nullable
|
||||||
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
public Parcelable onRestoreInstanceState(Parcelable inState) {
|
||||||
SavedState state = (SavedState) inState;
|
SavedState state = (SavedState) inState;
|
||||||
imageUris = requireNonNull(state.imageUris);
|
if (!imageUris.isEmpty()) throw new AssertionError();
|
||||||
onNewUris();
|
if (state.imageUris != null) onNewUris(true, state.imageUris);
|
||||||
return state.getSuperState();
|
return state.getSuperState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@UiThread
|
||||||
public void onPreviewLoaded() {
|
private void onError(Exception e) {
|
||||||
previewsLoaded++;
|
String errorMsg;
|
||||||
checkAllPreviewsLoaded();
|
Context ctx = imagePreview.getContext();
|
||||||
}
|
if (e instanceof UnsupportedMimeTypeException) {
|
||||||
|
String mimeType = ((UnsupportedMimeTypeException) e).getMimeType();
|
||||||
@Override
|
errorMsg = ctx.getString(
|
||||||
public void onUriError(Uri uri) {
|
R.string.image_attach_error_invalid_mime_type, mimeType);
|
||||||
boolean removed = imageUris.remove(uri);
|
} else if (e instanceof FileTooBigException) {
|
||||||
if (!removed) {
|
int mb = MAX_IMAGE_SIZE / 1024 / 1024;
|
||||||
// we have removed this Uri already, do not remove it again
|
errorMsg = ctx.getString(R.string.image_attach_error_too_big, mb);
|
||||||
return;
|
} else {
|
||||||
|
errorMsg = ctx.getString(R.string.image_attach_error);
|
||||||
}
|
}
|
||||||
imagePreview.removeUri(uri);
|
Toast.makeText(ctx, errorMsg, LENGTH_LONG).show();
|
||||||
if (imageUris.isEmpty()) onCancel();
|
onCancel();
|
||||||
Toast.makeText(textInput.getContext(), R.string.image_attach_error,
|
|
||||||
LENGTH_LONG).show();
|
|
||||||
checkAllPreviewsLoaded();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancel() {
|
public void onCancel() {
|
||||||
textInput.clearText();
|
textInput.clearText();
|
||||||
|
attachmentManager.cancel();
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAllPreviewsLoaded() {
|
|
||||||
if (previewsLoaded == imageUris.size()) {
|
|
||||||
loadingPreviews = false;
|
|
||||||
// all previews were loaded
|
|
||||||
updateViewState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showImageOnboarding(Activity activity,
|
public void showImageOnboarding(Activity activity,
|
||||||
Runnable onOnboardingSeen) {
|
Runnable onOnboardingSeen) {
|
||||||
PromptStateChangeListener listener = (prompt, state) -> {
|
PromptStateChangeListener listener = (prompt, state) -> {
|
||||||
@@ -261,8 +333,11 @@ public class TextAttachmentController extends TextSendController
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface AttachImageListener {
|
@UiThread
|
||||||
void onAttachImage(Intent intent);
|
public interface AttachmentListener extends SendListener, LifecycleOwner {
|
||||||
}
|
|
||||||
|
|
||||||
|
void onAttachImage(Intent intent);
|
||||||
|
|
||||||
|
void onTooManyAttachments();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.briarproject.briar.android.view;
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
@@ -10,6 +9,7 @@ import android.view.View;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.briar.R;
|
import org.briarproject.briar.R;
|
||||||
import org.briarproject.briar.android.view.EmojiTextInputView.TextInputListener;
|
import org.briarproject.briar.android.view.EmojiTextInputView.TextInputListener;
|
||||||
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -84,8 +84,9 @@ public class TextSendController implements TextInputListener {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UiThread
|
||||||
public interface SendListener {
|
public interface SendListener {
|
||||||
void onSendClick(@Nullable String text, List<Uri> imageUris);
|
void onSendClick(@Nullable String text, List<AttachmentHeader> headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
9
briar-android/src/main/res/drawable/ic_crash.xml
Normal file
9
briar-android/src/main/res/drawable/ic_crash.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="128dp"
|
||||||
|
android:height="128dp"
|
||||||
|
android:viewportHeight="24.0"
|
||||||
|
android:viewportWidth="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M11.99,2C6.47,2 2,6.47 2,12s4.47,10 9.99,10S22,17.53 22,12 17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM16.18,7.76l-1.06,1.06 -1.06,-1.06L13,8.82l1.06,1.06L13,10.94 14.06,12l1.06,-1.06L16.18,12l1.06,-1.06 -1.06,-1.06 1.06,-1.06zM7.82,12l1.06,-1.06L9.94,12 11,10.94 9.94,9.88 11,8.82 9.94,7.76 8.88,8.82 7.82,7.76 6.76,8.82l1.06,1.06 -1.06,1.06zM12,14c-2.33,0 -4.31,1.46 -5.11,3.5h10.22c-0.8,-2.04 -2.78,-3.5 -5.11,-3.5z"/>
|
||||||
|
</vector>
|
||||||
@@ -1,224 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
<include
|
||||||
android:id="@+id/report_form"
|
android:id="@+id/appBar"
|
||||||
|
layout="@layout/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"/>
|
||||||
android:visibility="visible"
|
|
||||||
tools:context=".android.reporting.DevReportActivity"
|
|
||||||
tools:visibility="invisible">
|
|
||||||
|
|
||||||
<include
|
<FrameLayout
|
||||||
android:id="@+id/appBar"
|
android:id="@+id/fragmentContainer"
|
||||||
layout="@layout/toolbar"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"/>
|
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
|
||||||
android:id="@+id/user_comment_layout"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/margin_large"
|
|
||||||
android:layout_marginLeft="@dimen/margin_large"
|
|
||||||
android:layout_marginRight="@dimen/margin_large"
|
|
||||||
android:layout_marginStart="@dimen/margin_large"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
app:hintEnabled="false"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/appBar">
|
|
||||||
|
|
||||||
<android.support.design.widget.TextInputEditText
|
|
||||||
android:id="@+id/user_comment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textMultiLine|textCapSentences"
|
|
||||||
android:maxLines="5"
|
|
||||||
tools:hint="@string/describe_crash"/>
|
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
|
||||||
|
|
||||||
<android.support.design.widget.TextInputLayout
|
|
||||||
android:id="@+id/user_email_layout"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/margin_large"
|
|
||||||
android:layout_marginLeft="@dimen/margin_large"
|
|
||||||
android:layout_marginRight="@dimen/margin_large"
|
|
||||||
android:layout_marginStart="@dimen/margin_large"
|
|
||||||
app:hintEnabled="false"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/user_comment_layout">
|
|
||||||
|
|
||||||
<android.support.design.widget.TextInputEditText
|
|
||||||
android:id="@+id/user_email"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/optional_contact_email"
|
|
||||||
android:inputType="textEmailAddress"
|
|
||||||
android:maxLines="1"/>
|
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/include_debug_report"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="@dimen/margin_large"
|
|
||||||
android:layout_marginStart="@dimen/margin_large"
|
|
||||||
android:checked="false"
|
|
||||||
android:text="@string/include_debug_report_crash"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/chevron"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/chevron"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/chevron"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/chevron"
|
|
||||||
style="@style/BriarButtonFlat.Positive"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/show"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/user_email_layout"/>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:id="@+id/report_scroll"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/include_debug_report">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/report_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingBottom="@dimen/listitem_height_one_line_avatar"
|
|
||||||
android:paddingEnd="@dimen/margin_large"
|
|
||||||
android:paddingStart="@dimen/margin_large"
|
|
||||||
android:paddingTop="@dimen/margin_small"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress_wheel"
|
|
||||||
style="?android:attr/progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/include_debug_report"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
||||||
|
|
||||||
<android.support.constraint.ConstraintLayout
|
|
||||||
android:id="@+id/request_report"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"/>
|
||||||
android:padding="@dimen/margin_large"
|
|
||||||
android:visibility="invisible"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<TextView
|
</LinearLayout>
|
||||||
android:id="@+id/crashed"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/briar_crashed"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="@dimen/text_size_large"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/fault"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintVertical_chainStyle="packed"
|
|
||||||
tools:layout_editor_absoluteY="8dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/fault"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/not_your_fault"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="@dimen/text_size_large"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/pleaseSend"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/crashed"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/pleaseSend"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/please_send_report"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="@dimen/text_size_large"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/encrypted"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/fault"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/encrypted"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/report_is_encrypted"
|
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
|
||||||
android:textSize="@dimen/text_size_large"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/acceptButton"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/pleaseSend"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/declineButton"
|
|
||||||
style="@style/BriarButtonFlat.Negative"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/close"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/acceptButton"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/acceptButton"
|
|
||||||
app:layout_constraintHorizontal_weight="1"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/acceptButton"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/acceptButton"
|
|
||||||
style="@style/BriarButtonFlat.Positive"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/margin_large"
|
|
||||||
android:text="@string/send_report"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_weight="1"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/declineButton"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/encrypted"/>
|
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|||||||
123
briar-android/src/main/res/layout/fragment_crash.xml
Normal file
123
briar-android/src/main/res/layout/fragment_crash.xml
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
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">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:fillViewport="true"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/acceptButton"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/margin_large">
|
||||||
|
|
||||||
|
<android.support.v7.widget.AppCompatImageView
|
||||||
|
android:id="@+id/errorIcon"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@drawable/ic_crash"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/crashed"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHeight_max="128dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
app:tint="?attr/colorControlNormal"
|
||||||
|
tools:ignore="ContentDescription"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/crashed"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/briar_crashed"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="@dimen/text_size_large"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/fault"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/errorIcon"
|
||||||
|
tools:layout_editor_absoluteY="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fault"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/not_your_fault"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="@dimen/text_size_large"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/pleaseSend"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/crashed"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pleaseSend"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/please_send_report"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="@dimen/text_size_large"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/encrypted"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/fault"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/encrypted"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="@string/report_is_encrypted"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="@dimen/text_size_large"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/pleaseSend"/>
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/declineButton"
|
||||||
|
style="@style/BriarButtonFlat.Negative"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/close"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/acceptButton"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/acceptButton"
|
||||||
|
app:layout_constraintHorizontal_weight="1"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/acceptButton"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/acceptButton"
|
||||||
|
style="@style/BriarButtonFlat.Positive"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/send_report"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_weight="1"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/declineButton"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/scrollView"/>
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
113
briar-android/src/main/res/layout/fragment_report_form.xml
Normal file
113
briar-android/src/main/res/layout/fragment_report_form.xml
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView
|
||||||
|
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:fillViewport="true">
|
||||||
|
|
||||||
|
<android.support.constraint.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:id="@+id/user_comment_layout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/margin_large"
|
||||||
|
android:layout_marginLeft="@dimen/margin_large"
|
||||||
|
android:layout_marginRight="@dimen/margin_large"
|
||||||
|
android:layout_marginStart="@dimen/margin_large"
|
||||||
|
android:layout_marginTop="@dimen/margin_large"
|
||||||
|
app:hintEnabled="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputEditText
|
||||||
|
android:id="@+id/user_comment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textMultiLine|textCapSentences"
|
||||||
|
android:maxLines="5"
|
||||||
|
tools:hint="@string/describe_crash"/>
|
||||||
|
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:id="@+id/user_email_layout"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/margin_large"
|
||||||
|
android:layout_marginLeft="@dimen/margin_large"
|
||||||
|
android:layout_marginRight="@dimen/margin_large"
|
||||||
|
android:layout_marginStart="@dimen/margin_large"
|
||||||
|
app:hintEnabled="false"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/user_comment_layout">
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputEditText
|
||||||
|
android:id="@+id/user_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/optional_contact_email"
|
||||||
|
android:inputType="textEmailAddress"
|
||||||
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/include_debug_report"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/margin_large"
|
||||||
|
android:layout_marginStart="@dimen/margin_large"
|
||||||
|
android:checked="false"
|
||||||
|
android:text="@string/include_debug_report_crash"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/chevron"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/chevron"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/chevron"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/chevron"
|
||||||
|
style="@style/BriarButtonFlat.Positive"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/show"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/user_email_layout"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/report_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="@dimen/listitem_height_one_line_avatar"
|
||||||
|
android:paddingEnd="@dimen/margin_large"
|
||||||
|
android:paddingStart="@dimen/margin_large"
|
||||||
|
android:paddingTop="@dimen/margin_small"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/include_debug_report"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_wheel"
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/include_debug_report"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -129,7 +129,9 @@
|
|||||||
<string name="message_hint">Type message</string>
|
<string name="message_hint">Type message</string>
|
||||||
<string name="image_caption_hint">Add a caption (optional)</string>
|
<string name="image_caption_hint">Add a caption (optional)</string>
|
||||||
<string name="image_attach">Attach image</string>
|
<string name="image_attach">Attach image</string>
|
||||||
<string name="image_attach_error">Could not attach image</string>
|
<string name="image_attach_error">Could not attach image(s)</string>
|
||||||
|
<string name="image_attach_error_too_big">Image too big. Limit is %d MB.</string>
|
||||||
|
<string name="image_attach_error_invalid_mime_type">Image format unsupported: %s</string>
|
||||||
<string name="set_contact_alias">Change contact name</string>
|
<string name="set_contact_alias">Change contact name</string>
|
||||||
<string name="set_contact_alias_hint">Contact name</string>
|
<string name="set_contact_alias_hint">Contact name</string>
|
||||||
<string name="set_alias_button">Change</string>
|
<string name="set_alias_button">Change</string>
|
||||||
@@ -148,6 +150,7 @@
|
|||||||
<string name="dialog_message_no_image_support">Your contact\'s Briar does not yet support image attachments. Once they upgrade you\'ll see a different icon.</string>
|
<string name="dialog_message_no_image_support">Your contact\'s Briar does not yet support image attachments. Once they upgrade you\'ll see a different icon.</string>
|
||||||
<string name="dialog_title_image_support">You can now send images to this contact</string>
|
<string name="dialog_title_image_support">You can now send images to this contact</string>
|
||||||
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
|
<string name="dialog_message_image_support">Tap this icon to attach images.</string>
|
||||||
|
<string name="messaging_too_many_attachments_toast">Only the first %d images will be sent</string>
|
||||||
|
|
||||||
<!-- Adding Contacts -->
|
<!-- Adding Contacts -->
|
||||||
|
|
||||||
@@ -213,6 +216,24 @@
|
|||||||
<item quantity="one">New contact added.</item>
|
<item quantity="one">New contact added.</item>
|
||||||
<item quantity="other">%d new contacts added.</item>
|
<item quantity="other">%d new contacts added.</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="adding_contact_slow_warning">Adding this contact is taking longer than usual</string>
|
||||||
|
<string name="adding_contact_slow_title">Cannot Connect to Contact</string>
|
||||||
|
<string name="adding_contact_slow_text">Adding this contact is taking longer than usual.\n\nPlease check that your contact has received your link and added you:</string>
|
||||||
|
<string name="offline_state">No Internet connection</string>
|
||||||
|
<string name="duplicate_link_dialog_title">Duplicate Link</string>
|
||||||
|
<string name="duplicate_link_dialog_text_1">You already have a pending contact with this link: %s</string>
|
||||||
|
<!-- This is a question asking whether two nicknames refer to the same person -->
|
||||||
|
<string name="duplicate_link_dialog_text_2">Are %s and %s the same person?</string>
|
||||||
|
<!-- This is a button for answering that two nicknames do indeed refer to the same person. This
|
||||||
|
string will be used in a dialog button, so if the translation of this string is longer than 20
|
||||||
|
characters, please use "Yes" instead, and use "No" for the "Different Person" button -->
|
||||||
|
<string name="same_person_button">Same Person</string>
|
||||||
|
<!-- This is a button for answering that two nicknames refer to different people. This string
|
||||||
|
will be used in a dialog button, so if the translation of this string longer than 20 characters,
|
||||||
|
please use "No" instead, and use "Yes" for the "Same Person" button -->
|
||||||
|
<string name="different_person_button">Different Person</string>
|
||||||
|
<string name="duplicate_link_dialog_text_3">%s and %s sent you the same link.\n\nOne of them may be trying to discover who your contacts are.\n\nDon\'t tell them you received the same link from someone else.</string>
|
||||||
|
<string name="pending_contact_updated_toast">Pending contact updated</string>
|
||||||
|
|
||||||
<!-- Introductions -->
|
<!-- Introductions -->
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||||
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
|
import org.briarproject.briar.android.attachment.ImageHelper.DecodeResult;
|
||||||
import org.briarproject.briar.api.messaging.Attachment;
|
import org.briarproject.briar.api.messaging.Attachment;
|
||||||
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
import org.briarproject.briar.api.messaging.AttachmentHeader;
|
||||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||||
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class AttachmentControllerTest extends BrambleMockTestCase {
|
public class AttachmentRetrieverTest extends BrambleMockTestCase {
|
||||||
|
|
||||||
private final AttachmentDimensions dimensions = new AttachmentDimensions(
|
private final AttachmentDimensions dimensions = new AttachmentDimensions(
|
||||||
100, 50, 200, 75, 300
|
100, 50, 200, 75, 300
|
||||||
@@ -32,8 +32,8 @@ public class AttachmentControllerTest extends BrambleMockTestCase {
|
|||||||
private final MessagingManager messagingManager =
|
private final MessagingManager messagingManager =
|
||||||
context.mock(MessagingManager.class);
|
context.mock(MessagingManager.class);
|
||||||
private final ImageHelper imageHelper = context.mock(ImageHelper.class);
|
private final ImageHelper imageHelper = context.mock(ImageHelper.class);
|
||||||
private final AttachmentController controller =
|
private final AttachmentRetriever controller =
|
||||||
new AttachmentController(
|
new AttachmentRetriever(
|
||||||
messagingManager,
|
messagingManager,
|
||||||
dimensions,
|
dimensions,
|
||||||
imageHelper
|
imageHelper
|
||||||
@@ -94,23 +94,6 @@ public class AttachmentControllerTest extends BrambleMockTestCase {
|
|||||||
assertFalse(item.hasError());
|
assertFalse(item.hasError());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testImageHealsWrongMimeType() {
|
|
||||||
AttachmentHeader h = getAttachmentHeader("image/png");
|
|
||||||
|
|
||||||
context.checking(new Expectations() {{
|
|
||||||
oneOf(imageHelper).decodeStream(with(any(InputStream.class)));
|
|
||||||
will(returnValue(new DecodeResult(160, 240, "image/jpeg")));
|
|
||||||
oneOf(imageHelper).getExtensionFromMimeType("image/jpeg");
|
|
||||||
will(returnValue("jpg"));
|
|
||||||
}});
|
|
||||||
|
|
||||||
AttachmentItem item = controller.getAttachmentItem(h, attachment, true);
|
|
||||||
assertEquals("image/jpeg", item.getMimeType());
|
|
||||||
assertEquals("jpg", item.getExtension());
|
|
||||||
assertFalse(item.hasError());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBigJpegImage() {
|
public void testBigJpegImage() {
|
||||||
String mimeType = "image/jpeg";
|
String mimeType = "image/jpeg";
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.attachment;
|
||||||
|
|
||||||
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
import com.bumptech.glide.util.MarkEnforcingInputStream;
|
||||||
|
|
||||||
@@ -4,26 +4,30 @@ import org.briarproject.bramble.api.identity.Author;
|
|||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.Message;
|
import org.briarproject.bramble.api.sync.Message;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.Immutable;
|
import javax.annotation.concurrent.Immutable;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public abstract class ThreadedMessage extends PrivateMessage {
|
public abstract class ThreadedMessage {
|
||||||
|
|
||||||
|
private final Message message;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final MessageId parent;
|
private final MessageId parent;
|
||||||
private final Author author;
|
private final Author author;
|
||||||
|
|
||||||
public ThreadedMessage(Message message, @Nullable MessageId parent,
|
public ThreadedMessage(Message message, @Nullable MessageId parent,
|
||||||
Author author) {
|
Author author) {
|
||||||
super(message);
|
this.message = message;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.author = author;
|
this.author = author;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Message getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MessageId getParent() {
|
public MessageId getParent() {
|
||||||
return parent;
|
return parent;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user