Compare commits

..

1 Commits

Author SHA1 Message Date
akwizgran
62eda5cd91 Removed forums for beta release. 2016-05-13 12:32:09 +01:00
140 changed files with 250 additions and 9384 deletions

View File

@@ -2,6 +2,10 @@ apply plugin: 'com.android.library'
apply plugin: 'witness'
apply plugin: 'com.neenbedankt.android-apt'
repositories {
maven { url 'http://repo1.maven.org/maven2' }
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

View File

@@ -1,890 +0,0 @@
package org.briarproject;
import net.jodah.concurrentunit.Waiter;
import org.briarproject.api.clients.MessageQueueManager;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.SyncSession;
import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.api.system.Clock;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.properties.PropertiesModule;
import org.briarproject.sync.SyncModule;
import org.briarproject.transport.TransportModule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import javax.inject.Inject;
import static org.briarproject.TestPluginsModule.MAX_LATENCY;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ForumSharingIntegrationTest extends BriarTestCase {
LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
SyncSessionFactory sync0, sync1, sync2;
ForumManager forumManager0, forumManager1, forumManager2;
ContactManager contactManager0, contactManager1, contactManager2;
ContactId contactId0, contactId2, contactId1, contactId21;
IdentityManager identityManager0, identityManager1, identityManager2;
LocalAuthor author0, author1, author2;
Forum forum0;
SharerListener listener0, listener2;
InviteeListener listener1;
@Inject
Clock clock;
@Inject
AuthorFactory authorFactory;
// objects accessed from background threads need to be volatile
private volatile ForumSharingManager forumSharingManager0;
private volatile ForumSharingManager forumSharingManager1;
private volatile ForumSharingManager forumSharingManager2;
private volatile Waiter eventWaiter;
private volatile Waiter msgWaiter;
private final File testDir = TestUtils.getTestDirectory();
private final SecretKey master = TestUtils.getSecretKey();
private final int TIMEOUT = 15000;
private final String SHARER = "Sharer";
private final String INVITEE = "Invitee";
private final String SHARER2 = "Sharer2";
private static final Logger LOG =
Logger.getLogger(ForumSharingIntegrationTest.class.getName());
private ForumSharingIntegrationTestComponent t0, t1, t2;
@Rule
public ExpectedException thrown=ExpectedException.none();
@Before
public void setUp() {
ForumSharingIntegrationTestComponent component =
DaggerForumSharingIntegrationTestComponent.builder().build();
component.inject(this);
injectEagerSingletons(component);
assertTrue(testDir.mkdirs());
File t0Dir = new File(testDir, SHARER);
t0 = DaggerForumSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
injectEagerSingletons(t0);
File t1Dir = new File(testDir, INVITEE);
t1 = DaggerForumSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
injectEagerSingletons(t1);
File t2Dir = new File(testDir, SHARER2);
t2 = DaggerForumSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
injectEagerSingletons(t2);
identityManager0 = t0.getIdentityManager();
identityManager1 = t1.getIdentityManager();
identityManager2 = t2.getIdentityManager();
contactManager0 = t0.getContactManager();
contactManager1 = t1.getContactManager();
contactManager2 = t2.getContactManager();
forumManager0 = t0.getForumManager();
forumManager1 = t1.getForumManager();
forumManager2 = t2.getForumManager();
forumSharingManager0 = t0.getForumSharingManager();
forumSharingManager1 = t1.getForumSharingManager();
forumSharingManager2 = t2.getForumSharingManager();
sync0 = t0.getSyncSessionFactory();
sync1 = t1.getSyncSessionFactory();
sync2 = t2.getSyncSessionFactory();
// initialize waiters fresh for each test
eventWaiter = new Waiter();
msgWaiter = new Waiter();
}
@Test
public void testSuccessfulSharing() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was added successfully
assertEquals(0, forumSharingManager0.getAvailableForums().size());
assertEquals(1, forumManager1.getForums().size());
// invitee has one invitation message from sharer
List<ForumInvitationMessage> list =
new ArrayList<>(forumSharingManager1
.getForumInvitationMessages(contactId0));
assertEquals(1, list.size());
// check other things are alright with the forum message
ForumInvitationMessage invitation = list.get(0);
assertFalse(invitation.isAvailable());
assertEquals(forum0.getName(), invitation.getForumName());
assertEquals(contactId1, invitation.getContactId());
assertEquals("Hi!", invitation.getMessage());
// sharer has own invitation message
assertEquals(1,
forumSharingManager0.getForumInvitationMessages(contactId1)
.size());
// forum can not be shared again
Contact c1 = contactManager0.getContact(contactId1);
assertFalse(forumSharingManager0.canBeShared(forum0.getId(), c1));
Contact c0 = contactManager1.getContact(contactId0);
assertFalse(forumSharingManager1.canBeShared(forum0.getId(), c0));
} finally {
stopLifecycles();
}
}
@Test
public void testDeclinedSharing() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(false);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, null);
// sync first request message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was not added
assertEquals(0, forumSharingManager0.getAvailableForums().size());
assertEquals(0, forumManager1.getForums().size());
// forum is no longer available to invitee who declined
assertEquals(0, forumSharingManager1.getAvailableForums().size());
// invitee has one invitation message from sharer
List<ForumInvitationMessage> list =
new ArrayList<>(forumSharingManager1
.getForumInvitationMessages(contactId0));
assertEquals(1, list.size());
// check other things are alright with the forum message
ForumInvitationMessage invitation = list.get(0);
assertFalse(invitation.isAvailable());
assertEquals(forum0.getName(), invitation.getForumName());
assertEquals(contactId1, invitation.getContactId());
assertEquals(null, invitation.getMessage());
// sharer has own invitation message
assertEquals(1,
forumSharingManager0.getForumInvitationMessages(contactId1)
.size());
// forum can be shared again
Contact c1 = contactManager0.getContact(contactId1);
assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
} finally {
stopLifecycles();
}
}
@Test
public void testInviteeLeavesAfterFinished() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was added successfully
assertEquals(0, forumSharingManager0.getAvailableForums().size());
assertEquals(1, forumManager1.getForums().size());
assertTrue(forumManager1.getForums().contains(forum0));
// sharer shares forum with invitee
Contact c1 = contactManager0.getContact(contactId1);
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(c1));
// invitee gets forum shared by sharer
Contact contact0 = contactManager1.getContact(contactId1);
assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0));
// invitee un-subscribes from forum
forumManager1.removeForum(forum0);
// send leave message to sharer
syncToSharer();
// forum is gone
assertEquals(0, forumSharingManager0.getAvailableForums().size());
assertEquals(0, forumManager1.getForums().size());
// sharer no longer shares forum with invitee
assertFalse(forumSharingManager0.getSharedWith(forum0.getId())
.contains(c1));
// invitee no longer gets forum shared by sharer
assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0));
// forum can be shared again
assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
Contact c0 = contactManager1.getContact(contactId0);
assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
} finally {
stopLifecycles();
}
}
@Test
public void testSharerLeavesAfterFinished() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, null);
// sync first request message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was added successfully
assertEquals(0, forumSharingManager0.getAvailableForums().size());
assertEquals(1, forumManager1.getForums().size());
assertTrue(forumManager1.getForums().contains(forum0));
// sharer shares forum with invitee
Contact c1 = contactManager0.getContact(contactId1);
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(c1));
// invitee gets forum shared by sharer
Contact contact0 = contactManager1.getContact(contactId1);
assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0));
// sharer un-subscribes from forum
forumManager0.removeForum(forum0);
// send leave message to invitee
syncToInvitee();
// forum is gone for sharer, but not invitee
assertEquals(0, forumManager0.getForums().size());
assertEquals(1, forumManager1.getForums().size());
// invitee no longer shares forum with sharer
Contact c0 = contactManager1.getContact(contactId0);
assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
.contains(c0));
// sharer no longer gets forum shared by invitee
assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0));
// forum can be shared again
assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
} finally {
stopLifecycles();
}
}
@Test
public void testSharerLeavesBeforeResponse() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, null);
// sharer un-subscribes from forum
forumManager0.removeForum(forum0);
// from her on expect the response to fail with a DbException
thrown.expect(DbException.class);
// sync first request message and leave message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// invitee has no forums available
assertEquals(0, forumSharingManager1.getAvailableForums().size());
} finally {
stopLifecycles();
}
}
@Test
public void testSessionIdReuse() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was added successfully
assertEquals(1, forumManager1.getForums().size());
// reset event received state
listener1.requestReceived = false;
// get SessionId from invitation
List<ForumInvitationMessage> list = new ArrayList<>(
forumSharingManager1
.getForumInvitationMessages(contactId0));
assertEquals(1, list.size());
ForumInvitationMessage msg = list.get(0);
SessionId sessionId = msg.getSessionId();
// get all sorts of stuff needed to send a message
DatabaseComponent db = t0.getDatabaseComponent();
MessageQueueManager queue = t0.getMessageQueueManager();
Contact c1 = contactManager0.getContact(contactId1);
PrivateGroupFactory groupFactory = t0.getPrivateGroupFactory();
Group group = groupFactory
.createPrivateGroup(forumSharingManager0.getClientId(), c1);
long time = clock.currentTimeMillis();
BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
sessionId.getBytes(),
TestUtils.getRandomString(42),
TestUtils.getRandomBytes(FORUM_SALT_LENGTH)
);
byte[] body = t0.getClientHelper().toByteArray(bodyList);
// add the message to the queue
Transaction txn = db.startTransaction(false);
try {
queue.sendMessage(txn, group, time, body, new Metadata());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// actually send the message
syncToInvitee();
// make sure there was no new request received
assertFalse(listener1.requestReceived);
} finally {
stopLifecycles();
}
}
@Test
public void testSharingSameForumWithEachOther() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was added successfully
assertEquals(1, forumManager1.getForums().size());
// invitee now shares same forum back
forumSharingManager1.sendForumInvitation(forum0.getId(),
contactId0,
"I am re-sharing this forum with you.");
// sync re-share invitation
syncToSharer();
// make sure that no new request was received
assertFalse(listener0.requestReceived);
assertEquals(1,
forumSharingManager0.getForumInvitationMessages(contactId1)
.size());
} finally {
stopLifecycles();
}
}
@Test
public void testContactRemoved() throws Exception {
startLifecycles();
try {
// initialize and let invitee accept all requests
defaultInit(true);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// sync response back
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
// forum was added successfully
assertEquals(1, forumManager1.getForums().size());
assertEquals(1,
forumSharingManager0.getSharedWith(forum0.getId()).size());
// remember SessionId from invitation
List<ForumInvitationMessage> list = new ArrayList<>(
forumSharingManager1
.getForumInvitationMessages(contactId0));
assertEquals(1, list.size());
ForumInvitationMessage msg = list.get(0);
SessionId sessionId = msg.getSessionId();
// contacts now remove each other
contactManager0.removeContact(contactId1);
contactManager2.removeContact(contactId21);
contactManager1.removeContact(contactId0);
contactManager1.removeContact(contactId2);
// make sure sharer does share the forum with nobody now
assertEquals(0,
forumSharingManager0.getSharedWith(forum0.getId()).size());
// contacts add each other again
addDefaultContacts();
// get all sorts of stuff needed to send a message
DatabaseComponent db = t0.getDatabaseComponent();
MessageQueueManager queue = t0.getMessageQueueManager();
Contact c1 = contactManager0.getContact(contactId1);
PrivateGroupFactory groupFactory = t0.getPrivateGroupFactory();
Group group = groupFactory
.createPrivateGroup(forumSharingManager0.getClientId(), c1);
long time = clock.currentTimeMillis();
// construct a new message re-using the old SessionId
BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
sessionId.getBytes(),
TestUtils.getRandomString(42),
TestUtils.getRandomBytes(FORUM_SALT_LENGTH)
);
byte[] body = t0.getClientHelper().toByteArray(bodyList);
// add the message to the queue
Transaction txn = db.startTransaction(false);
try {
queue.sendMessage(txn, group, time, body, new Metadata());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// actually send the message
syncToInvitee();
eventWaiter.await(TIMEOUT, 1);
// make sure the new request was received with the same sessionId
// as proof that the state got deleted along with contacts
assertTrue(listener1.requestReceived);
} finally {
stopLifecycles();
}
}
@Test
public void testTwoContactsShareSameForum() throws Exception {
startLifecycles();
try {
// initialize
addDefaultIdentities();
addDefaultContacts();
addForumForSharer();
// second sharer adds the same forum
DatabaseComponent db2 = t2.getDatabaseComponent();
Transaction txn = db2.startTransaction(false);
db2.addGroup(txn, forum0.getGroup());
txn.setComplete();
db2.endTransaction(txn);
// add listeners
listener0 = new SharerListener();
t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(true, false);
t1.getEventBus().addListener(listener1);
listener2 = new SharerListener();
t2.getEventBus().addListener(listener2);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
// second sharer sends invitation for same forum
forumSharingManager2
.sendForumInvitation(forum0.getId(), contactId1, null);
// sync second request message
deliverMessage(sync2, contactId2, sync1, contactId1,
"Sharer2 to Invitee");
// make sure we have only one forum available
Collection<Forum> forums =
forumSharingManager1.getAvailableForums();
assertEquals(1, forums.size());
// make sure both sharers actually share the forum
Collection<Contact> contacts =
forumSharingManager1.getSharedBy(forum0.getId());
assertEquals(2, contacts.size());
// answer second request
Contact c2 = contactManager1.getContact(contactId2);
forumSharingManager1.respondToInvitation(forum0, c2, true);
// sync response
deliverMessage(sync1, contactId21, sync2, contactId2,
"Invitee to Sharer2");
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.responseReceived);
// answer first request
Contact c0 =
contactManager1.getContact(contactId0);
forumSharingManager1.respondToInvitation(forum0, c0, true);
// sync response
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
} finally {
stopLifecycles();
}
}
@After
public void tearDown() throws InterruptedException {
TestUtils.deleteTestDirectory(testDir);
}
private class SharerListener implements EventListener {
public volatile boolean requestReceived = false;
public volatile boolean responseReceived = false;
public void eventOccurred(Event e) {
if (e instanceof MessageValidatedEvent) {
MessageValidatedEvent event = (MessageValidatedEvent) e;
if (event.getClientId()
.equals(forumSharingManager0.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Sharer received message in group " +
((MessageValidatedEvent) e).getMessage()
.getGroupId().hashCode());
msgWaiter.resume();
}
} else if (e instanceof ForumInvitationResponseReceivedEvent) {
ForumInvitationResponseReceivedEvent event =
(ForumInvitationResponseReceivedEvent) e;
eventWaiter.assertEquals(contactId1, event.getContactId());
responseReceived = true;
eventWaiter.resume();
}
// this is only needed for tests where a forum is re-shared
else if (e instanceof ForumInvitationReceivedEvent) {
ForumInvitationReceivedEvent event =
(ForumInvitationReceivedEvent) e;
eventWaiter.assertEquals(contactId1, event.getContactId());
requestReceived = true;
Forum f = event.getForum();
try {
Contact c = contactManager0.getContact(contactId1);
forumSharingManager0.respondToInvitation(f, c, true);
} catch (DbException ex) {
eventWaiter.rethrow(ex);
} finally {
eventWaiter.resume();
}
}
}
}
private class InviteeListener implements EventListener {
public volatile boolean requestReceived = false;
public volatile boolean responseReceived = false;
private final boolean accept, answer;
InviteeListener(boolean accept, boolean answer) {
this.accept = accept;
this.answer = answer;
}
InviteeListener(boolean accept) {
this(accept, true);
}
public void eventOccurred(Event e) {
if (e instanceof MessageValidatedEvent) {
MessageValidatedEvent event = (MessageValidatedEvent) e;
if (event.getClientId()
.equals(forumSharingManager1.getClientId()) &&
!event.isLocal()) {
LOG.info("TEST: Invitee received message in group " +
((MessageValidatedEvent) e).getMessage()
.getGroupId().hashCode());
msgWaiter.resume();
}
} else if (e instanceof ForumInvitationReceivedEvent) {
ForumInvitationReceivedEvent event =
(ForumInvitationReceivedEvent) e;
requestReceived = true;
if (!answer) return;
Forum f = event.getForum();
try {
Contact c =
contactManager1.getContact(event.getContactId());
forumSharingManager1.respondToInvitation(f, c, accept);
} catch (DbException ex) {
eventWaiter.rethrow(ex);
} finally {
eventWaiter.resume();
}
}
// this is only needed for tests where a forum is re-shared
else if (e instanceof ForumInvitationResponseReceivedEvent) {
ForumInvitationResponseReceivedEvent event =
(ForumInvitationResponseReceivedEvent) e;
eventWaiter.assertEquals(contactId0, event.getContactId());
responseReceived = true;
eventWaiter.resume();
}
}
}
private void startLifecycles() throws InterruptedException {
// Start the lifecycle manager and wait for it to finish
lifecycleManager0 = t0.getLifecycleManager();
lifecycleManager1 = t1.getLifecycleManager();
lifecycleManager2 = t2.getLifecycleManager();
lifecycleManager0.startServices();
lifecycleManager1.startServices();
lifecycleManager2.startServices();
lifecycleManager0.waitForStartup();
lifecycleManager1.waitForStartup();
lifecycleManager2.waitForStartup();
}
private void stopLifecycles() throws InterruptedException {
// Clean up
lifecycleManager0.stopServices();
lifecycleManager1.stopServices();
lifecycleManager2.stopServices();
lifecycleManager0.waitForShutdown();
lifecycleManager1.waitForShutdown();
lifecycleManager2.waitForShutdown();
}
private void defaultInit(boolean accept) throws DbException {
addDefaultIdentities();
addDefaultContacts();
addForumForSharer();
listenToEvents(accept);
}
private void addDefaultIdentities() throws DbException {
author0 = authorFactory.createLocalAuthor(SHARER,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
identityManager0.addLocalAuthor(author0);
author1 = authorFactory.createLocalAuthor(INVITEE,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
identityManager1.addLocalAuthor(author1);
author2 = authorFactory.createLocalAuthor(SHARER2,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
identityManager2.addLocalAuthor(author2);
}
private void addDefaultContacts() throws DbException {
// sharer adds invitee as contact
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
// second sharer does the same
contactId21 = contactManager2.addContact(author1,
author2.getId(), master, clock.currentTimeMillis(), true,
true
);
// invitee adds sharers back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
contactId2 = contactManager1.addContact(author2,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
}
private void addForumForSharer() throws DbException {
// sharer creates forum
forum0 = forumManager0.addForum("Test Forum");
}
private void listenToEvents(boolean accept) {
listener0 = new SharerListener();
t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(accept);
t1.getEventBus().addListener(listener1);
listener2 = new SharerListener();
t2.getEventBus().addListener(listener2);
}
private void syncToInvitee() throws IOException, TimeoutException {
deliverMessage(sync0, contactId0, sync1, contactId1,
"Sharer to Invitee");
}
private void syncToSharer() throws IOException, TimeoutException {
deliverMessage(sync1, contactId1, sync0, contactId0,
"Invitee to Sharer");
}
private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
SyncSessionFactory toSync, ContactId toId, String debug)
throws IOException, TimeoutException {
if (debug != null) LOG.info("TEST: Sending message from " + debug);
ByteArrayOutputStream out = new ByteArrayOutputStream();
// Create an outgoing sync session
SyncSession sessionFrom =
fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
// Write whatever needs to be written
sessionFrom.run();
out.close();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
// Create an incoming sync session
SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
// Read whatever needs to be read
sessionTo.run();
in.close();
// wait for message to actually arrive
msgWaiter.await(TIMEOUT, 1);
}
private void injectEagerSingletons(
ForumSharingIntegrationTestComponent component) {
component.inject(new LifecycleModule.EagerSingletons());
component.inject(new ForumModule.EagerSingletons());
component.inject(new CryptoModule.EagerSingletons());
component.inject(new ContactModule.EagerSingletons());
component.inject(new TransportModule.EagerSingletons());
component.inject(new SyncModule.EagerSingletons());
component.inject(new PropertiesModule.EagerSingletons());
}
}

View File

@@ -1,93 +0,0 @@
package org.briarproject;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager;
import org.briarproject.api.clients.PrivateGroupFactory;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.sync.SyncSessionFactory;
import org.briarproject.clients.ClientsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.data.DataModule;
import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule;
import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.properties.PropertiesModule;
import org.briarproject.sync.SyncModule;
import org.briarproject.system.SystemModule;
import org.briarproject.transport.TransportModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
TestDatabaseModule.class,
TestPluginsModule.class,
TestSeedProviderModule.class,
ClientsModule.class,
ContactModule.class,
CryptoModule.class,
DataModule.class,
DatabaseModule.class,
EventModule.class,
ForumModule.class,
IdentityModule.class,
LifecycleModule.class,
PropertiesModule.class,
SyncModule.class,
SystemModule.class,
TransportModule.class
})
public interface ForumSharingIntegrationTestComponent {
void inject(ForumSharingIntegrationTest testCase);
void inject(ContactModule.EagerSingletons init);
void inject(CryptoModule.EagerSingletons init);
void inject(ForumModule.EagerSingletons init);
void inject(LifecycleModule.EagerSingletons init);
void inject(PropertiesModule.EagerSingletons init);
void inject(SyncModule.EagerSingletons init);
void inject(TransportModule.EagerSingletons init);
LifecycleManager getLifecycleManager();
EventBus getEventBus();
IdentityManager getIdentityManager();
ContactManager getContactManager();
ForumSharingManager getForumSharingManager();
ForumManager getForumManager();
SyncSessionFactory getSyncSessionFactory();
/* the following methods are only needed to manually construct messages */
DatabaseComponent getDatabaseComponent();
PrivateGroupFactory getPrivateGroupFactory();
ClientHelper getClientHelper();
MessageQueueManager getMessageQueueManager();
}

View File

@@ -832,106 +832,6 @@ public class IntroductionIntegrationTest extends BriarTestCase {
}
}
@Test
public void testIntroduceesRemovedCleanup() throws Exception {
startLifecycles();
try {
// Add Identities
addDefaultIdentities();
// Add Transport Properties
addTransportProperties();
// Add introducees as contacts
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
contactId2 = contactManager0.addContact(author2,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
// Add introducer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
ContactId contactId02 = contactManager2.addContact(author0,
author2.getId(), master, clock.currentTimeMillis(), true,
true
);
assertTrue(contactId0.equals(contactId02));
// listen to events
IntroducerListener listener0 = new IntroducerListener();
t0.getEventBus().addListener(listener0);
IntroduceeListener listener1 = new IntroduceeListener(1, true);
t1.getEventBus().addListener(listener1);
IntroduceeListener listener2 = new IntroduceeListener(2, true);
t2.getEventBus().addListener(listener2);
// make introduction
long time = clock.currentTimeMillis();
Contact introducee1 = contactManager0.getContact(contactId1);
Contact introducee2 = contactManager0.getContact(contactId2);
introductionManager0
.makeIntroduction(introducee1, introducee2, "Hi!", time);
// sync first request message
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener1.requestReceived);
// get database and local group for introducee
DatabaseComponent db0 = t0.getDatabaseComponent();
IntroductionGroupFactory groupFactory0 =
t0.getIntroductionGroupFactory();
Group group1 = groupFactory0.createLocalGroup();
// get local session state messages
Map<MessageId, Metadata> map;
Transaction txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// check that we have one session state
assertEquals(1, map.size());
// introducer removes introducee1
contactManager0.removeContact(contactId1);
// get local session state messages again
txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// make sure local state is still there
assertEquals(1, map.size());
// introducer removes other introducee
contactManager0.removeContact(contactId2);
// get local session state messages again
txn = db0.startTransaction(false);
try {
map = db0.getMessageMetadata(txn, group1.getId());
txn.setComplete();
} finally {
db0.endTransaction(txn);
}
// make sure local state is gone now
assertEquals(0, map.size());
} finally {
stopLifecycles();
}
}
// TODO add a test for faking responses when #256 is implemented
@After

View File

@@ -2,11 +2,6 @@ package org.briarproject;
import org.briarproject.api.UniqueId;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.forum.ForumConstants;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.messaging.MessagingConstants;
import org.briarproject.api.messaging.PrivateMessage;
@@ -18,9 +13,6 @@ import org.junit.Test;
import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
import static org.junit.Assert.assertTrue;
@@ -33,8 +25,6 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
AuthorFactory authorFactory;
@Inject
PrivateMessageFactory privateMessageFactory;
@Inject
ForumPostFactory forumPostFactory;
public MessageSizeIntegrationTest() throws Exception {
MessageSizeIntegrationTestComponent component =
@@ -61,30 +51,4 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
+ MAX_PRIVATE_MESSAGE_BODY_LENGTH);
assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH);
}
@Test
public void testForumPostFitsIntoPacket() throws Exception {
// Create a maximum-length author
String authorName = TestUtils.getRandomString(
MAX_AUTHOR_NAME_LENGTH);
byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
Author author = authorFactory.createAuthor(authorName, authorPublic);
// Create a maximum-length forum post
GroupId groupId = new GroupId(TestUtils.getRandomId());
long timestamp = Long.MAX_VALUE;
MessageId parent = new MessageId(TestUtils.getRandomId());
String contentType = TestUtils.getRandomString(
ForumConstants.MAX_CONTENT_TYPE_LENGTH);
byte[] body = new byte[MAX_FORUM_POST_BODY_LENGTH];
PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
ForumPost post = forumPostFactory.createPseudonymousPost(groupId,
timestamp, parent, author, contentType, body, privateKey);
// Check the size of the serialised message
int length = post.getMessage().getRaw().length;
assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH
+ MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH
+ ForumConstants.MAX_CONTENT_TYPE_LENGTH
+ MAX_FORUM_POST_BODY_LENGTH);
assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH);
}
}

View File

@@ -5,7 +5,6 @@ import org.briarproject.crypto.CryptoModule;
import org.briarproject.data.DataModule;
import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule;
import org.briarproject.messaging.MessagingModule;
import org.briarproject.sync.SyncModule;
@@ -25,7 +24,6 @@ import dagger.Component;
DataModule.class,
DatabaseModule.class,
EventModule.class,
ForumModule.class,
IdentityModule.class,
MessagingModule.class,
SyncModule.class,

View File

@@ -98,78 +98,6 @@
/>
</activity>
<activity
android:name=".android.forum.AvailableForumsActivity"
android:label="@string/available_forums_title"
android:parentActivityName=".android.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.forum.CreateForumActivity"
android:label="@string/create_forum_title"
android:parentActivityName=".android.NavDrawerActivity"
android:windowSoftInputMode="stateVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.forum.ForumActivity"
android:label="@string/app_name"
android:parentActivityName=".android.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.forum.ReadForumPostActivity"
android:label="@string/app_name"
android:parentActivityName=".android.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.forum.ShareForumActivity"
android:label="@string/forums_share_toolbar_header"
android:parentActivityName=".android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.forum.ForumActivity"
/>
</activity>
<activity
android:name=".android.forum.ForumSharingStatusActivity"
android:label="@string/forum_sharing_status"
android:parentActivityName=".android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.forum.ForumActivity"
/>
</activity>
<activity
android:name=".android.forum.WriteForumPostActivity"
android:label="@string/app_name"
android:parentActivityName=".android.NavDrawerActivity"
android:windowSoftInputMode="stateVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".android.NavDrawerActivity"
/>
</activity>
<activity
android:name=".android.identity.CreateIdentityActivity"
android:label="@string/new_identity_title"

View File

@@ -6,6 +6,11 @@ apply plugin: 'witness'
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'de.undercouch.download'
repositories {
jcenter()
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
dependencies {
def supportVersion = '23.2.1'
compile project(':briar-api')

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:radius="@dimen/unread_bubble_size"/>
<padding
android:left="@dimen/unread_bubble_padding_horizontal"
android:right="@dimen/unread_bubble_padding_horizontal"/>
<solid
android:color="@color/briar_primary"/>
<stroke
android:color="@color/briar_text_primary_inverse"
android:width="@dimen/avatar_border_width"/>
</shape>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/availableForumsView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:padding="20dp" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="@dimen/text_size_medium"
android:text="@string/choose_forum_name" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/createForumNameEntry"
android:maxLines="1"
android:inputType="text|textCapSentences" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/createForumFeedback"
android:gravity="center"
android:paddingLeft="50dp"
android:paddingRight="50dp" />
<Button
style="@style/BriarButton"
android:id="@+id/createForumButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@string/create_forum_button" />
<ProgressBar
android:id="@+id/createForumProgressBar"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:indeterminate="true"
android:layout_centerHorizontal="true"
android:visibility="gone" />
</LinearLayout>

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/margin_activity_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/choose_nickname"
android:textSize="@dimen/text_size_large"/>
<android.support.design.widget.TextInputLayout
android:id="@+id/nicknameInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
<EditText
android:id="@+id/nicknameEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:ems="10"
android:inputType="textPersonName"
android:maxLines="1"/>
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/createIdentityButton"
style="@style/BriarButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:enabled="false"
android:text="@string/create_identity_button"/>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:indeterminate="true"
android:visibility="gone"/>
</LinearLayout>

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/expiry_warning"
android:textSize="@dimen/text_size_large"/>

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/default_separator_inverted"
android:padding="@dimen/margin_medium"
android:text="@string/forum_shared_by"
android:textSize="@dimen/text_size_large"/>
<View style="@style/Divider.ForumList"/>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/sharedByView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_medium"
android:paddingTop="@dimen/margin_medium"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/default_separator_inverted"
android:padding="@dimen/margin_medium"
android:text="@string/forum_shared_with"
android:textSize="@dimen/text_size_large"/>
<View style="@style/Divider.ForumList"/>
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/sharedWithView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_medium"
android:paddingTop="@dimen/margin_medium"/>
</LinearLayout>
</ScrollView>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="@+id/shareForumContainer"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
tools:src="@drawable/ic_launcher"/>
app:civ_border_width="@dimen/avatar_border_width"
app:civ_border_color="@color/briar_primary"/>
<ImageView
android:id="@+id/statusView"

View File

@@ -9,10 +9,11 @@
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/contactAvatar"
style="@style/BriarAvatar"
android:layout_width="30dp"
android:layout_height="30dp"
android:transitionName="avatar"
app:civ_border_color="@color/action_bar_text"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/>
<ImageView
@@ -21,7 +22,7 @@
android:layout_height="15dp"
android:layout_gravity="bottom|right"
android:scaleType="fitCenter"
tools:ignore="ContentDescription"
tools:src="@drawable/contact_online"/>
tools:src="@drawable/contact_online"
tools:ignore="ContentDescription"/>
</FrameLayout>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -8,11 +9,11 @@
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/dropdown_picture_size"
android:layout_height="@dimen/dropdown_picture_size"
android:layout_margin="@dimen/margin_small"
tools:src="@drawable/ic_launcher"/>
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"/>
<TextView
android:id="@+id/nameView"

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.briarproject.android.util.BriarRecyclerView
android:id="@+id/forumList"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="org.briarproject.android.util.BriarRecyclerViewBehavior"/>
</android.support.design.widget.CoordinatorLayout>

View File

@@ -10,11 +10,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<org.briarproject.android.util.ViewfinderView
android:id="@+id/viewfinder_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@@ -24,7 +24,6 @@
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarContact1"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_centerHorizontal="true"
@@ -32,6 +31,8 @@
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:layout_toLeftOf="@+id/introductionIcon"
android:layout_toStartOf="@+id/introductionIcon"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/>
<ImageView
@@ -44,7 +45,6 @@
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarContact2"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_centerHorizontal="true"
@@ -53,6 +53,8 @@
android:layout_toEndOf="@+id/introductionIcon"
android:layout_toRightOf="@+id/introductionIcon"
android:transitionName="avatar"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/>
</RelativeLayout>

View File

@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/listitem_horizontal_margin"
android:background="?attr/selectableItemBackground">
<org.briarproject.android.util.TextAvatarView
android:id="@+id/avatarView"
android:layout_width="@dimen/avatar_forum_size"
android:layout_height="@dimen/avatar_forum_size"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
/>
<TextView
android:id="@+id/forumNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/avatarView"
android:layout_toRightOf="@+id/avatarView"
android:maxLines="2"
android:textColor="@android:color/primary_text_light"
android:textSize="@dimen/text_size_medium"
tools:text="This is a name of a forum that is available"/>
<TextView
android:id="@+id/sharedByView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/forumNameView"
android:layout_marginBottom="-8dp"
android:layout_toEndOf="@+id/avatarView"
android:layout_toRightOf="@+id/avatarView"
android:paddingTop="@dimen/margin_medium"
android:textColor="@android:color/secondary_text_light"
android:textSize="@dimen/text_size_small"
tools:text="Shared by Megalox"/>
<Button
android:id="@+id/acceptButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_button_accept"
android:layout_below="@+id/sharedByView"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
<Button
android:id="@+id/declineButton"
style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/dialog_button_decline"
android:layout_below="@+id/sharedByView"
android:layout_toLeftOf="@+id/acceptButton"
android:layout_toStartOf="@+id/acceptButton"/>
<View style="@style/Divider.ForumList"
android:layout_below="@+id/acceptButton"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/>
</RelativeLayout>

View File

@@ -10,55 +10,36 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/listitem_horizontal_margin"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:background="?attr/selectableItemBackground"
>
<FrameLayout
android:id="@+id/avatarFrameView"
android:layout_width="@dimen/listitem_picture_frame_size"
android:layout_height="@dimen/listitem_picture_frame_size"
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size"
android:layout_height="@dimen/listitem_picture_size"
android:layout_gravity="bottom|left"
tools:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/unreadCountView"
android:layout_width="wrap_content"
android:layout_height="@dimen/unread_bubble_size"
android:layout_gravity="right|top"
android:background="@drawable/bubble"
android:gravity="center"
android:minWidth="@dimen/unread_bubble_size"
android:textColor="@color/briar_text_primary_inverse"
android:textSize="@dimen/unread_bubble_text_size"
android:textStyle="bold"
tools:text="123"/>
</FrameLayout>
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:transitionName="avatar"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/>
<LinearLayout
android:id="@+id/textViews"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerVertical="true"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:layout_toEndOf="@+id/avatarFrameView"
android:layout_toLeftOf="@+id/bulbView"
android:layout_toRightOf="@+id/avatarFrameView"
android:orientation="vertical">
android:layout_toRightOf="@+id/avatarView"
android:layout_toEndOf="@+id/avatarView">
<TextView
android:id="@+id/nameView"

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/margin_small"
android:paddingTop="@dimen/margin_small">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_picture_size_small"
android:layout_height="@dimen/listitem_picture_size_small"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
tools:src="@drawable/ic_launcher"/>
<TextView
android:id="@+id/nameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:maxLines="2"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_medium"
tools:text="This is a name of a contact"/>
</LinearLayout>

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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="wrap_content"
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:paddingTop="@dimen/listitem_horizontal_margin"
android:background="?attr/selectableItemBackground">
<org.briarproject.android.util.TextAvatarView
android:id="@+id/avatarView"
android:layout_height="@dimen/avatar_forum_size"
android:layout_width="@dimen/avatar_forum_size"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
/>
<TextView
android:id="@+id/forumNameView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:textColor="@color/briar_text_primary"
android:textSize="@dimen/text_size_medium"
tools:text="This is a name of a forum"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/avatarView"
android:layout_toEndOf="@+id/avatarView"/>
<TextView
android:id="@+id/unreadView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="@dimen/margin_medium"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:textColor="@color/briar_text_secondary"
android:textSize="@dimen/text_size_small"
android:text="@string/no_unread_posts"
android:layout_below="@+id/forumNameView"
android:layout_toRightOf="@+id/avatarView"
android:layout_toEndOf="@+id/avatarView"/>
<TextView
android:id="@+id/dateView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_below="@+id/forumNameView"
android:paddingTop="@dimen/margin_medium"
android:paddingBottom="@dimen/listitem_horizontal_margin"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
android:textColor="@color/briar_text_secondary"
android:textSize="@dimen/text_size_small"
tools:text="Dec 24"/>
<View style="@style/Divider.ForumList"
android:layout_below="@+id/unreadView"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/>
</RelativeLayout>

View File

@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/messageLayout"
layout="@layout/list_item_msg_in"/>
<RelativeLayout
android:id="@+id/introductionLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|start"
android:background="@drawable/notice_in"
android:layout_marginLeft="@dimen/message_bubble_margin_tail"
android:layout_marginRight="@dimen/message_bubble_margin_non_tail">
<TextView
android:id="@+id/introductionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="80dp"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
tools:text="@string/forum_invitation_received"/>
<TextView
android:id="@+id/introductionTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/showForumsButton"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
<Button
android:id="@+id/showForumsButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="-15dp"
android:layout_alignEnd="@+id/introductionText"
android:layout_alignRight="@+id/introductionText"
android:layout_below="@+id/introductionText"
android:text="@string/forum_show_available"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include
android:id="@+id/messageLayout"
layout="@layout/list_item_msg_out"/>
<RelativeLayout
android:id="@+id/introductionLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|end"
android:background="@drawable/notice_out"
android:layout_marginLeft="@dimen/message_bubble_margin_non_tail"
android:layout_marginRight="@dimen/message_bubble_margin_tail">
<TextView
android:id="@+id/introductionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium"
android:textStyle="italic"
tools:text="@string/introduction_request_received"/>
<TextView
android:id="@+id/introductionTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/introductionText"
android:textColor="@color/private_message_date"
android:textSize="@dimen/text_size_tiny"
tools:text="Dec 24, 13:37"/>
<ImageView
android:id="@+id/introductionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@+id/introductionTime"
android:layout_toRightOf="@+id/introductionTime"
android:layout_alignBottom="@+id/introductionTime"
android:layout_marginLeft="@dimen/margin_medium"
tools:ignore="ContentDescription"
tools:src="@drawable/message_delivered"/>
</RelativeLayout>
</LinearLayout>

View File

@@ -14,7 +14,6 @@
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarView"
style="@style/BriarAvatar"
android:layout_width="@dimen/listitem_selectable_picture_size"
android:layout_height="@dimen/listitem_selectable_picture_size"
android:layout_alignParentLeft="true"
@@ -23,6 +22,8 @@
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
android:layout_marginStart="@dimen/listitem_horizontal_margin"
android:transitionName="avatar"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"
tools:src="@drawable/ic_launcher"/>
<TextView

View File

@@ -46,21 +46,6 @@
android:layout_height="@dimen/nav_separator_height"
android:layout_marginLeft="@dimen/margin_large"/>
<android.support.v7.widget.AppCompatButton
android:id="@+id/nav_btn_forums"
style="@style/NavMenuButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/social_chat"
android:onClick="onNavigationClick"
android:text="@string/forums_button"/>
<View
style="@style/Divider"
android:layout_width="match_parent"
android:layout_height="@dimen/margin_separator"
android:layout_marginLeft="@dimen/margin_large"/>
<android.support.v7.widget.AppCompatButton
android:id="@+id/nav_btn_settings"
style="@style/NavMenuButton"

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/margin_activity_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/introductionText"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/margin_medium"
android:layout_weight="1"
android:gravity="top"
android:textSize="@dimen/text_size_medium"
android:text="@string/forum_share_message"/>
<EditText
android:id="@+id/invitationMessageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:gravity="bottom"
android:hint="@string/introduction_message_hint"
android:inputType="text|textMultiLine|textCapSentences"/>
<Button
android:id="@+id/shareForumButton"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/forum_share_button"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
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">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarBackground"
style="@style/BriarAvatar"
android:layout_width="@dimen/avatar_forum_size"
android:layout_height="@dimen/avatar_forum_size"
android:layout_gravity="center"
android:src="@android:color/transparent"
app:civ_fill_color="@color/briar_button_positive"/>
<android.support.v7.widget.AppCompatTextView
android:id="@+id/textAvatarView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLength="1"
android:shadowColor="@color/forum_avatar_shadow"
android:shadowDx="0"
android:shadowDy="1.5"
android:shadowRadius="1.5"
android:textColor="@color/briar_text_primary_inverse"
android:textSize="30sp"
tools:text="T"/>
</merge>

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_forum_compose_post"
android:icon="@drawable/forum_item_create_white"
android:title="@string/forum_compose_post"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_forum_share"
android:icon="@drawable/social_share_white"
android:title="@string/forum_share_button"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_forum_sharing_status"
android:title="@string/forum_sharing_status"
app:showAsAction="never"/>
<item
android:id="@+id/action_forum_delete"
android:icon="@drawable/action_delete_white"
android:title="@string/forum_leave"
app:showAsAction="never"/>
</menu>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_create_forum"
android:icon="@drawable/ic_add_white"
android:title="@string/create_forum_button"
app:showAsAction="ifRoom"/>
</menu>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_share_forum"
android:icon="@drawable/ic_check_white"
android:title="@string/forum_share_action"
app:showAsAction="always"/>
</menu>

View File

@@ -16,9 +16,7 @@
<color name="private_message_date_inverse">#e0e0e0</color>
<color name="unread_background">#FFFFFF</color>
<color name="horizontal_border">#CCCCCC</color>
<color name="forums_available_background">@color/briar_gold</color>
<color name="no_private_messages">#AAAAAA</color>
<color name="forum_avatar_shadow">#99000000</color>
<color name="briar_primary">@color/briar_blue</color>
<color name="briar_primary_dark">@color/briar_blue_dark</color>
@@ -36,17 +34,10 @@
<!-- this is needed as preference_category_material layout uses this color as the text color -->
<color name="preference_fallback_accent_color">@color/briar_accent</color>
<color name="divider">#c1c1c1</color>
<color name="default_separator">#000000</color>
<color name="default_separator_inverted">#ffffff</color>
<color name="menu_background">#FFFFFF</color>
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
<color name="spinner_arrow">@color/briar_blue_dark</color>
<!-- ViewfinderView -->
<color name="possible_result_points">#c0ffbd21</color> <!-- Material Yellow 700 with alpha -->
<color name="result_view">#b0000000</color>
<color name="viewfinder_laser">#d50000</color> <!-- Red accent 700 -->
<color name="viewfinder_mask">#60000000</color>
</resources>

View File

@@ -26,17 +26,10 @@
<dimen name="listitem_height_one_line_avatar">56dp</dimen>
<dimen name="listitem_height_contact_selector">68dp</dimen>
<dimen name="listitem_picture_size">48dp</dimen>
<dimen name="listitem_picture_size_small">23dp</dimen>
<dimen name="listitem_picture_frame_size">50dp</dimen>
<dimen name="listitem_selectable_picture_size">40dp</dimen>
<dimen name="dropdown_picture_size">32dp</dimen>
<dimen name="avatar_forum_size">48dp</dimen>
<dimen name="avatar_border_width">2dp</dimen>
<dimen name="unread_bubble_text_size">12sp</dimen>
<dimen name="unread_bubble_border_width">2dp</dimen>
<dimen name="unread_bubble_padding_horizontal">6dp</dimen>
<dimen name="unread_bubble_size">19dp</dimen>
<dimen name="avatar_border_width">1dp</dimen>
<dimen name="message_bubble_margin_tail">14dp</dimen>
<dimen name="message_bubble_margin_non_tail">51dp</dimen>

View File

@@ -35,7 +35,6 @@
<string name="contact_list_button">Contacts</string>
<string name="delete_contact">Delete contact</string>
<string name="contact_deleted_toast">Contact deleted</string>
<string name="forums_button">Forums</string>
<string name="settings_button">Settings</string>
<string name="sign_out_button">Sign Out</string>
<string name="contact_list_title">Contacts</string>
@@ -66,67 +65,26 @@
<string name="qr_code_invalid">The QR code is invalid</string>
<string name="connecting_to_device">Connecting to device\u2026</string>
<string name="authenticating_with_device">Authenticating with device\u2026</string>
<string name="connection_aborted_local">Connection aborted by us! This could mean that someone is trying to interfere with your connection</string>
<string name="connection_aborted_remote">Connection aborted by your contact! This could mean that someone is trying to interfere with your connection</string>
<string name="connection_aborted_local">Connection failed</string>
<string name="connection_aborted_remote">Connection failed</string>
<string name="no_private_messages">No messages</string>
<string name="private_message_hint">Type message</string>
<string name="message_sent_toast">Message sent</string>
<string name="forums_title">Forums</string>
<string name="no_forums">You don\'t have any forums.\n\nWhy don\'t you create a new one yourself or ask your contacts to share one with you?</string>
<plurals name="forums_shared">
<item quantity="one">%d forum shared by contacts</item>
<item quantity="other">%d forums shared by contacts</item>
</plurals>
<string name="show_forums">Show</string>
<string name="forum_leave">Leave Forum</string>
<string name="forum_left_toast">Left Forum</string>
<string name="forum_sharing_status">Sharing Status</string>
<string name="no_forum_posts">No posts</string>
<string name="no_unread_posts">no unread posts</string>
<plurals name="unread_posts">
<item quantity="one">%d unread post</item>
<item quantity="other">%d unread posts</item>
</plurals>
<string name="create_forum_title">New Forum</string>
<string name="choose_forum_name">Choose a name for your forum:</string>
<string name="create_forum_button">Create Forum</string>
<string name="forum_created_toast">Forum created</string>
<string name="forum_share_action">Share this forum with chosen contacts</string>
<string name="forum_share_button">Share Forum</string>
<string name="forum_shared_snackbar">Forum shared with chosen contacts</string>
<string name="forum_share_message">You may compose an optional invitation message that will be sent to the selected contacts.</string>
<string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string>
<string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string>
<string name="forum_show_available">Show Available Forums</string>
<string name="forum_compose_post">New Forum Post</string>
<string name="from">From:</string>
<string name="anonymous">Anonymous</string>
<string name="new_identity_item">New identity\u2026</string>
<string name="new_identity_title">New Identity</string>
<string name="create_identity_button">Create Identity</string>
<string name="identity_created_toast">Identity created</string>
<string name="forum_post_hint">Type forum post</string>
<string name="available_forums_title">Available Forums</string>
<string name="forum_joined_toast">Joined Forum</string>
<string name="forum_declined_toast">Forum Invitation Declined</string>
<string name="shared_by_format">Shared by %s</string>
<string name="forum_shared_by">Shared by</string>
<string name="forum_shared_with">Shared with</string>
<string name="nobody">Nobody</string>
<string name="no_contacts_prompt">You don\'t have any contacts. Add a contact now?</string>
<string name="add_button">Add</string>
<string name="cancel_button">Cancel</string>
<string name="done_button">Done</string>
<string name="delete_button">Delete</string>
<string name="post_sent_toast">Forum post sent</string>
<plurals name="private_message_notification_text">
<item quantity="one">New private message.</item>
<item quantity="other">%d new private messages.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">New forum post.</item>
<item quantity="other">%d new forum posts.</item>
</plurals>
<!-- Settings -->
<string name="settings_title">Settings</string>
@@ -142,7 +100,6 @@
<string name="panic_setting_hint">Configure how Briar will react when you use a panic button app</string>
<string name="notification_settings_title">Notifications</string>
<string name="notify_private_messages_setting">Show alerts for private messages</string>
<string name="notify_forum_posts_setting">Show alerts for forum posts</string>
<string name="notify_vibration_setting">Vibrate</string>
<string name="notify_sound_setting">Sound</string>
<string name="notify_sound_setting_default">Default ringtone</string>
@@ -209,8 +166,6 @@
<string name="dialog_welcome_message">Add a contact to start communicating securely or press the icon in the upper left corner of the screen for more options.</string>
<string name="dialog_title_share_crash_report">Briar has crashed</string>
<string name="dialog_message_share_crash_report">Would you like to review the crash report and send it to the developers? It will be stored encrypted on your device until the next time you log into Briar, and then sent securely to the developers.</string>
<string name="dialog_title_leave_forum">Confirm Leaving Forum</string>
<string name="dialog_message_leave_forum">Are you sure that you want to leave this forum? Contacts you have shared this forum with might get cut off from receiving updates for this forum.</string>
<string name="dialog_button_ok">OK</string>
<string name="dialog_button_leave">Leave</string>
<string name="dialog_button_introduce">Introduce</string>
@@ -220,8 +175,6 @@
<string name="dashboard_toolbar_header">Briar</string>
<string name="settings_toolbar_header">Settings</string>
<string name="contacts_toolbar_header">Contacts</string>
<string name="forums_toolbar_header">Forums</string>
<string name="forums_share_toolbar_header">Choose Contacts</string>
<!-- Progress titles -->
<string name="progress_title_logout">Signing out of Briar..</string>
<string name="progress_title_please_wait">Please wait..</string>

View File

@@ -93,7 +93,7 @@
</style>
<style name="Divider">
<item name="android:background">@color/divider</item>
<item name="android:background">?android:attr/listDivider</item>
</style>
<style name="Divider.Horizontal" parent="Divider">
@@ -112,14 +112,6 @@
<item name="android:layout_height">1dp</item>
</style>
<style name="BriarAvatar">
<item name="civ_border_width">@dimen/avatar_border_width</item>
<item name="civ_border_color">@color/briar_primary</item>
<!-- Remove when we are using 'de.hdodenhof:circleimageview:2.1.0' -->
<item name="civ_border_overlay">true</item>
</style>
<style name="NavMenuButton" parent="Widget.AppCompat.Button.Borderless.Colored">
<item name="android:textSize">@dimen/text_size_medium</item>
<item name="android:textColor">@android:color/tertiary_text_light</item>

View File

@@ -49,12 +49,6 @@
android:persistent="false"
android:title="@string/notify_private_messages_setting"/>
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_key_notify_forum_posts"
android:persistent="false"
android:title="@string/notify_forum_posts_setting"/>
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_key_notify_vibration"

View File

@@ -3,15 +3,6 @@ package org.briarproject.android;
import android.app.Activity;
import org.briarproject.android.contact.ConversationActivity;
import org.briarproject.android.forum.AvailableForumsActivity;
import org.briarproject.android.forum.ContactSelectorFragment;
import org.briarproject.android.forum.CreateForumActivity;
import org.briarproject.android.forum.ForumActivity;
import org.briarproject.android.forum.ForumSharingStatusActivity;
import org.briarproject.android.forum.ReadForumPostActivity;
import org.briarproject.android.forum.ShareForumActivity;
import org.briarproject.android.forum.ShareForumMessageFragment;
import org.briarproject.android.forum.WriteForumPostActivity;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.identity.CreateIdentityActivity;
import org.briarproject.android.introduction.IntroductionActivity;
@@ -52,20 +43,6 @@ public interface ActivityComponent {
void inject(CreateIdentityActivity activity);
void inject(AvailableForumsActivity activity);
void inject(WriteForumPostActivity activity);
void inject(CreateForumActivity activity);
void inject(ShareForumActivity activity);
void inject(ForumSharingStatusActivity activity);
void inject(ReadForumPostActivity activity);
void inject(ForumActivity activity);
void inject(SettingsActivity activity);
void inject(IntroductionActivity activity);
@@ -73,9 +50,6 @@ public interface ActivityComponent {
@Named("ContactListFragment")
BaseFragment newContactListFragment();
@Named("ForumListFragment")
BaseFragment newForumListFragment();
@Named("ChooseIdentityFragment")
BaseFragment newChooseIdentityFragment();
@@ -85,12 +59,6 @@ public interface ActivityComponent {
@Named("ContactChooserFragment")
BaseFragment newContactChooserFragment();
@Named("ContactSelectorFragment")
ContactSelectorFragment newContactSelectorFragment();
@Named("ShareForumMessageFragment")
ShareForumMessageFragment newShareForumMessageFragment();
@Named("IntroductionMessageFragment")
IntroductionMessageFragment newIntroductionMessageFragment();
}

View File

@@ -19,9 +19,6 @@ import org.briarproject.android.controller.PasswordControllerImpl;
import org.briarproject.android.controller.SetupController;
import org.briarproject.android.controller.SetupControllerImpl;
import org.briarproject.android.controller.TransportStateListener;
import org.briarproject.android.forum.ContactSelectorFragment;
import org.briarproject.android.forum.ForumListFragment;
import org.briarproject.android.forum.ShareForumMessageFragment;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.introduction.ContactChooserFragment;
import org.briarproject.android.introduction.IntroductionMessageFragment;
@@ -116,13 +113,6 @@ public class ActivityModule {
return new BriarServiceConnection();
}
@Provides
@Named("ForumListFragment")
BaseFragment provideForumListFragment(ForumListFragment fragment) {
fragment.setArguments(new Bundle());
return fragment;
}
@Provides
@Named("ContactListFragment")
BaseFragment provideContactListFragment(ContactListFragment fragment) {
@@ -153,22 +143,6 @@ public class ActivityModule {
return fragment;
}
@Provides
@Named("ContactSelectorFragment")
ContactSelectorFragment provideContactSelectorFragment(
ContactSelectorFragment fragment) {
fragment.setArguments(new Bundle());
return fragment;
}
@Provides
@Named("ShareForumMessageFragment")
ShareForumMessageFragment provideShareForumMessageFragment(
ShareForumMessageFragment fragment) {
fragment.setArguments(new Bundle());
return fragment;
}
@Provides
@Named("IntroductionMessageFragment")
IntroductionMessageFragment provideIntroductionMessageFragment(

View File

@@ -14,9 +14,6 @@ import org.briarproject.api.crypto.PasswordStrengthEstimator;
import org.briarproject.api.db.DatabaseConfig;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.introduction.IntroductionManager;
@@ -89,12 +86,6 @@ public interface AndroidComponent extends CoreEagerSingletons {
TransportPropertyManager transportPropertyManager();
ForumManager forumManager();
ForumSharingManager forumSharingManager();
ForumPostFactory forumPostFactory();
SettingsManager settingsManager();
ContactExchangeTask contactExchangeTask();

View File

@@ -13,20 +13,17 @@ import org.briarproject.R;
import org.briarproject.android.api.AndroidExecutor;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.contact.ConversationActivity;
import org.briarproject.android.forum.ForumActivity;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.IntroductionSucceededEvent;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.event.SettingsUpdatedEvent;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.lifecycle.Service;
import org.briarproject.api.lifecycle.ServiceException;
import org.briarproject.api.messaging.MessagingManager;
@@ -54,7 +51,6 @@ import static android.content.Context.NOTIFICATION_SERVICE;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.BriarActivity.GROUP_ID;
@@ -64,12 +60,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
Service, EventListener {
private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3;
private static final int FORUM_POST_NOTIFICATION_ID = 4;
private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 5;
private static final String CONTACT_URI =
"content://org.briarproject/contact";
private static final String FORUM_URI =
"content://org.briarproject/forum";
private static final Logger LOG =
Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
@@ -77,16 +70,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private final Executor dbExecutor;
private final SettingsManager settingsManager;
private final MessagingManager messagingManager;
private final ForumManager forumManager;
private final AndroidExecutor androidExecutor;
private final Context appContext;
// The following must only be accessed on the main UI thread
private final Map<GroupId, Integer> contactCounts = new HashMap<>();
private final Map<GroupId, Integer> forumCounts = new HashMap<>();
private final AtomicBoolean used = new AtomicBoolean(false);
private int contactTotal = 0, forumTotal = 0;
private int contactTotal = 0;
private int nextRequestId = 0;
private GroupId visibleGroup = null;
@@ -95,12 +86,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Inject
public AndroidNotificationManagerImpl(@DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager, MessagingManager messagingManager,
ForumManager forumManager, AndroidExecutor androidExecutor,
Application app) {
AndroidExecutor androidExecutor, Application app) {
this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager;
this.messagingManager = messagingManager;
this.forumManager = forumManager;
this.androidExecutor = androidExecutor;
appContext = app.getApplicationContext();
}
@@ -121,7 +110,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Override
public Void call() {
clearPrivateMessageNotification();
clearForumPostNotification();
clearIntroductionSuccessNotification();
return null;
}
@@ -139,12 +127,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
}
private void clearForumPostNotification() {
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.cancel(FORUM_POST_NOTIFICATION_ID);
}
private void clearIntroductionSuccessNotification() {
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
@@ -162,8 +144,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
ClientId c = m.getClientId();
if (c.equals(messagingManager.getClientId()))
showPrivateMessageNotification(m.getMessage().getGroupId());
else if (c.equals(forumManager.getClientId()))
showForumPostNotification(m.getMessage().getGroupId());
}
} else if (e instanceof IntroductionRequestReceivedEvent) {
ContactId c = ((IntroductionRequestReceivedEvent) e).getContactId();
@@ -174,9 +154,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} else if (e instanceof IntroductionSucceededEvent) {
Contact c = ((IntroductionSucceededEvent) e).getContact();
showIntroductionSucceededNotification(c);
} else if (e instanceof ForumInvitationReceivedEvent) {
ContactId c = ((ForumInvitationReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
}
}
@@ -282,82 +259,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
return defaults;
}
@Override
public void showForumPostNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
Integer count = forumCounts.get(g);
if (count == null) forumCounts.put(g, 1);
else forumCounts.put(g, count + 1);
forumTotal++;
if (!g.equals(visibleGroup))
updateForumPostNotification();
}
});
}
@Override
public void clearForumPostNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@Override
public void run() {
Integer count = forumCounts.remove(g);
if (count == null) return; // Already cleared
forumTotal -= count;
// FIXME: If the notification isn't showing, this may show it
updateForumPostNotification();
}
});
}
private void updateForumPostNotification() {
if (forumTotal == 0) {
clearForumPostNotification();
} else if (settings.getBoolean("notifyForumPosts", true)) {
NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext);
b.setSmallIcon(R.drawable.message_notification_icon);
b.setContentTitle(appContext.getText(R.string.app_name));
b.setContentText(appContext.getResources().getQuantityString(
R.plurals.forum_post_notification_text, forumTotal,
forumTotal));
String ringtoneUri = settings.get("notifyRingtoneUri");
if (!StringUtils.isNullOrEmpty(ringtoneUri))
b.setSound(Uri.parse(ringtoneUri));
b.setDefaults(getDefaults());
b.setOnlyAlertOnce(true);
b.setAutoCancel(true);
if (forumCounts.size() == 1) {
Intent i = new Intent(appContext, ForumActivity.class);
GroupId g = forumCounts.keySet().iterator().next();
i.putExtra(GROUP_ID, g.getBytes());
String idHex = StringUtils.toHexString(g.getBytes());
i.setData(Uri.parse(FORUM_URI + "/" + idHex));
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(ForumActivity.class);
t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
} else {
Intent i = new Intent(appContext, NavDrawerActivity.class);
i.putExtra(NavDrawerActivity.INTENT_FORUMS, true);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
TaskStackBuilder t = TaskStackBuilder.create(appContext);
t.addParentStack(NavDrawerActivity.class);
t.addNextIntent(i);
b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
}
if (Build.VERSION.SDK_INT >= 21) {
b.setCategory(CATEGORY_SOCIAL);
b.setVisibility(VISIBILITY_SECRET);
}
Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.notify(FORUM_POST_NOTIFICATION_ID, b.build());
}
}
@Override
public void blockNotification(final GroupId g) {
androidExecutor.execute(new Runnable() {
@@ -423,5 +324,4 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}
});
}
}

View File

@@ -8,7 +8,6 @@ import android.support.v7.app.AlertDialog;
import org.briarproject.R;
import org.briarproject.android.contact.ContactListFragment;
import org.briarproject.android.forum.ForumListFragment;
import org.briarproject.android.fragment.BaseFragment;
/**
@@ -24,8 +23,6 @@ public abstract class BriarFragmentActivity extends BriarActivity {
if (fragmentTag.equals(ContactListFragment.TAG)) {
actionBar.setTitle(R.string.contacts_toolbar_header);
} else if (fragmentTag.equals(ForumListFragment.TAG)) {
actionBar.setTitle(R.string.forums_toolbar_header);
}
}

View File

@@ -1,12 +1,17 @@
package org.briarproject.android;
import static android.view.Gravity.CENTER;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import org.briarproject.R;
import org.briarproject.android.util.LayoutUtils;
import android.app.Activity;
import android.os.Bundle;
import org.briarproject.R;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
import android.widget.LinearLayout;
import android.widget.TextView;
public class ExpiredActivity extends Activity {
@@ -16,6 +21,19 @@ public class ExpiredActivity extends Activity {
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
setContentView(R.layout.activity_expired);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setGravity(CENTER);
int pad = LayoutUtils.getPadding(this);
TextView warning = new TextView(this);
warning.setGravity(CENTER);
warning.setTextSize(18);
warning.setPadding(pad, pad, pad, pad);
warning.setText(R.string.expiry_warning);
layout.addView(warning);
setContentView(layout);
}
}

View File

@@ -43,7 +43,6 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
public final static String PREF_SEEN_WELCOME_MESSAGE = "welcome_message";
public static final String INTENT_CONTACTS = "intent_contacts";
public static final String INTENT_FORUMS = "intent_forums";
private static final String KEY_CURRENT_FRAGMENT_ID = "key_current_id";
@@ -70,9 +69,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
exitIfStartupFailed(intent);
checkAuthorHandle(intent);
clearBackStack();
if (intent.getBooleanExtra(INTENT_FORUMS, false))
startFragment(activityComponent.newForumListFragment());
else if (intent.getBooleanExtra(INTENT_CONTACTS, false))
if (intent.getBooleanExtra(INTENT_CONTACTS, false))
startFragment(activityComponent.newContactListFragment());
}
@@ -171,9 +168,6 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
case R.id.nav_btn_contacts:
startFragment(activityComponent.newContactListFragment());
break;
case R.id.nav_btn_forums:
startFragment(activityComponent.newForumListFragment());
break;
case R.id.nav_btn_settings:
startActivity(new Intent(this, SettingsActivity.class));
break;

View File

@@ -2,17 +2,13 @@ package org.briarproject.android.api;
import org.briarproject.api.sync.GroupId;
/** Manages notifications for private messages and forum posts. */
/** Manages notifications for private messages and introductions. */
public interface AndroidNotificationManager {
void showPrivateMessageNotification(GroupId g);
void clearPrivateMessageNotification(GroupId g);
void showForumPostNotification(GroupId g);
void clearForumPostNotification(GroupId g);
void blockNotification(GroupId g);
void unblockNotification(GroupId g);

View File

@@ -1,7 +1,6 @@
package org.briarproject.android.contact;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.view.View;
@@ -12,7 +11,6 @@ import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.identity.Author;
import org.briarproject.util.StringUtils;
import java.util.List;
@@ -50,9 +48,6 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
if (listener != null) listener.onItemClick(ui.avatar, item);
}
});
ViewCompat.setTransitionName(ui.avatar, "avatar" +
StringUtils.toHexString(item.getGroupId().getBytes()));
}
@Override

View File

@@ -2,7 +2,6 @@ package org.briarproject.android.contact;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -11,7 +10,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.util.StringUtils;
public class ContactListAdapter
extends BaseContactListAdapter<ContactListAdapter.ContactHolder> {
@@ -34,13 +32,18 @@ public class ContactListAdapter
ContactListItem item = getItem(position);
// unread count
// name and unread count
String contactName = item.getContact().getAuthor().getName();
int unread = item.getUnreadCount();
if (unread > 0) {
ui.unread.setText(String.valueOf(unread));
ui.unread.setVisibility(View.VISIBLE);
// TODO show these in a bubble on top of the avatar
ui.name.setText(contactName + " (" + unread + ")");
// different background for contacts with unread messages
ui.layout.setBackgroundColor(
ContextCompat.getColor(ctx, R.color.unread_background));
} else {
ui.unread.setVisibility(View.INVISIBLE);
ui.name.setText(contactName);
}
// date of last message
@@ -59,16 +62,12 @@ public class ContactListAdapter
} else {
ui.bulb.setImageResource(R.drawable.contact_disconnected);
}
ViewCompat.setTransitionName(ui.bulb,
"bulb" + StringUtils.toHexString(item.getGroupId().getBytes()));
}
protected static class ContactHolder
extends BaseContactListAdapter.BaseContactHolder {
public final ImageView bulb;
public final TextView unread;
public final TextView date;
public final TextView identity;
@@ -76,7 +75,6 @@ public class ContactListAdapter
super(v);
bulb = (ImageView) v.findViewById(R.id.bulbView);
unread = (TextView) v.findViewById(R.id.unreadCountView);
date = (TextView) v.findViewById(R.id.dateView);
identity = (TextView) v.findViewById(R.id.identityView);
}

View File

@@ -1,13 +1,11 @@
package org.briarproject.android.contact;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.util.Pair;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,8 +29,6 @@ import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.introduction.IntroductionManager;
@@ -50,7 +46,6 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.BriarActivity.GROUP_ID;
@@ -79,8 +74,6 @@ public class ContactListFragment extends BaseFragment implements EventListener {
protected volatile MessagingManager messagingManager;
@Inject
protected volatile IntroductionManager introductionManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
public ContactListFragment() {
@@ -110,22 +103,16 @@ public class ContactListFragment extends BaseFragment implements EventListener {
ConversationActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
ContactListAdapter.ContactHolder holder =
(ContactListAdapter.ContactHolder) list
.getRecyclerView()
.findViewHolderForAdapterPosition(
adapter.findItemPosition(item));
Pair<View, String> avatar =
Pair.create((View) holder.avatar, ViewCompat
.getTransitionName(holder.avatar));
Pair<View, String> bulb =
Pair.create((View) holder.bulb, ViewCompat
.getTransitionName(holder.bulb));
ActivityOptionsCompat options =
makeSceneTransitionAnimation(getActivity(),
avatar, bulb);
ActivityCompat.startActivity(getActivity(), i,
options.toBundle());
if (Build.VERSION.SDK_INT >= 16) {
ActivityOptionsCompat options =
ActivityOptionsCompat.
makeSceneTransitionAnimation(
getActivity(),
view, "avatar");
getActivity().startActivity(i, options.toBundle());
} else {
startActivity(i);
}
}
};
@@ -234,8 +221,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
MessageValidatedEvent m = (MessageValidatedEvent) e;
ClientId c = m.getClientId();
if (m.isValid() && (c.equals(messagingManager.getClientId()) ||
c.equals(introductionManager.getClientId()) ||
c.equals(forumSharingManager.getClientId()))) {
c.equals(introductionManager.getClientId()))) {
LOG.info("Message added, reloading");
reloadConversation(m.getMessage().getGroupId());
}
@@ -329,16 +315,6 @@ public class ContactListFragment extends BaseFragment implements EventListener {
if (LOG.isLoggable(INFO))
LOG.info("Loading introduction messages took " + duration + " ms");
now = System.currentTimeMillis();
Collection<ForumInvitationMessage> invitations =
forumSharingManager.getForumInvitationMessages(id);
for (ForumInvitationMessage i : invitations) {
messages.add(ConversationItem.from(i));
}
duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading forum invitations took " + duration + " ms");
return messages;
}
}

View File

@@ -30,7 +30,7 @@ public class ContactListItem {
}
void setMessages(Collection<ConversationItem> messages) {
empty = messages == null || messages.isEmpty();
empty = messages.isEmpty();
timestamp = 0;
unread = 0;
if (!empty) {

View File

@@ -6,7 +6,6 @@ import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
@@ -44,14 +43,11 @@ import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.event.MessagesAckedEvent;
import org.briarproject.api.event.MessagesSentEvent;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.introduction.IntroductionManager;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
@@ -123,8 +119,6 @@ public class ConversationActivity extends BriarActivity
protected volatile PrivateMessageFactory privateMessageFactory;
@Inject
protected volatile IntroductionManager introductionManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
private volatile GroupId groupId = null;
private volatile ContactId contactId = null;
@@ -157,10 +151,6 @@ public class ConversationActivity extends BriarActivity
ab.setDisplayShowTitleEnabled(false);
}
String hexGroupId = StringUtils.toHexString(b);
ViewCompat.setTransitionName(toolbarAvatar, "avatar" + hexGroupId);
ViewCompat.setTransitionName(toolbarStatus, "bulb" + hexGroupId);
adapter = new ConversationAdapter(this, this);
list = (BriarRecyclerView) findViewById(R.id.conversationView);
list.setLayoutManager(new LinearLayoutManager(this));
@@ -212,7 +202,7 @@ public class ConversationActivity extends BriarActivity
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
supportFinishAfterTransition();
return true;
case R.id.action_introduction:
if (contactId == null) return false;
@@ -232,13 +222,6 @@ public class ConversationActivity extends BriarActivity
}
}
@Override
public void onBackPressed() {
// FIXME disabled exit transition, because it doesn't work for some reason
//supportFinishAfterTransition();
finish();
}
private void loadData() {
runOnDbThread(new Runnable() {
@Override
@@ -309,13 +292,10 @@ public class ConversationActivity extends BriarActivity
Collection<IntroductionMessage> introductions =
introductionManager
.getIntroductionMessages(contactId);
Collection<ForumInvitationMessage> invitations =
forumSharingManager
.getForumInvitationMessages(contactId);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading headers took " + duration + " ms");
displayMessages(headers, introductions, invitations);
displayMessages(headers, introductions);
} catch (NoSuchContactException e) {
finishOnUiThread();
} catch (DbException e) {
@@ -327,14 +307,12 @@ public class ConversationActivity extends BriarActivity
}
private void displayMessages(final Collection<PrivateMessageHeader> headers,
final Collection<IntroductionMessage> introductions,
final Collection<ForumInvitationMessage> invitations) {
final Collection<IntroductionMessage> introductions) {
runOnUiThread(new Runnable() {
@Override
public void run() {
sendButton.setEnabled(true);
if (headers.isEmpty() && introductions.isEmpty() &&
invitations.isEmpty()) {
if (headers.isEmpty() && introductions.isEmpty()) {
// we have no messages,
// so let the list know to hide progress bar
list.showData();
@@ -362,10 +340,6 @@ public class ConversationActivity extends BriarActivity
}
items.add(item);
}
for (ForumInvitationMessage i : invitations) {
ConversationItem item = ConversationItem.from(i);
items.add(item);
}
adapter.addAll(items);
// Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1);
@@ -519,12 +493,6 @@ public class ConversationActivity extends BriarActivity
ConversationItem.from(this, contactName, ir);
addIntroduction(item);
}
} else if (e instanceof ForumInvitationReceivedEvent) {
ForumInvitationReceivedEvent event =
(ForumInvitationReceivedEvent) e;
if (event.getContactId().equals(contactId)) {
loadMessages();
}
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.android.contact;
import android.content.Context;
import android.content.Intent;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
@@ -14,9 +13,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.forum.AvailableForumsActivity;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.util.StringUtils;
@@ -25,8 +22,6 @@ import java.util.List;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.support.v7.widget.RecyclerView.ViewHolder;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_IN;
import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_OUT;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_IN;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_OUT;
import static org.briarproject.android.contact.ConversationItem.IncomingItem;
@@ -87,14 +82,6 @@ class ConversationAdapter extends RecyclerView.Adapter {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_notice_out, viewGroup, false);
return new NoticeHolder(v, type);
} else if (type == FORUM_INVITATION_IN) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_forum_invitation_in, viewGroup, false);
return new InvitationHolder(v, type);
} else if (type == FORUM_INVITATION_OUT) {
v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_forum_invitation_out, viewGroup, false);
return new InvitationHolder(v, type);
}
// incoming message (non-local)
else {
@@ -119,12 +106,6 @@ class ConversationAdapter extends RecyclerView.Adapter {
bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item);
} else if (item instanceof ConversationNoticeInItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item);
} else if (item instanceof ConversationForumInvitationOutItem) {
bindInvitation((InvitationHolder) ui,
(ConversationForumInvitationOutItem) item);
} else if (item instanceof ConversationForumInvitationInItem) {
bindInvitation((InvitationHolder) ui,
(ConversationForumInvitationInItem) item);
} else {
throw new IllegalArgumentException("Unhandled Conversation Item");
}
@@ -276,65 +257,6 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
}
private void bindInvitation(InvitationHolder ui,
final ConversationForumInvitationItem item) {
ForumInvitationMessage fim = item.getForumInvitationMessage();
String message = fim.getMessage();
if (StringUtils.isNullOrEmpty(message)) {
ui.messageLayout.setVisibility(View.GONE);
} else {
ui.messageLayout.setVisibility(View.VISIBLE);
ui.message.body.setText(message);
ui.message.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
}
// Outgoing Invitation
if (item instanceof ConversationForumInvitationOutItem) {
ui.text.setText(ctx.getString(R.string.forum_invitation_sent,
fim.getForumName(), contactName));
ConversationForumInvitationOutItem i =
(ConversationForumInvitationOutItem) item;
if (i.isSeen()) {
ui.status.setImageResource(R.drawable.message_delivered);
ui.message.status.setImageResource(
R.drawable.message_delivered_white);
} else if (i.isSent()) {
ui.status.setImageResource(R.drawable.message_sent);
ui.message.status.setImageResource(
R.drawable.message_sent_white);
} else {
ui.status.setImageResource(R.drawable.message_stored);
ui.message.status.setImageResource(
R.drawable.message_stored_white);
}
}
// Incoming Invitation
else {
ui.text.setText(ctx.getString(R.string.forum_invitation_received,
contactName, fim.getForumName()));
if (fim.isAvailable()) {
ui.showForumsButton.setVisibility(View.VISIBLE);
ui.showForumsButton
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ctx,
AvailableForumsActivity.class);
ctx.startActivity(intent);
}
});
} else {
ui.showForumsButton.setVisibility(View.GONE);
}
}
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, item.getTime()));
}
@Override
public int getItemCount() {
return items.size();
@@ -473,33 +395,6 @@ class ConversationAdapter extends RecyclerView.Adapter {
}
}
private static class InvitationHolder extends RecyclerView.ViewHolder {
private final View messageLayout;
private final MessageHolder message;
private final TextView text;
private final Button showForumsButton;
private final TextView date;
private final ImageView status;
public InvitationHolder(View v, int type) {
super(v);
messageLayout = v.findViewById(R.id.messageLayout);
message = new MessageHolder(messageLayout,
type == FORUM_INVITATION_IN ? MSG_IN : MSG_OUT);
text = (TextView) v.findViewById(R.id.introductionText);
showForumsButton = (Button) v.findViewById(R.id.showForumsButton);
date = (TextView) v.findViewById(R.id.introductionTime);
if (type == FORUM_INVITATION_OUT) {
status = (ImageView) v.findViewById(R.id.introductionStatus);
} else {
status = null;
}
}
}
private class ListCallbacks extends SortedList.Callback<ConversationItem> {
@Override

View File

@@ -1,32 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationMessage;
// This class is not thread-safe
public class ConversationForumInvitationInItem
extends ConversationForumInvitationItem
implements ConversationItem.IncomingItem {
private boolean read;
public ConversationForumInvitationInItem(ForumInvitationMessage fim) {
super(fim);
this.read = fim.isRead();
}
@Override
int getType() {
return FORUM_INVITATION_IN;
}
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -1,18 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationMessage;
abstract class ConversationForumInvitationItem extends ConversationItem {
private final ForumInvitationMessage fim;
public ConversationForumInvitationItem(ForumInvitationMessage fim) {
super(fim.getId(), fim.getTimestamp());
this.fim = fim;
}
public ForumInvitationMessage getForumInvitationMessage() {
return fim;
}
}

View File

@@ -1,48 +0,0 @@
package org.briarproject.android.contact;
import org.briarproject.api.forum.ForumInvitationMessage;
/**
* This class is needed and can not be replaced by an ConversationNoticeOutItem,
* because it carries the optional invitation message
* to be displayed as a regular private message.
* <p/>
* This class is not thread-safe
*/
public class ConversationForumInvitationOutItem
extends ConversationForumInvitationItem
implements ConversationItem.OutgoingItem {
private boolean sent, seen;
public ConversationForumInvitationOutItem(ForumInvitationMessage fim) {
super(fim);
this.sent = fim.isSent();
this.seen = fim.isSeen();
}
@Override
int getType() {
return FORUM_INVITATION_OUT;
}
@Override
public boolean isSent() {
return sent;
}
@Override
public void setSent(boolean sent) {
this.sent = sent;
}
@Override
public boolean isSeen() {
return seen;
}
@Override
public void setSeen(boolean seen) {
this.seen = seen;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.android.contact;
import android.content.Context;
import org.briarproject.R;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.introduction.IntroductionResponse;
@@ -21,8 +20,6 @@ public abstract class ConversationItem {
final static int INTRODUCTION_OUT = 4;
final static int NOTICE_IN = 5;
final static int NOTICE_OUT = 6;
final static int FORUM_INVITATION_IN = 7;
final static int FORUM_INVITATION_OUT = 8;
private MessageId id;
private long time;
@@ -95,14 +92,6 @@ public abstract class ConversationItem {
}
}
public static ConversationItem from(ForumInvitationMessage fim) {
if (fim.isLocal()) {
return new ConversationForumInvitationOutItem(fim);
} else {
return new ConversationForumInvitationInItem(fim);
}
}
/**
* This method should not be used to get user-facing objects,
* Its purpose is to provider data for the contact list.

View File

@@ -1,182 +0,0 @@
package org.briarproject.android.forum;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.GroupAddedEvent;
import org.briarproject.api.event.GroupRemovedEvent;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.AvailableForumsAdapter.AvailableForumClickListener;
public class AvailableForumsActivity extends BriarActivity
implements EventListener, AvailableForumClickListener {
private static final Logger LOG =
Logger.getLogger(AvailableForumsActivity.class.getName());
private AvailableForumsAdapter adapter;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumManager forumManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile EventBus eventBus;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_available_forums);
adapter = new AvailableForumsAdapter(this, this);
BriarRecyclerView list =
(BriarRecyclerView) findViewById(R.id.availableForumsView);
list.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapter);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onResume() {
super.onResume();
eventBus.addListener(this);
loadForums();
}
private void loadForums() {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Collection<ForumContacts> available = new ArrayList<>();
long now = System.currentTimeMillis();
for (Forum f : forumSharingManager.getAvailableForums()) {
try {
Collection<Contact> c =
forumSharingManager.getSharedBy(f.getId());
available.add(new ForumContacts(f, c));
} catch (NoSuchGroupException e) {
// Continue
}
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayForums(available);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayForums(final Collection<ForumContacts> available) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (available.isEmpty()) {
LOG.info("No forums available, finishing");
finish();
} else {
adapter.clear();
List<AvailableForumsItem> list =
new ArrayList<>(available.size());
for (ForumContacts f : available)
list.add(new AvailableForumsItem(f));
adapter.addAll(list);
}
}
});
}
@Override
public void onPause() {
super.onPause();
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, reloading");
loadForums();
} else if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
LOG.info("Forum added, reloading");
loadForums();
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
LOG.info("Forum removed, reloading");
loadForums();
}
} else if (e instanceof ForumInvitationReceivedEvent) {
LOG.info("Available forums updated, reloading");
loadForums();
}
}
@Override
public void onItemClick(AvailableForumsItem item, boolean accept) {
respondToInvitation(item, accept);
// show toast
int res = R.string.forum_declined_toast;
if (accept) res = R.string.forum_joined_toast;
Toast.makeText(this, res, LENGTH_SHORT).show();
}
private void respondToInvitation(final AvailableForumsItem item,
final boolean accept) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
Forum f = item.getForum();
for (Contact c : item.getContacts()) {
forumSharingManager.respondToInvitation(f, c, accept);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
loadForums();
}
});
}
}

View File

@@ -1,161 +0,0 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.TextAvatarView;
import org.briarproject.api.contact.Contact;
import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
class AvailableForumsAdapter extends
RecyclerView.Adapter<AvailableForumsAdapter.AvailableForumViewHolder> {
private final Context ctx;
private final AvailableForumClickListener listener;
private final SortedList<AvailableForumsItem> forums =
new SortedList<>(AvailableForumsItem.class,
new SortedListCallBacks());
AvailableForumsAdapter(Context ctx, AvailableForumClickListener listener) {
this.ctx = ctx;
this.listener = listener;
}
@Override
public AvailableForumViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(ctx)
.inflate(R.layout.list_item_available_forum, parent, false);
return new AvailableForumViewHolder(v);
}
@Override
public void onBindViewHolder(AvailableForumViewHolder ui, int position) {
final AvailableForumsItem item = getItem(position);
ui.avatar.setText(item.getForum().getName().substring(0, 1));
ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes());
ui.name.setText(item.getForum().getName());
Collection<String> names = new ArrayList<>();
for (Contact c : item.getContacts()) names.add(c.getAuthor().getName());
ui.sharedBy.setText(ctx.getString(R.string.shared_by_format,
StringUtils.join(names, ", ")));
ui.accept.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(item, true);
}
});
ui.decline.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onItemClick(item, false);
}
});
}
@Override
public int getItemCount() {
return forums.size();
}
public AvailableForumsItem getItem(int position) {
return forums.get(position);
}
public void add(AvailableForumsItem item) {
forums.add(item);
}
public void addAll(Collection<AvailableForumsItem> list) {
forums.addAll(list);
}
public void clear() {
forums.clear();
}
protected static class AvailableForumViewHolder
extends RecyclerView.ViewHolder {
private final TextAvatarView avatar;
private final TextView name;
private final TextView sharedBy;
private final Button accept;
private final Button decline;
public AvailableForumViewHolder(View v) {
super(v);
avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
name = (TextView) v.findViewById(R.id.forumNameView);
sharedBy = (TextView) v.findViewById(R.id.sharedByView);
accept = (Button) v.findViewById(R.id.acceptButton);
decline = (Button) v.findViewById(R.id.declineButton);
}
}
private class SortedListCallBacks
extends SortedList.Callback<AvailableForumsItem> {
@Override
public int compare(AvailableForumsItem o1,
AvailableForumsItem o2) {
return String.CASE_INSENSITIVE_ORDER
.compare(o1.getForum().getName(),
o2.getForum().getName());
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(AvailableForumsItem oldItem,
AvailableForumsItem newItem) {
return oldItem.getForum().equals(newItem.getForum()) &&
oldItem.getContacts().equals(newItem.getContacts());
}
@Override
public boolean areItemsTheSame(AvailableForumsItem oldItem,
AvailableForumsItem newItem) {
return oldItem.getForum().equals(newItem.getForum());
}
}
interface AvailableForumClickListener {
void onItemClick(AvailableForumsItem item, boolean accept);
}
}

View File

@@ -1,23 +0,0 @@
package org.briarproject.android.forum;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.forum.Forum;
import java.util.Collection;
class AvailableForumsItem {
private final ForumContacts forumContacts;
AvailableForumsItem(ForumContacts forumContacts) {
this.forumContacts = forumContacts;
}
Forum getForum() {
return forumContacts.getForum();
}
Collection<Contact> getContacts() {
return forumContacts.getContacts();
}
}

View File

@@ -1,103 +0,0 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.api.contact.ContactId;
import java.util.ArrayList;
import java.util.Collection;
public class ContactSelectorAdapter
extends BaseContactListAdapter<ContactSelectorAdapter.SelectableContactHolder> {
public ContactSelectorAdapter(Context context,
OnItemClickListener listener) {
super(context, listener);
}
@Override
public SelectableContactHolder onCreateViewHolder(ViewGroup viewGroup,
int i) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_selectable_contact, viewGroup, false);
return new SelectableContactHolder(v);
}
@Override
public void onBindViewHolder(SelectableContactHolder ui, int position) {
super.onBindViewHolder(ui, position);
SelectableContactListItem item =
(SelectableContactListItem) getItem(position);
if (item.isSelected()) {
ui.checkBox.setChecked(true);
} else {
ui.checkBox.setChecked(false);
}
if (item.isDisabled()) {
// we share this forum already with that contact
ui.layout.setEnabled(false);
grayOutItem(ui);
}
}
public Collection<ContactId> getSelectedContactIds() {
Collection<ContactId> selected = new ArrayList<>();
for (int i = 0; i < contacts.size(); i++) {
SelectableContactListItem item =
(SelectableContactListItem) contacts.get(i);
if (item.isSelected()) selected.add(item.getContact().getId());
}
return selected;
}
protected static class SelectableContactHolder
extends BaseContactListAdapter.BaseContactHolder {
private final CheckBox checkBox;
public SelectableContactHolder(View v) {
super(v);
checkBox = (CheckBox) v.findViewById(R.id.checkBox);
}
}
@Override
public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
return compareByName(c1, c2);
}
private void grayOutItem(final SelectableContactHolder ui) {
if (Build.VERSION.SDK_INT >= 11) {
float alpha = 0.25f;
ui.avatar.setAlpha(alpha);
ui.name.setAlpha(alpha);
ui.checkBox.setAlpha(alpha);
} else {
ColorFilter colorFilter = new PorterDuffColorFilter(Color.GRAY,
PorterDuff.Mode.MULTIPLY);
ui.avatar.setColorFilter(colorFilter);
ui.name.setEnabled(false);
ui.checkBox.setEnabled(false);
}
}
}

View File

@@ -1,239 +0,0 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.transition.Fade;
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 org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
public class ContactSelectorFragment extends BaseFragment implements
BaseContactListAdapter.OnItemClickListener {
public final static String TAG = "ContactSelectorFragment";
private static final Logger LOG =
Logger.getLogger(ContactSelectorFragment.class.getName());
private ShareForumActivity shareForumActivity;
private Menu menu;
private BriarRecyclerView list;
private ContactSelectorAdapter adapter;
private Collection<ContactId> selectedContacts;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ContactManager contactManager;
@Inject
protected volatile IdentityManager identityManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
protected volatile GroupId groupId;
public void initBundle(GroupId groupId) {
Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, groupId.getBytes());
setArguments(bundle);
}
@Inject
public ContactSelectorFragment() {
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
shareForumActivity = (ShareForumActivity) context;
} catch (ClassCastException e) {
throw new InstantiationError(
"This fragment is only meant to be attached to the ShareForumActivity");
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
if (groupId == null) throw new IllegalStateException("No GroupId");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View contentView = inflater.inflate(
R.layout.introduction_contact_chooser, container, false);
if (Build.VERSION.SDK_INT >= 21) {
setExitTransition(new Fade());
}
adapter = new ContactSelectorAdapter(getActivity(), this);
list = (BriarRecyclerView) contentView.findViewById(R.id.contactList);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_contacts));
// restore selected contacts if available
if (savedInstanceState != null) {
ArrayList<Integer> intContacts =
savedInstanceState.getIntegerArrayList(CONTACTS);
selectedContacts = ShareForumActivity.getContactsFromIntegers(
intContacts);
}
return contentView;
}
@Override
public void onResume() {
super.onResume();
if (selectedContacts != null)
loadContacts(Collections.unmodifiableCollection(selectedContacts));
else loadContacts(null);
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (adapter != null) {
selectedContacts = adapter.getSelectedContactIds();
outState.putIntegerArrayList(CONTACTS,
getContactsFromIds(selectedContacts));
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.forum_share_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
this.menu = menu;
// hide sharing action initially, if no contact is selected
updateMenuItem();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
shareForumActivity.onBackPressed();
return true;
case R.id.action_share_forum:
selectedContacts = adapter.getSelectedContactIds();
shareForumActivity.showMessageScreen(groupId, selectedContacts);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void onItemClick(View view, ContactListItem item) {
((SelectableContactListItem) item).toggleSelected();
adapter.notifyItemChanged(adapter.findItemPosition(item), item);
updateMenuItem();
}
private void loadContacts(final Collection<ContactId> selection) {
shareForumActivity.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();
List<ContactListItem> contacts = new ArrayList<>();
for (Contact c : contactManager.getActiveContacts()) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
// was this contact already selected?
boolean selected = selection != null &&
selection.contains(c.getId());
// do we have already some sharing with that contact?
boolean disabled =
!forumSharingManager.canBeShared(groupId, c);
contacts.add(new SelectableContactListItem(c,
localAuthor, groupId, selected, disabled));
}
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayContacts(Collections.unmodifiableList(contacts));
} catch (DbException e) {
displayContacts(Collections.<ContactListItem>emptyList());
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayContacts(final List<ContactListItem> contacts) {
shareForumActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (!contacts.isEmpty()) adapter.addAll(contacts);
else list.showData();
updateMenuItem();
}
});
}
private void updateMenuItem() {
if (menu == null) return;
MenuItem item = menu.findItem(R.id.action_share_forum);
if (item == null) return;
selectedContacts = adapter.getSelectedContactIds();
if (selectedContacts.size() > 0) {
item.setVisible(true);
} else {
item.setVisible(false);
}
}
}

View File

@@ -1,161 +0,0 @@
package org.briarproject.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.util.StringUtils;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
public class CreateForumActivity extends BriarActivity
implements OnEditorActionListener, OnClickListener {
private static final Logger LOG =
Logger.getLogger(CreateForumActivity.class.getName());
private EditText nameEntry;
private Button createForumButton;
private ProgressBar progress;
private TextView feedback;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumManager forumManager;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_create_forum);
nameEntry = (EditText) findViewById(R.id.createForumNameEntry);
TextWatcher nameEntryWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
enableOrDisableCreateButton();
}
};
nameEntry.setOnEditorActionListener(this);
nameEntry.addTextChangedListener(nameEntryWatcher);
feedback = (TextView) findViewById(R.id.createForumFeedback);
createForumButton = (Button) findViewById(R.id.createForumButton);
createForumButton.setOnClickListener(this);
progress = (ProgressBar) findViewById(R.id.createForumProgressBar);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
private void enableOrDisableCreateButton() {
if (progress == null) return; // Not created yet
createForumButton.setEnabled(validateName());
}
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
hideSoftKeyboard(textView);
return true;
}
private boolean validateName() {
String name = nameEntry.getText().toString();
int length = StringUtils.toUtf8(name).length;
if (length > MAX_FORUM_NAME_LENGTH) {
feedback.setText(R.string.name_too_long);
return false;
}
feedback.setText("");
return length > 0;
}
@Override
public void onClick(View view) {
if (view == createForumButton) {
hideSoftKeyboard(view);
if (!validateName()) return;
createForumButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
storeForum(nameEntry.getText().toString());
}
}
private void storeForum(final String name) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();
Forum f = forumManager.addForum(name);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Storing forum took " + duration + " ms");
displayForum(f);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
finishOnUiThread();
}
}
});
}
private void displayForum(final Forum f) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Intent i = new Intent(CreateForumActivity.this,
ForumActivity.class);
i.putExtra(GROUP_ID, f.getId().getBytes());
i.putExtra(FORUM_NAME, f.getName());
startActivity(i);
Toast.makeText(CreateForumActivity.this,
R.string.forum_created_toast, LENGTH_LONG).show();
finish();
}
});
}
}

View File

@@ -1,448 +0,0 @@
package org.briarproject.android.forum;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.AlertDialog;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.util.ListLoadingProgressBar;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.GroupRemovedEvent;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.LinearLayout.VERTICAL;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ReadForumPostActivity.RESULT_PREV_NEXT;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
public class ForumActivity extends BriarActivity implements EventListener,
OnItemClickListener {
public static final String FORUM_NAME = "briar.FORUM_NAME";
public static final String MIN_TIMESTAMP = "briar.MIN_TIMESTAMP";
private static final int REQUEST_READ = 2;
private static final int REQUEST_FORUM_SHARED = 3;
private static final Logger LOG =
Logger.getLogger(ForumActivity.class.getName());
@Inject protected AndroidNotificationManager notificationManager;
private Map<MessageId, byte[]> bodyCache = new HashMap<>();
private TextView empty = null;
private ForumAdapter adapter = null;
private ListView list = null;
private ListLoadingProgressBar loading = null;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile ForumManager forumManager;
@Inject protected volatile EventBus eventBus;
private volatile GroupId groupId = null;
private volatile Forum forum = null;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException();
groupId = new GroupId(b);
String forumName = i.getStringExtra(FORUM_NAME);
if (forumName != null) setTitle(forumName);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
empty = new TextView(this);
empty.setLayoutParams(MATCH_WRAP_1);
empty.setGravity(CENTER);
empty.setTextSize(18);
empty.setText(R.string.no_forum_posts);
empty.setVisibility(GONE);
layout.addView(empty);
adapter = new ForumAdapter(this);
list = new ListView(this);
list.setLayoutParams(MATCH_WRAP_1);
list.setAdapter(adapter);
list.setOnItemClickListener(this);
list.setVisibility(GONE);
layout.addView(list);
// Show a progress bar while the list is loading
loading = new ListLoadingProgressBar(this);
layout.addView(loading);
setContentView(layout);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onResume() {
super.onResume();
eventBus.addListener(this);
notificationManager.blockNotification(groupId);
notificationManager.clearForumPostNotification(groupId);
loadForum();
loadHeaders();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.forum_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
ActivityOptionsCompat options = ActivityOptionsCompat
.makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_forum_compose_post:
Intent i = new Intent(this, WriteForumPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(FORUM_NAME, forum.getName());
i.putExtra(MIN_TIMESTAMP, getMinTimestampForNewPost());
startActivity(i);
return true;
case R.id.action_forum_share:
Intent i2 = new Intent(this, ShareForumActivity.class);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i2.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat
.startActivityForResult(this, i2, REQUEST_FORUM_SHARED,
options.toBundle());
return true;
case R.id.action_forum_sharing_status:
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i3.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat.startActivity(this, i3, options.toBundle());
return true;
case R.id.action_forum_delete:
showUnsubscribeDialog();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void loadForum() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
forum = forumManager.getForum(groupId);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading forum " + duration + " ms");
displayForumName();
} catch (NoSuchGroupException e) {
finishOnUiThread();
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayForumName() {
runOnUiThread(new Runnable() {
public void run() {
setTitle(forum.getName());
}
});
}
private void loadHeaders() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
Collection<ForumPostHeader> headers =
forumManager.getPostHeaders(groupId);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayHeaders(headers);
} catch (NoSuchGroupException e) {
finishOnUiThread();
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayHeaders(final Collection<ForumPostHeader> headers) {
runOnUiThread(new Runnable() {
public void run() {
loading.setVisibility(GONE);
adapter.clear();
if (headers.isEmpty()) {
empty.setVisibility(VISIBLE);
list.setVisibility(GONE);
} else {
empty.setVisibility(GONE);
list.setVisibility(VISIBLE);
for (ForumPostHeader h : headers) {
ForumItem item = new ForumItem(h);
byte[] body = bodyCache.get(h.getId());
if (body == null) loadPostBody(h);
else item.setBody(body);
adapter.add(item);
}
adapter.sort(ForumItemComparator.INSTANCE);
// Scroll to the bottom
list.setSelection(adapter.getCount() - 1);
}
}
});
}
private void loadPostBody(final ForumPostHeader h) {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
byte[] body = forumManager.getPostBody(h.getId());
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading message took " + duration + " ms");
displayPost(h.getId(), body);
} catch (NoSuchMessageException e) {
// The item will be removed when we get the event
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayPost(final MessageId m, final byte[] body) {
runOnUiThread(new Runnable() {
public void run() {
bodyCache.put(m, body);
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
ForumItem item = adapter.getItem(i);
if (item.getHeader().getId().equals(m)) {
item.setBody(body);
adapter.notifyDataSetChanged();
// Scroll to the bottom
list.setSelection(count - 1);
return;
}
}
}
});
}
@Override
protected void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_READ && result == RESULT_PREV_NEXT) {
int position = data.getIntExtra("briar.POSITION", -1);
if (position >= 0 && position < adapter.getCount())
displayPost(position);
}
else if (request == REQUEST_FORUM_SHARED && result == RESULT_OK) {
Snackbar s = Snackbar.make(list, R.string.forum_shared_snackbar,
LENGTH_LONG);
s.getView().setBackgroundResource(R.color.briar_primary);
s.show();
}
}
@Override
public void onPause() {
super.onPause();
eventBus.removeListener(this);
notificationManager.unblockNotification(groupId);
if (isFinishing()) markPostsRead();
}
private void markPostsRead() {
List<MessageId> unread = new ArrayList<>();
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
ForumPostHeader h = adapter.getItem(i).getHeader();
if (!h.isRead()) unread.add(h.getId());
}
if (unread.isEmpty()) return;
if (LOG.isLoggable(INFO))
LOG.info("Marking " + unread.size() + " posts read");
markPostsRead(Collections.unmodifiableList(unread));
}
private void markPostsRead(final Collection<MessageId> unread) {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
for (MessageId m : unread)
forumManager.setReadFlag(m, true);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Marking read took " + duration + " ms");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
public void eventOccurred(Event e) {
if (e instanceof MessageValidatedEvent) {
MessageValidatedEvent m = (MessageValidatedEvent) e;
if (m.isValid() && m.getMessage().getGroupId().equals(groupId)) {
LOG.info("Message added, reloading");
loadHeaders();
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent s = (GroupRemovedEvent) e;
if (s.getGroup().getId().equals(groupId)) {
LOG.info("Forum removed");
finishOnUiThread();
}
}
}
private long getMinTimestampForNewPost() {
// Don't use an earlier timestamp than the newest post
long timestamp = 0;
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
long t = adapter.getItem(i).getHeader().getTimestamp();
if (t > timestamp) timestamp = t;
}
return timestamp + 1;
}
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
displayPost(position);
}
private void displayPost(int position) {
ForumPostHeader header = adapter.getItem(position).getHeader();
Intent i = new Intent(this, ReadForumPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(FORUM_NAME, forum.getName());
i.putExtra("briar.MESSAGE_ID", header.getId().getBytes());
Author author = header.getAuthor();
if (author != null) {
i.putExtra("briar.AUTHOR_NAME", author.getName());
i.putExtra("briar.AUTHOR_ID", author.getId().getBytes());
}
i.putExtra("briar.AUTHOR_STATUS", header.getAuthorStatus().name());
i.putExtra("briar.CONTENT_TYPE", header.getContentType());
i.putExtra("briar.TIMESTAMP", header.getTimestamp());
i.putExtra(MIN_TIMESTAMP, getMinTimestampForNewPost());
i.putExtra("briar.POSITION", position);
startActivityForResult(i, REQUEST_READ);
}
private void showUnsubscribeDialog() {
DialogInterface.OnClickListener okListener =
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
unsubscribe(forum);
Toast.makeText(ForumActivity.this,
R.string.forum_left_toast, LENGTH_SHORT)
.show();
}
};
AlertDialog.Builder builder =
new AlertDialog.Builder(ForumActivity.this,
R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.dialog_title_leave_forum));
builder.setMessage(getString(R.string.dialog_message_leave_forum));
builder.setPositiveButton(R.string.dialog_button_leave, okListener);
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
private void unsubscribe(final Forum f) {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
forumManager.removeForum(f);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Removing forum took " + duration + " ms");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
}

View File

@@ -1,93 +0,0 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.content.res.Resources;
import android.text.format.DateUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.Gravity.CENTER_VERTICAL;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
class ForumAdapter extends ArrayAdapter<ForumItem> {
private final int pad;
ForumAdapter(Context ctx) {
super(ctx, android.R.layout.simple_expandable_list_item_1,
new ArrayList<ForumItem>());
pad = LayoutUtils.getPadding(ctx);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ForumItem item = getItem(position);
ForumPostHeader header = item.getHeader();
Context ctx = getContext();
Resources res = ctx.getResources();
LinearLayout layout = new LinearLayout(ctx);
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
if (!header.isRead())
layout.setBackgroundColor(res.getColor(R.color.unread_background));
LinearLayout headerLayout = new LinearLayout(ctx);
headerLayout.setOrientation(HORIZONTAL);
headerLayout.setGravity(CENTER_VERTICAL);
AuthorView authorView = new AuthorView(ctx);
authorView.setLayoutParams(WRAP_WRAP_1);
authorView.setPadding(0, pad, pad, pad);
Author author = header.getAuthor();
if (author == null) {
authorView.init(null, null, header.getAuthorStatus());
} else {
authorView.init(author.getName(), author.getId(),
header.getAuthorStatus());
}
headerLayout.addView(authorView);
TextView date = new TextView(ctx);
date.setPadding(pad, pad, pad, pad);
long timestamp = header.getTimestamp();
date.setText(DateUtils.getRelativeTimeSpanString(ctx, timestamp));
headerLayout.addView(date);
layout.addView(headerLayout);
if (item.getBody() == null) {
TextView ellipsis = new TextView(ctx);
ellipsis.setPadding(pad, 0, pad, pad);
ellipsis.setText("\u2026");
layout.addView(ellipsis);
} else if (header.getContentType().equals("text/plain")) {
TextView text = new TextView(ctx);
text.setPadding(pad, 0, pad, pad);
text.setText(StringUtils.fromUtf8(item.getBody()));
layout.addView(text);
} else {
ImageButton attachment = new ImageButton(ctx);
attachment.setPadding(pad, 0, pad, pad);
attachment.setImageResource(R.drawable.content_attachment);
layout.addView(attachment);
}
return layout;
}
}

View File

@@ -1,25 +0,0 @@
package org.briarproject.android.forum;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.forum.Forum;
import java.util.Collection;
class ForumContacts {
private final Forum forum;
private final Collection<Contact> contacts;
ForumContacts(Forum forum, Collection<Contact> contacts) {
this.forum = forum;
this.contacts = contacts;
}
Forum getForum() {
return forum;
}
Collection<Contact> getContacts() {
return contacts;
}
}

View File

@@ -1,27 +0,0 @@
package org.briarproject.android.forum;
import org.briarproject.api.forum.ForumPostHeader;
// This class is not thread-safe
class ForumItem {
private final ForumPostHeader header;
private byte[] body;
ForumItem(ForumPostHeader header) {
this.header = header;
body = null;
}
ForumPostHeader getHeader() {
return header;
}
byte[] getBody() {
return body;
}
void setBody(byte[] body) {
this.body = body;
}
}

View File

@@ -1,17 +0,0 @@
package org.briarproject.android.forum;
import java.util.Comparator;
class ForumItemComparator implements Comparator<ForumItem> {
static final ForumItemComparator INSTANCE = new ForumItemComparator();
public int compare(ForumItem a, ForumItem b) {
// The oldest message comes first
long aTime = a.getHeader().getTimestamp();
long bTime = b.getHeader().getTimestamp();
if (aTime < bTime) return -1;
if (aTime > bTime) return 1;
return 0;
}
}

View File

@@ -1,197 +0,0 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.util.TextAvatarView;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
import static org.briarproject.android.BriarActivity.GROUP_ID;
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
public class ForumListAdapter extends
RecyclerView.Adapter<ForumListAdapter.ForumViewHolder> {
private SortedList<ForumListItem> forums = new SortedList<>(
ForumListItem.class, new SortedList.Callback<ForumListItem>() {
@Override
public int compare(ForumListItem a, ForumListItem b) {
if (a == b) return 0;
// The forum with the newest message comes first
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
if (aTime > bTime) return -1;
if (aTime < bTime) return 1;
// Break ties by forum name
String aName = a.getForum().getName();
String bName = b.getForum().getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
@Override
public void onInserted(int position, int count) {
notifyItemRangeInserted(position, count);
}
@Override
public void onRemoved(int position, int count) {
notifyItemRangeRemoved(position, count);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onChanged(int position, int count) {
notifyItemRangeChanged(position, count);
}
@Override
public boolean areContentsTheSame(ForumListItem a, ForumListItem b) {
return a.getForum().equals(b.getForum()) &&
a.getTimestamp() == b.getTimestamp() &&
a.getUnreadCount() == b.getUnreadCount();
}
@Override
public boolean areItemsTheSame(ForumListItem a, ForumListItem b) {
return a.getForum().equals(b.getForum());
}
});
private final Context ctx;
public ForumListAdapter(Context ctx) {
this.ctx = ctx;
}
@Override
public ForumViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_forum, parent, false);
return new ForumViewHolder(v);
}
@Override
public void onBindViewHolder(ForumViewHolder ui, int position) {
final ForumListItem item = getItem(position);
// Avatar
ui.avatar.setText(item.getForum().getName().substring(0, 1));
ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes());
// Forum Name
ui.name.setText(item.getForum().getName());
// Unread Count
int unread = item.getUnreadCount();
if (unread > 0) {
ui.unread.setText(ctx.getResources()
.getQuantityString(R.plurals.unread_posts, unread, unread));
ui.unread.setTextColor(
ContextCompat.getColor(ctx, R.color.briar_button_positive));
} else {
ui.unread.setText(ctx.getString(R.string.no_unread_posts));
ui.unread.setTextColor(
ContextCompat.getColor(ctx, R.color.briar_text_secondary));
}
// Date or "No Posts"
if (item.isEmpty()) {
ui.date.setVisibility(View.GONE);
} else {
long timestamp = item.getTimestamp();
ui.date.setText(
DateUtils.getRelativeTimeSpanString(ctx, timestamp));
ui.date.setVisibility(View.VISIBLE);
}
// Open Forum on Click
ui.layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(ctx, ForumActivity.class);
Forum f = item.getForum();
i.putExtra(GROUP_ID, f.getId().getBytes());
i.putExtra(FORUM_NAME, f.getName());
ctx.startActivity(i);
}
});
}
@Override
public int getItemCount() {
return forums.size();
}
public ForumListItem getItem(int position) {
return forums.get(position);
}
@Nullable
public ForumListItem getItem(GroupId g) {
for (int i = 0; i < forums.size(); i++) {
ForumListItem item = forums.get(i);
if (item.getForum().getGroup().getId().equals(g)) {
return item;
}
}
return null;
}
public void addAll(Collection<ForumListItem> items) {
forums.addAll(items);
}
public void updateItem(ForumListItem item) {
ForumListItem oldItem = getItem(item.getForum().getGroup().getId());
int position = forums.indexOf(oldItem);
forums.updateItemAt(position, item);
}
public void remove(ForumListItem item) {
forums.remove(item);
}
public void clear() {
forums.clear();
}
public boolean isEmpty() {
return forums.size() == 0;
}
protected static class ForumViewHolder extends RecyclerView.ViewHolder {
private final ViewGroup layout;
private final TextAvatarView avatar;
private final TextView name;
private final TextView unread;
private final TextView date;
public ForumViewHolder(View v) {
super(v);
layout = (ViewGroup) v;
avatar = (TextAvatarView) v.findViewById(R.id.avatarView);
name = (TextView) v.findViewById(R.id.forumNameView);
unread = (TextView) v.findViewById(R.id.unreadView);
date = (TextView) v.findViewById(R.id.dateView);
}
}
}

View File

@@ -1,286 +0,0 @@
package org.briarproject.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
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 org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.fragment.BaseEventFragment;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.GroupAddedEvent;
import org.briarproject.api.event.GroupRemovedEvent;
import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
public class ForumListFragment extends BaseEventFragment implements
View.OnClickListener {
public final static String TAG = "ForumListFragment";
private static final Logger LOG =
Logger.getLogger(ForumListFragment.class.getName());
private BriarRecyclerView list;
private ForumListAdapter adapter;
private Snackbar snackbar;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile ForumManager forumManager;
@Inject protected volatile ForumSharingManager forumSharingManager;
@Inject
public ForumListFragment() {
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setHasOptionsMenu(true);
View contentView =
inflater.inflate(R.layout.fragment_forum_list, container,
false);
adapter = new ForumListAdapter(getActivity());
list = (BriarRecyclerView) contentView.findViewById(R.id.forumList);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_forums));
snackbar = Snackbar.make(list, "", LENGTH_INDEFINITE);
snackbar.getView().setBackgroundResource(R.color.briar_primary);
snackbar.setAction(R.string.show_forums, this);
snackbar.setActionTextColor(ContextCompat
.getColor(getContext(), R.color.briar_button_positive));
return contentView;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void onResume() {
super.onResume();
loadForumHeaders();
loadAvailableForums();
}
@Override
public void onPause() {
super.onPause();
adapter.clear();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.forum_list_actions, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_create_forum:
Intent intent =
new Intent(getContext(), CreateForumActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void loadForumHeaders() {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
// load forums
long now = System.currentTimeMillis();
Collection<ForumListItem> forums = new ArrayList<>();
for (Forum f : forumManager.getForums()) {
try {
Collection<ForumPostHeader> headers =
forumManager.getPostHeaders(f.getId());
forums.add(new ForumListItem(f, headers));
} catch (NoSuchGroupException e) {
// Continue
}
}
displayForumHeaders(forums);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayForumHeaders(final Collection<ForumListItem> forums) {
listener.runOnUiThread(new Runnable() {
@Override
public void run() {
if (forums.size() > 0) adapter.addAll(forums);
else list.showData();
}
});
}
private void loadAvailableForums() {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();
int available =
forumSharingManager.getAvailableForums().size();
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading available took " + duration + " ms");
displayAvailableForums(available);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayAvailableForums(final int availableCount) {
listener.runOnUiThread(new Runnable() {
@Override
public void run() {
if (availableCount == 0) {
snackbar.dismiss();
} else {
snackbar.show();
snackbar.setText(getResources().getQuantityString(
R.plurals.forums_shared, availableCount,
availableCount));
}
}
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, reloading available forums");
loadAvailableForums();
} else if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
LOG.info("Forum added, reloading forums");
loadForumHeaders();
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getClientId().equals(forumManager.getClientId())) {
LOG.info("Forum removed, removing from list");
removeForum(g.getGroup().getId());
}
} else if (e instanceof MessageValidatedEvent) {
MessageValidatedEvent m = (MessageValidatedEvent) e;
if (m.isValid()) {
ClientId c = m.getClientId();
if (c.equals(forumManager.getClientId())) {
LOG.info("Forum post added, reloading");
loadForumHeaders(m.getMessage().getGroupId());
}
}
} else if (e instanceof ForumInvitationReceivedEvent) {
loadAvailableForums();
}
}
private void loadForumHeaders(final GroupId g) {
listener.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
long now = System.currentTimeMillis();
Forum f = forumManager.getForum(g);
Collection<ForumPostHeader> headers =
forumManager.getPostHeaders(g);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Partial load took " + duration + " ms");
updateForum(new ForumListItem(f, headers));
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void updateForum(final ForumListItem item) {
listener.runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.updateItem(item);
}
});
}
private void removeForum(final GroupId g) {
listener.runOnUiThread(new Runnable() {
@Override
public void run() {
ForumListItem item = adapter.getItem(g);
if (item != null) adapter.remove(item);
}
});
}
@Override
public void onClick(View view) {
// snackbar click
startActivity(new Intent(getContext(), AvailableForumsActivity.class));
}
}

View File

@@ -1,52 +0,0 @@
package org.briarproject.android.forum;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumPostHeader;
import java.util.Collection;
class ForumListItem {
private final Forum forum;
private final boolean empty;
private final long timestamp;
private final int unread;
ForumListItem(Forum forum, Collection<ForumPostHeader> headers) {
this.forum = forum;
empty = headers.isEmpty();
if (empty) {
timestamp = 0;
unread = 0;
} else {
ForumPostHeader newest = null;
long timestamp = -1;
int unread = 0;
for (ForumPostHeader h : headers) {
if (h.getTimestamp() > timestamp) {
timestamp = h.getTimestamp();
newest = h;
}
if (!h.isRead()) unread++;
}
this.timestamp = newest.getTimestamp();
this.unread = unread;
}
}
Forum getForum() {
return forum;
}
boolean isEmpty() {
return empty;
}
long getTimestamp() {
return timestamp;
}
int getUnreadCount() {
return unread;
}
}

View File

@@ -1,169 +0,0 @@
package org.briarproject.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.util.BriarRecyclerView;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
public class ForumSharingStatusActivity extends BriarActivity {
private GroupId groupId;
private BriarRecyclerView sharedByList, sharedWithList;
private ForumSharingStatusAdapter sharedByAdapter, sharedWithAdapter;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject
protected volatile IdentityManager identityManager;
public final static String TAG = "ForumSharingStatusActivity";
private static final Logger LOG = Logger.getLogger(TAG);
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_forum_sharing_status);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b);
sharedByList = (BriarRecyclerView) findViewById(R.id.sharedByView);
sharedByAdapter = new ForumSharingStatusAdapter(this);
sharedByList.setLayoutManager(new LinearLayoutManager(this));
sharedByList.setAdapter(sharedByAdapter);
sharedByList.setEmptyText(getString(R.string.nobody));
sharedWithList = (BriarRecyclerView) findViewById(R.id.sharedWithView);
sharedWithAdapter = new ForumSharingStatusAdapter(this);
sharedWithList.setLayoutManager(new LinearLayoutManager(this));
sharedWithList.setAdapter(sharedWithAdapter);
sharedWithList.setEmptyText(getString(R.string.nobody));
}
@Override
public void onResume() {
super.onResume();
loadSharedBy();
loadSharedWith();
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
private void loadSharedBy() {
dbController.runOnDbThread(new Runnable() {
@Override
public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try {
Collection<Contact> contacts =
forumSharingManager.getSharedBy(groupId);
for (Contact c : contacts) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
ContactListItem item =
new ContactListItem(c, localAuthor, false, null,
null);
contactItems.add(item);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
displaySharedBy(contactItems);
}
});
}
private void displaySharedBy(final List<ContactListItem> contacts) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (contacts.isEmpty()) {
sharedByList.showData();
} else {
sharedByAdapter.addAll(contacts);
}
}
});
}
private void loadSharedWith() {
dbController.runOnDbThread(new Runnable() {
@Override
public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try {
Collection<Contact> contacts =
forumSharingManager.getSharedWith(groupId);
for (Contact c : contacts) {
LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId());
ContactListItem item =
new ContactListItem(c, localAuthor, false, null,
null);
contactItems.add(item);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
displaySharedWith(contactItems);
}
});
}
private void displaySharedWith(final List<ContactListItem> contacts) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (contacts.isEmpty()) {
sharedWithList.showData();
} else {
sharedWithAdapter.addAll(contacts);
}
}
});
}
}

View File

@@ -1,36 +0,0 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.contact.BaseContactListAdapter;
import org.briarproject.android.contact.ContactListItem;
public class ForumSharingStatusAdapter
extends BaseContactListAdapter<BaseContactListAdapter.BaseContactHolder> {
public ForumSharingStatusAdapter(Context context) {
super(context, null);
}
@Override
public BaseContactHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_contact_small, viewGroup, false);
return new BaseContactHolder(v);
}
@Override
public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
return compareByName(c1, c2);
}
}

View File

@@ -1,249 +0,0 @@
package org.briarproject.android.forum;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.format.DateUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.AuthorView;
import org.briarproject.android.util.ElasticHorizontalSpace;
import org.briarproject.android.util.HorizontalBorder;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_VERTICAL;
import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.LinearLayout.VERTICAL;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
import static org.briarproject.android.forum.ForumActivity.MIN_TIMESTAMP;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP_1;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP_1;
public class ReadForumPostActivity extends BriarActivity
implements OnClickListener {
static final int RESULT_REPLY = RESULT_FIRST_USER;
static final int RESULT_PREV_NEXT = RESULT_FIRST_USER + 1;
private static final Logger LOG =
Logger.getLogger(ReadForumPostActivity.class.getName());
private GroupId groupId = null;
private String forumName = null;
private long minTimestamp = -1;
private ImageButton prevButton = null, nextButton = null;
private ImageButton replyButton = null;
private TextView content = null;
private int position = -1;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile ForumManager forumManager;
private volatile MessageId messageId = null;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException();
groupId = new GroupId(b);
forumName = i.getStringExtra(FORUM_NAME);
if (forumName == null) throw new IllegalStateException();
setTitle(forumName);
b = i.getByteArrayExtra("briar.MESSAGE_ID");
if (b == null) throw new IllegalStateException();
messageId = new MessageId(b);
String contentType = i.getStringExtra("briar.CONTENT_TYPE");
if (contentType == null) throw new IllegalStateException();
long timestamp = i.getLongExtra("briar.TIMESTAMP", -1);
if (timestamp == -1) throw new IllegalStateException();
minTimestamp = i.getLongExtra(MIN_TIMESTAMP, -1);
if (minTimestamp == -1) throw new IllegalStateException();
position = i.getIntExtra("briar.POSITION", -1);
if (position == -1) throw new IllegalStateException();
String authorName = i.getStringExtra("briar.AUTHOR_NAME");
AuthorId authorId = null;
b = i.getByteArrayExtra("briar.AUTHOR_ID");
if (b != null) authorId = new AuthorId(b);
String s = i.getStringExtra("briar.AUTHOR_STATUS");
if (s == null) throw new IllegalStateException();
Author.Status authorStatus = Author.Status.valueOf(s);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setOrientation(VERTICAL);
ScrollView scrollView = new ScrollView(this);
scrollView.setLayoutParams(MATCH_WRAP_1);
LinearLayout message = new LinearLayout(this);
message.setOrientation(VERTICAL);
LinearLayout header = new LinearLayout(this);
header.setLayoutParams(MATCH_WRAP);
header.setOrientation(HORIZONTAL);
header.setGravity(CENTER_VERTICAL);
int pad = LayoutUtils.getPadding(this);
AuthorView authorView = new AuthorView(this);
authorView.setPadding(0, pad, pad, pad);
authorView.setLayoutParams(WRAP_WRAP_1);
authorView.init(authorName, authorId, authorStatus);
header.addView(authorView);
TextView date = new TextView(this);
date.setPadding(pad, pad, pad, pad);
date.setText(DateUtils.getRelativeTimeSpanString(this, timestamp));
header.addView(date);
message.addView(header);
if (contentType.equals("text/plain")) {
// Load and display the message body
content = new TextView(this);
content.setPadding(pad, 0, pad, pad);
message.addView(content);
loadPostBody();
}
scrollView.addView(message);
layout.addView(scrollView);
layout.addView(new HorizontalBorder(this));
LinearLayout footer = new LinearLayout(this);
footer.setLayoutParams(MATCH_WRAP);
footer.setOrientation(HORIZONTAL);
footer.setGravity(CENTER);
Resources res = getResources();
footer.setBackgroundColor(res.getColor(R.color.button_bar_background));
prevButton = new ImageButton(this);
prevButton.setBackgroundResource(0);
prevButton.setImageResource(R.drawable.navigation_previous_item);
prevButton.setOnClickListener(this);
footer.addView(prevButton);
footer.addView(new ElasticHorizontalSpace(this));
nextButton = new ImageButton(this);
nextButton.setBackgroundResource(0);
nextButton.setImageResource(R.drawable.navigation_next_item);
nextButton.setOnClickListener(this);
footer.addView(nextButton);
footer.addView(new ElasticHorizontalSpace(this));
replyButton = new ImageButton(this);
replyButton.setBackgroundResource(0);
replyButton.setImageResource(R.drawable.social_reply_all);
replyButton.setOnClickListener(this);
footer.addView(replyButton);
layout.addView(footer);
setContentView(layout);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onPause() {
super.onPause();
if (isFinishing()) markPostRead();
}
private void markPostRead() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
forumManager.setReadFlag(messageId, true);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Marking read took " + duration + " ms");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void loadPostBody() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
byte[] body = forumManager.getPostBody(messageId);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading post took " + duration + " ms");
displayPostBody(StringUtils.fromUtf8(body));
} catch (NoSuchMessageException e) {
finishOnUiThread();
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayPostBody(final String body) {
runOnUiThread(new Runnable() {
public void run() {
content.setText(body);
}
});
}
public void onClick(View view) {
if (view == prevButton) {
Intent i = new Intent();
i.putExtra("briar.POSITION", position - 1);
setResult(RESULT_PREV_NEXT, i);
finish();
} else if (view == nextButton) {
Intent i = new Intent();
i.putExtra("briar.POSITION", position + 1);
setResult(RESULT_PREV_NEXT, i);
finish();
} else if (view == replyButton) {
Intent i = new Intent(this, WriteForumPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
i.putExtra(FORUM_NAME, forumName);
i.putExtra("briar.PARENT_ID", messageId.getBytes());
i.putExtra(MIN_TIMESTAMP, minTimestamp);
startActivity(i);
setResult(RESULT_REPLY);
finish();
}
}
}

View File

@@ -1,41 +0,0 @@
package org.briarproject.android.forum;
import org.briarproject.android.contact.ContactListItem;
import org.briarproject.android.contact.ConversationItem;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import java.util.Collections;
// This class is not thread-safe
public class SelectableContactListItem extends ContactListItem {
private boolean selected, disabled;
public SelectableContactListItem(Contact contact, LocalAuthor localAuthor,
GroupId groupId, boolean selected, boolean disabled) {
super(contact, localAuthor, false, groupId,
Collections.<ConversationItem>emptyList());
this.selected = selected;
this.disabled = disabled;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
public boolean isSelected() {
return selected;
}
public void toggleSelected() {
selected = !selected;
}
public boolean isDisabled() {
return disabled;
}
}

View File

@@ -1,105 +0,0 @@
package org.briarproject.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ShareForumActivity extends BriarActivity implements
BaseFragment.BaseFragmentListener {
public final static String CONTACTS = "contacts";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share_forum);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
GroupId groupId = new GroupId(b);
if (savedInstanceState == null) {
ContactSelectorFragment contactSelectorFragment =
activityComponent.newContactSelectorFragment();
contactSelectorFragment.initBundle(groupId);
getSupportFragmentManager().beginTransaction()
.add(R.id.shareForumContainer, contactSelectorFragment)
.commit();
}
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
public void showMessageScreen(GroupId groupId,
Collection<ContactId> contacts) {
ShareForumMessageFragment messageFragment =
activityComponent.newShareForumMessageFragment();
messageFragment.initBundle(groupId, contacts);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(android.R.anim.fade_in,
android.R.anim.fade_out,
android.R.anim.slide_in_left,
android.R.anim.slide_out_right)
.replace(R.id.shareForumContainer, messageFragment,
ContactSelectorFragment.TAG)
.addToBackStack(null)
.commit();
}
public static ArrayList<Integer> getContactsFromIds(
Collection<ContactId> contacts) {
// transform ContactIds to Integers so they can be added to a bundle
ArrayList<Integer> intContacts = new ArrayList<>(contacts.size());
for (ContactId contactId : contacts) {
intContacts.add(contactId.getInt());
}
return intContacts;
}
public void sharingSuccessful(View v) {
setResult(RESULT_OK);
hideSoftKeyboard(v);
supportFinishAfterTransition();
}
protected static Collection<ContactId> getContactsFromIntegers(
ArrayList<Integer> intContacts) {
// turn contact integers from a bundle back to ContactIds
List<ContactId> contacts = new ArrayList<>(intContacts.size());
for(Integer c : intContacts) {
contacts.add(new ContactId(c));
}
return contacts;
}
@Override
public void showLoadingScreen(boolean isBlocking, int stringId) {
// this is handled by the recycler view in ContactSelectorFragment
}
@Override
public void hideLoadingScreen() {
// this is handled by the recycler view in ContactSelectorFragment
}
}

View File

@@ -1,171 +0,0 @@
package org.briarproject.android.forum;
import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.sync.GroupId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ShareForumActivity.CONTACTS;
import static org.briarproject.android.forum.ShareForumActivity.getContactsFromIds;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
public class ShareForumMessageFragment extends BaseFragment {
public final static String TAG = "IntroductionMessageFragment";
private static final Logger LOG =
Logger.getLogger(ShareForumMessageFragment.class.getName());
private ShareForumActivity shareForumActivity;
private ViewHolder ui;
// Fields that are accessed from background threads must be volatile
@Inject
protected volatile ForumSharingManager forumSharingManager;
private volatile GroupId groupId;
private volatile Collection<ContactId> contacts;
public void initBundle(GroupId groupId, Collection<ContactId> contacts) {
Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, groupId.getBytes());
bundle.putIntegerArrayList(CONTACTS, getContactsFromIds(contacts));
setArguments(bundle);
}
@Inject
public ShareForumMessageFragment() {
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
shareForumActivity = (ShareForumActivity) context;
} catch (ClassCastException e) {
throw new InstantiationError(
"This fragment is only meant to be attached to the ShareForumActivity");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// change toolbar text
ActionBar actionBar = shareForumActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.forum_share_button);
}
// allow for home button to act as back button
setHasOptionsMenu(true);
// inflate view
View v = inflater.inflate(R.layout.share_forum_message, container,
false);
ui = new ViewHolder(v);
ui.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onButtonClick();
}
});
// get groupID and contactIDs from fragment arguments
groupId = new GroupId(getArguments().getByteArray(GROUP_ID));
ArrayList<Integer> intContacts =
getArguments().getIntegerArrayList(CONTACTS);
if (intContacts == null) throw new IllegalArgumentException();
contacts = ShareForumActivity.getContactsFromIntegers(intContacts);
return v;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
shareForumActivity.onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
public void onButtonClick() {
// disable button to prevent accidental double invitations
ui.button.setEnabled(false);
String msg = ui.message.getText().toString();
shareForum(msg);
// don't wait for the introduction to be made before finishing activity
shareForumActivity.sharingSuccessful(ui.message);
}
private void shareForum(final String msg) {
shareForumActivity.runOnDbThread(new Runnable() {
@Override
public void run() {
try {
for (ContactId c : contacts) {
forumSharingManager.sendForumInvitation(groupId, c,
msg);
}
} catch (DbException e) {
sharingError();
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void sharingError() {
shareForumActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(shareForumActivity,
R.string.introduction_error, LENGTH_SHORT).show();
}
});
}
private static class ViewHolder {
private final EditText message;
private final Button button;
ViewHolder(View v) {
message = (EditText) v.findViewById(R.id.invitationMessageView);
button = (Button) v.findViewById(R.id.shareForumButton);
}
}
}

View File

@@ -1,317 +0,0 @@
package org.briarproject.android.forum;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputType;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.AndroidComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.identity.CreateIdentityActivity;
import org.briarproject.android.identity.LocalAuthorItem;
import org.briarproject.android.identity.LocalAuthorItemComparator;
import org.briarproject.android.identity.LocalAuthorSpinnerAdapter;
import org.briarproject.android.util.CommonLayoutParams;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.FormatException;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.db.DbException;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
import static android.widget.LinearLayout.VERTICAL;
import static android.widget.RelativeLayout.ALIGN_PARENT_LEFT;
import static android.widget.RelativeLayout.ALIGN_PARENT_RIGHT;
import static android.widget.RelativeLayout.CENTER_VERTICAL;
import static android.widget.RelativeLayout.LEFT_OF;
import static android.widget.RelativeLayout.RIGHT_OF;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.forum.ForumActivity.FORUM_NAME;
import static org.briarproject.android.forum.ForumActivity.MIN_TIMESTAMP;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_WRAP;
public class WriteForumPostActivity extends BriarActivity
implements OnItemSelectedListener, OnClickListener {
private static final int REQUEST_CREATE_IDENTITY = 2;
private static final Logger LOG =
Logger.getLogger(WriteForumPostActivity.class.getName());
@Inject @CryptoExecutor protected Executor cryptoExecutor;
private LocalAuthorSpinnerAdapter adapter = null;
private Spinner spinner = null;
private ImageButton sendButton = null;
private EditText content = null;
private AuthorId localAuthorId = null;
private GroupId groupId = null;
// Fields that are accessed from background threads must be volatile
@Inject protected volatile IdentityManager identityManager;
@Inject protected volatile ForumManager forumManager;
@Inject protected volatile ForumPostFactory forumPostFactory;
@Inject protected volatile CryptoComponent crypto;
private volatile MessageId parentId = null;
private volatile long minTimestamp = -1;
private volatile LocalAuthor localAuthor = null;
private volatile Forum forum = null;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
Intent i = getIntent();
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException();
groupId = new GroupId(b);
String forumName = i.getStringExtra(FORUM_NAME);
if (forumName == null) throw new IllegalStateException();
setTitle(forumName);
minTimestamp = i.getLongExtra(MIN_TIMESTAMP, -1);
if (minTimestamp == -1) throw new IllegalStateException();
b = i.getByteArrayExtra("briar.PARENT_ID");
if (b != null) parentId = new MessageId(b);
if (state != null) {
b = state.getByteArray("briar.LOCAL_AUTHOR_ID");
if (b != null) localAuthorId = new AuthorId(b);
}
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_WRAP);
layout.setOrientation(VERTICAL);
int pad = LayoutUtils.getPadding(this);
layout.setPadding(pad, 0, pad, pad);
RelativeLayout header = new RelativeLayout(this);
TextView from = new TextView(this);
from.setId(1);
from.setTextSize(18);
from.setText(R.string.from);
RelativeLayout.LayoutParams left = CommonLayoutParams.relative();
left.addRule(ALIGN_PARENT_LEFT);
left.addRule(CENTER_VERTICAL);
header.addView(from, left);
adapter = new LocalAuthorSpinnerAdapter(this, true);
spinner = new Spinner(this);
spinner.setId(2);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(this);
RelativeLayout.LayoutParams between = CommonLayoutParams.relative();
between.addRule(CENTER_VERTICAL);
between.addRule(RIGHT_OF, 1);
between.addRule(LEFT_OF, 3);
header.addView(spinner, between);
sendButton = new ImageButton(this);
sendButton.setId(3);
sendButton.setBackgroundResource(0);
sendButton.setImageResource(R.drawable.social_send_now);
sendButton.setEnabled(false); // Enabled after loading the forum
sendButton.setOnClickListener(this);
RelativeLayout.LayoutParams right = CommonLayoutParams.relative();
right.addRule(ALIGN_PARENT_RIGHT);
right.addRule(CENTER_VERTICAL);
header.addView(sendButton, right);
layout.addView(header);
content = new EditText(this);
content.setId(4);
int inputType = TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE
| TYPE_TEXT_FLAG_CAP_SENTENCES;
content.setInputType(inputType);
content.setHint(R.string.forum_post_hint);
layout.addView(content);
setContentView(layout);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onResume() {
super.onResume();
loadAuthorsAndForum();
}
private void loadAuthorsAndForum() {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
Collection<LocalAuthor> localAuthors =
identityManager.getLocalAuthors();
forum = forumManager.getForum(groupId);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms");
displayAuthorsAndForum(localAuthors);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
private void displayAuthorsAndForum(
final Collection<LocalAuthor> localAuthors) {
runOnUiThread(new Runnable() {
public void run() {
if (localAuthors.isEmpty()) throw new IllegalStateException();
adapter.clear();
for (LocalAuthor a : localAuthors)
adapter.add(new LocalAuthorItem(a));
adapter.sort(LocalAuthorItemComparator.INSTANCE);
int count = adapter.getCount();
for (int i = 0; i < count; i++) {
LocalAuthorItem item = adapter.getItem(i);
if (item == LocalAuthorItem.ANONYMOUS) continue;
if (item == LocalAuthorItem.NEW) continue;
if (item.getLocalAuthor().getId().equals(localAuthorId)) {
localAuthor = item.getLocalAuthor();
spinner.setSelection(i);
break;
}
}
setTitle(forum.getName());
sendButton.setEnabled(true);
}
});
}
@Override
public void onSaveInstanceState(Bundle state) {
super.onSaveInstanceState(state);
if (localAuthorId != null) {
byte[] b = localAuthorId.getBytes();
state.putByteArray("briar.LOCAL_AUTHOR_ID", b);
}
}
@Override
protected void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_CREATE_IDENTITY && result == RESULT_OK) {
byte[] b = data.getByteArrayExtra("briar.LOCAL_AUTHOR_ID");
if (b == null) throw new IllegalStateException();
localAuthorId = new AuthorId(b);
loadAuthorsAndForum();
}
}
public void onItemSelected(AdapterView<?> parent, View view, int position,
long id) {
LocalAuthorItem item = adapter.getItem(position);
if (item == LocalAuthorItem.ANONYMOUS) {
localAuthor = null;
localAuthorId = null;
} else if (item == LocalAuthorItem.NEW) {
localAuthor = null;
localAuthorId = null;
Intent i = new Intent(this, CreateIdentityActivity.class);
startActivityForResult(i, REQUEST_CREATE_IDENTITY);
} else {
localAuthor = item.getLocalAuthor();
localAuthorId = localAuthor.getId();
}
}
public void onNothingSelected(AdapterView<?> parent) {
localAuthor = null;
localAuthorId = null;
}
public void onClick(View view) {
if (forum == null) throw new IllegalStateException();
String body = content.getText().toString();
if (body.equals("")) return;
createPost(StringUtils.toUtf8(body));
Toast.makeText(this, R.string.post_sent_toast, LENGTH_LONG).show();
finish();
}
private void createPost(final byte[] body) {
cryptoExecutor.execute(new Runnable() {
public void run() {
// Don't use an earlier timestamp than the newest post
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, minTimestamp);
ForumPost p;
try {
if (localAuthor == null) {
p = forumPostFactory.createAnonymousPost(groupId,
timestamp, parentId, "text/plain", body);
} else {
KeyParser keyParser = crypto.getSignatureKeyParser();
byte[] b = localAuthor.getPrivateKey();
PrivateKey authorKey = keyParser.parsePrivateKey(b);
p = forumPostFactory.createPseudonymousPost(groupId,
timestamp, parentId, localAuthor, "text/plain",
body, authorKey);
}
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (FormatException e) {
throw new RuntimeException(e);
}
storePost(p);
}
});
}
private void storePost(final ForumPost p) {
runOnDbThread(new Runnable() {
public void run() {
try {
long now = System.currentTimeMillis();
forumManager.addLocalPost(p);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Storing message took " + duration + " ms");
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
}

View File

@@ -59,7 +59,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
private ListPreference enableBluetooth;
private ListPreference torOverMobile;
private CheckBoxPreference notifyPrivateMessages;
private CheckBoxPreference notifyForumPosts;
private CheckBoxPreference notifyVibration;
private Preference notifySound;
@@ -94,8 +93,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
(ListPreference) findPreference("pref_key_tor_mobile");
notifyPrivateMessages = (CheckBoxPreference) findPreference(
"pref_key_notify_private_messages");
notifyForumPosts = (CheckBoxPreference) findPreference(
"pref_key_notify_forum_posts");
notifyVibration = (CheckBoxPreference) findPreference(
"pref_key_notify_vibration");
notifySound = findPreference("pref_key_notify_sound");
@@ -103,7 +100,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
enableBluetooth.setOnPreferenceChangeListener(this);
torOverMobile.setOnPreferenceChangeListener(this);
notifyPrivateMessages.setOnPreferenceChangeListener(this);
notifyForumPosts.setOnPreferenceChangeListener(this);
notifyVibration.setOnPreferenceChangeListener(this);
notifySound.setOnPreferenceClickListener(
@@ -197,9 +193,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
notifyPrivateMessages.setChecked(settings.getBoolean(
"notifyPrivateMessages", true));
notifyForumPosts.setChecked(settings.getBoolean(
"notifyForumPosts", true));
notifyVibration.setChecked(settings.getBoolean(
"notifyVibration", true));
@@ -241,10 +234,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
Settings s = new Settings();
s.putBoolean("notifyPrivateMessages", (Boolean) o);
storeSettings(s);
} else if (preference == notifyForumPosts) {
Settings s = new Settings();
s.putBoolean("notifyForumPosts", (Boolean) o);
storeSettings(s);
} else if (preference == notifyVibration) {
Settings s = new Settings();
s.putBoolean("notifyVibration", (Boolean) o);

View File

@@ -2,14 +2,12 @@ package org.briarproject.android.identity;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -18,7 +16,7 @@ import android.widget.Toast;
import org.briarproject.R;
import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity;
import org.briarproject.android.util.AndroidUtils;
import org.briarproject.android.util.LayoutUtils;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.CryptoExecutor;
import org.briarproject.api.crypto.KeyPair;
@@ -33,11 +31,18 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_WORDS;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.LinearLayout.VERTICAL;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
public class CreateIdentityActivity extends BriarActivity
@@ -50,10 +55,10 @@ public class CreateIdentityActivity extends BriarActivity
@CryptoExecutor
protected Executor cryptoExecutor;
private TextInputLayout nicknameInput;
private EditText nicknameEntry;
private Button createIdentityButton;
private ProgressBar progress;
private TextView feedback;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -66,32 +71,52 @@ public class CreateIdentityActivity extends BriarActivity
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
int pad = LayoutUtils.getPadding(this);
layout.setPadding(pad, pad, pad, pad);
setContentView(R.layout.activity_create_identity);
TextView chooseNickname = new TextView(this);
chooseNickname.setGravity(CENTER);
chooseNickname.setTextSize(18);
chooseNickname.setText(R.string.choose_nickname);
layout.addView(chooseNickname);
nicknameInput = (TextInputLayout) findViewById(R.id.nicknameInputLayout);
nicknameEntry = (EditText) findViewById(R.id.nicknameEntry);
nicknameEntry.addTextChangedListener(new TextWatcher() {
nicknameEntry = new EditText(this) {
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
enableOrDisableCreateButton();
}
@Override
public void afterTextChanged(Editable s) {}
});
};
nicknameEntry.setId(1);
nicknameEntry.setMaxLines(1);
int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS;
nicknameEntry.setInputType(inputType);
nicknameEntry.setOnEditorActionListener(this);
layout.addView(nicknameEntry);
createIdentityButton = (Button) findViewById(R.id.createIdentityButton);
if (createIdentityButton != null)
createIdentityButton.setOnClickListener(this);
feedback = new TextView(this);
feedback.setGravity(CENTER);
feedback.setPadding(0, pad, 0, pad);
layout.addView(feedback);
progress = (ProgressBar) findViewById(R.id.progressBar);
createIdentityButton = new Button(this);
createIdentityButton.setLayoutParams(WRAP_WRAP);
createIdentityButton.setText(R.string.create_identity_button);
createIdentityButton.setEnabled(false);
createIdentityButton.setOnClickListener(this);
layout.addView(createIdentityButton);
progress = new ProgressBar(this);
progress.setLayoutParams(WRAP_WRAP);
progress.setIndeterminate(true);
progress.setVisibility(GONE);
layout.addView(progress);
setContentView(layout);
}
@Override
@@ -114,11 +139,10 @@ public class CreateIdentityActivity extends BriarActivity
String nickname = nicknameEntry.getText().toString();
int length = StringUtils.toUtf8(nickname).length;
if (length > MAX_AUTHOR_NAME_LENGTH) {
String str = getString(R.string.name_too_long);
AndroidUtils.setError(nicknameInput, str, true);
feedback.setText(R.string.name_too_long);
return false;
}
AndroidUtils.setError(nicknameInput, null, false);
feedback.setText("");
return length > 0;
}
@@ -145,7 +169,7 @@ public class CreateIdentityActivity extends BriarActivity
}
private void storeLocalAuthor(final LocalAuthor a) {
dbController.runOnDbThread(new Runnable() {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {

View File

@@ -19,8 +19,6 @@ import android.widget.TextView;
import android.widget.Toast;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import org.briarproject.R;
import org.briarproject.android.AndroidComponent;
@@ -29,7 +27,6 @@ import org.briarproject.android.fragment.BaseEventFragment;
import org.briarproject.android.util.CameraView;
import org.briarproject.android.util.QrCodeDecoder;
import org.briarproject.android.util.QrCodeUtils;
import org.briarproject.android.util.ViewfinderView;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.KeyAgreementAbortedEvent;
import org.briarproject.api.event.KeyAgreementFailedEvent;
@@ -58,7 +55,7 @@ import static java.util.logging.Level.WARNING;
@SuppressWarnings("deprecation")
public class ShowQrCodeFragment extends BaseEventFragment
implements QrCodeDecoder.ResultCallback, ResultPointCallback {
implements QrCodeDecoder.ResultCallback {
public static final String TAG = "ShowQrCodeFragment";
@@ -78,7 +75,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
protected Executor ioExecutor;
private CameraView cameraView;
private ViewfinderView viewfinderView;
private View statusView;
private TextView status;
private ImageView qrCode;
@@ -113,13 +109,9 @@ public class ShowQrCodeFragment extends BaseEventFragment
super.onViewCreated(view, savedInstanceState);
cameraView = (CameraView) view.findViewById(R.id.camera_view);
viewfinderView =
(ViewfinderView) view.findViewById(R.id.viewfinder_view);
statusView = view.findViewById(R.id.status_container);
status = (TextView) view.findViewById(R.id.connect_status);
qrCode = (ImageView) view.findViewById(R.id.qr_code);
viewfinderView.setFrameProvider(cameraView);
}
@Override
@@ -128,7 +120,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
decoder = new QrCodeDecoder(this, this);
decoder = new QrCodeDecoder(this);
}
@Override
@@ -227,7 +219,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
getActivity().finish();
} else {
cameraView.start(camera, decoder, 0);
viewfinderView.drawViewfinder();
}
}
};
@@ -364,11 +355,6 @@ public class ShowQrCodeFragment extends BaseEventFragment
});
}
@Override
public void foundPossibleResultPoint(ResultPoint point) {
viewfinderView.addPossibleResultPoint(point);
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override

View File

@@ -120,11 +120,10 @@ public class BriarRecyclerView extends FrameLayout {
emptyView.setVisibility(VISIBLE);
recyclerView.setVisibility(INVISIBLE);
} else {
// use GONE here so empty view doesn't use space on small lists
emptyView.setVisibility(GONE);
emptyView.setVisibility(INVISIBLE);
recyclerView.setVisibility(VISIBLE);
}
progressBar.setVisibility(GONE);
progressBar.setVisibility(INVISIBLE);
}
}
@@ -132,9 +131,4 @@ public class BriarRecyclerView extends FrameLayout {
if (recyclerView == null) initViews();
recyclerView.scrollToPosition(position);
}
public RecyclerView getRecyclerView() {
return this.recyclerView;
}
}

View File

@@ -1,8 +1,6 @@
package org.briarproject.android.util;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
@@ -15,7 +13,6 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
@@ -34,25 +31,17 @@ import static java.util.logging.Level.WARNING;
@SuppressWarnings("deprecation")
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
AutoFocusCallback, ViewfinderView.FrameProvider {
AutoFocusCallback {
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
private static final int MIN_FRAME_SIZE = 240;
private static final int MAX_FRAME_SIZE = 675; // = 5/8 * 1080
private static final Logger LOG =
Logger.getLogger(CameraView.class.getName());
private Camera camera = null;
private Rect framingRect;
private Rect framingRectInPreview;
private Rect framingRectInSensor;
private PreviewConsumer previewConsumer = null;
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
private boolean autoFocus = false, surfaceExists = false;
private Point cameraResolution;
private final Object cameraResolutionLock = new Object();
public CameraView(Context context) {
super(context);
}
@@ -195,24 +184,6 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
LOG.info("No suitable focus mode");
}
params.setZoom(0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
List<Camera.Area> areas = new ArrayList<>();
areas.add(new Camera.Area(getFramingRectInSensor(), 1000));
if (params.getMaxNumFocusAreas() > 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Focus areas supported: " +
params.getMaxNumFocusAreas());
}
params.setFocusAreas(areas);
}
if (params.getMaxNumMeteringAreas() > 0) {
if (LOG.isLoggable(INFO)) {
LOG.info("Metering areas supported: " +
params.getMaxNumMeteringAreas());
}
params.setMeteringAreas(areas);
}
}
}
private void setPreviewSize(Parameters params) {
@@ -251,13 +222,6 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
if (LOG.isLoggable(INFO))
LOG.info("Best size " + bestSize.width + "x" + bestSize.height);
params.setPreviewSize(bestSize.width, bestSize.height);
synchronized (cameraResolutionLock) {
cameraResolution = new Point(bestSize.width, bestSize.height);
}
} else {
synchronized (cameraResolutionLock) {
cameraResolution = null;
}
}
}
@@ -312,152 +276,4 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
LOG.log(WARNING, "Error retrying auto focus", e);
}
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. This target helps with alignment as well as forces the user to hold the device
* far enough away to ensure the image will be in focus.
*
* @return The rectangle to draw on screen in window coordinates.
*/
@Override
public Rect getFramingRect() {
if (framingRect == null) {
framingRect = calculateFramingRect(true);
if (LOG.isLoggable(INFO))
LOG.info("Calculated framing rect: " + framingRect);
}
return framingRect;
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. This target helps with alignment as well as forces the user to hold the device
* far enough away to ensure the image will be in focus.
* <p/>
* Adapted from the Zxing Barcode Scanner.
*
* @return The rectangle to draw on screen in window coordinates.
*/
private Rect calculateFramingRect(boolean withOrientation) {
if (camera == null) {
return null;
}
if (surfaceWidth == 0 || surfaceHeight == 0) {
// Called early, before the surface is ready
return null;
}
boolean portrait =
withOrientation && displayOrientation % 180 == 90;
int size = findDesiredDimensionInRange(
portrait ? surfaceWidth : surfaceHeight,
portrait ? surfaceHeight / 2 : surfaceWidth / 2,
MIN_FRAME_SIZE, MAX_FRAME_SIZE);
int leftOffset = portrait ?
(surfaceWidth - size) / 2 :
((surfaceWidth / 2) - size) / 2;
int topOffset = portrait ?
((surfaceHeight / 2) - size) / 2 :
(surfaceHeight - size) / 2;
return new Rect(leftOffset, topOffset, leftOffset + size,
topOffset + size);
}
/**
* Calculates the square that fits best inside the given region.
*/
private static int findDesiredDimensionInRange(int side1, int side2,
int hardMin, int hardMax) {
if (LOG.isLoggable(INFO))
LOG.info("Finding framing dimension, side1 = " + side1 +
", side2 = " + side2);
int minSide = Math.min(side1, side2);
int dim = 5 * minSide / 8; // Target 5/8 of smallest side
if (dim < hardMin) {
if (hardMin > minSide) {
if (LOG.isLoggable(INFO))
LOG.info("Returning minimum side length: " + minSide);
return minSide;
} else {
if (LOG.isLoggable(INFO))
LOG.info("Returning hard minimum: " + hardMin);
return hardMin;
}
}
if (dim > hardMax) {
if (LOG.isLoggable(INFO))
LOG.info("Returning hard maximum: " + hardMax);
return hardMax;
}
if (LOG.isLoggable(INFO))
LOG.info("Returning desired dimension: " + dim);
return dim;
}
/**
* Like {@link #getFramingRect} but coordinates are in terms of the preview
* frame, not UI / screen.
* <p/>
* Adapted from the Zxing Barcode Scanner.
*
* @return {@link Rect} expressing QR code scan area in terms of the preview size
*/
@Override
public Rect getFramingRectInPreview() {
if (framingRectInPreview == null) {
Rect framingRect = getFramingRect();
if (framingRect == null) {
return null;
}
Rect rect = new Rect(framingRect);
Point cameraResolution = getCameraResolution();
if (cameraResolution == null || surfaceWidth == 0 ||
surfaceHeight == 0) {
// Called early, before the surface is ready
return null;
}
rect.left = rect.left * cameraResolution.x / surfaceWidth;
rect.right = rect.right * cameraResolution.x / surfaceWidth;
rect.top = rect.top * cameraResolution.y / surfaceHeight;
rect.bottom = rect.bottom * cameraResolution.y / surfaceHeight;
framingRectInPreview = rect;
}
return framingRectInPreview;
}
private Point getCameraResolution() {
Point ret;
synchronized (cameraResolutionLock) {
ret = new Point(cameraResolution);
}
return ret;
}
/**
* Like {@link #getFramingRect} but coordinates are in terms of the sensor,
* not UI / screen (ie. it is independent of orientation)
*
* @return {@link Rect} expressing QR code scan area in terms of the sensor
*/
private Rect getFramingRectInSensor() {
if (framingRectInSensor == null) {
Rect framingRect = calculateFramingRect(false);
if (framingRect == null) {
return null;
}
Rect rect = new Rect(framingRect);
if (surfaceWidth == 0 || surfaceHeight == 0) {
// Called early, before the surface is ready
return null;
}
rect.left = (rect.left * 2000 / surfaceWidth) - 1000;
rect.right = (rect.right * 2000 / surfaceWidth) - 1000;
rect.top = (rect.top * 2000 / surfaceHeight) - 1000;
rect.bottom = (rect.bottom * 2000 / surfaceHeight) - 1000;
framingRectInSensor = rect;
}
return framingRectInSensor;
}
}

View File

@@ -6,18 +6,14 @@ import android.hardware.Camera.Size;
import android.os.AsyncTask;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
@@ -30,14 +26,11 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private final Reader reader = new QRCodeReader();
private final ResultCallback callback;
private final ResultPointCallback pointCallback;
private boolean stopped = false;
public QrCodeDecoder(ResultCallback callback,
ResultPointCallback pointCallback) {
public QrCodeDecoder(ResultCallback callback) {
this.callback = callback;
this.pointCallback = pointCallback;
}
public void start(Camera camera) {
@@ -79,11 +72,9 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
height, 0, 0, width, height, false);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
Map<DecodeHintType, Object> hints = new HashMap<>();
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, pointCallback);
Result result = null;
try {
result = reader.decode(bitmap, hints);
result = reader.decode(bitmap);
} catch (ReaderException e) {
return null; // No barcode found
} catch (RuntimeException e) {

View File

@@ -1,57 +0,0 @@
package org.briarproject.android.util;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import org.briarproject.R;
import de.hdodenhof.circleimageview.CircleImageView;
public class TextAvatarView extends FrameLayout {
final private AppCompatTextView textView;
final private CircleImageView backgroundView;
public TextAvatarView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater
.inflate(R.layout.text_avatar_view, this, true);
textView = (AppCompatTextView) findViewById(R.id.textAvatarView);
backgroundView = (CircleImageView) findViewById(R.id.avatarBackground);
}
public TextAvatarView(Context context) {
this(context, null);
}
public void setText(String text) {
textView.setText(text);
}
public void setBackgroundBytes(byte[] bytes) {
int r = getByte(bytes, 0) * 3 / 4 + 96;
int g = getByte(bytes, 1) * 3 / 4 + 96;
int b = getByte(bytes, 2) * 3 / 4 + 96;
int color = Color.rgb(r, g, b);
backgroundView.setFillColor(color);
}
private byte getByte(byte[] bytes, int index) {
if (bytes == null) {
return -128;
} else {
return bytes[index % bytes.length];
}
}
}

View File

@@ -1,214 +0,0 @@
/*
* Copyright (C) 2008 ZXing authors
* Copyright (C) 2016 Sublime Software Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.briarproject.android.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.google.zxing.ResultPoint;
import org.briarproject.R;
import java.util.ArrayList;
import java.util.List;
/**
* This view is overlaid on top of the camera preview. It adds the viewfinder
* rectangle and partial transparency outside it, as well as the laser scanner
* animation and result points.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ViewfinderView extends View {
private static final int[] SCANNER_ALPHA =
{0, 64, 128, 192, 255, 192, 128, 64};
private static final long ANIMATION_DELAY = 80L;
private static final int CURRENT_POINT_OPACITY = 0xA0;
private static final int MAX_RESULT_POINTS = 20;
private static final int POINT_SIZE = 6;
private FrameProvider frameProvider;
private final Paint paint;
private Bitmap resultBitmap;
private final int maskColor;
private final int resultColor;
private final int laserColor;
private final int resultPointColor;
private int scannerAlpha;
private List<ResultPoint> possibleResultPoints;
private List<ResultPoint> lastPossibleResultPoints;
// This constructor is used when the class is built from an XML resource.
public ViewfinderView(Context context, AttributeSet attrs) {
super(context, attrs);
if (isInEditMode()) {
paint = null;
maskColor = 0;
resultColor = 0;
laserColor = 0;
resultPointColor = 0;
return;
}
// Initialize these once for performance rather than calling them every
// time in onDraw().
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Resources resources = getResources();
maskColor = resources.getColor(R.color.viewfinder_mask);
resultColor = resources.getColor(R.color.result_view);
laserColor = resources.getColor(R.color.viewfinder_laser);
resultPointColor = resources.getColor(R.color.possible_result_points);
scannerAlpha = 0;
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = null;
}
public void setFrameProvider(FrameProvider frameProvider) {
this.frameProvider = frameProvider;
}
@SuppressLint("DrawAllocation")
@Override
public void onDraw(Canvas canvas) {
if (frameProvider == null) {
return; // not ready yet, early draw before done configuring
}
Rect frame = this.frameProvider.getFramingRect();
Rect previewFrame = this.frameProvider.getFramingRectInPreview();
if (frame == null || previewFrame == null) {
return;
}
int width = canvas.getWidth();
int height = canvas.getHeight();
// Draw the exterior (i.e. outside the framing rect) darkened
paint.setColor(resultBitmap != null ? resultColor : maskColor);
canvas.drawRect(0, 0, width, frame.top, paint);
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,
paint);
canvas.drawRect(0, frame.bottom + 1, width, height, paint);
if (resultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
paint.setAlpha(CURRENT_POINT_OPACITY);
canvas.drawBitmap(resultBitmap, null, frame, paint);
} else {
// Draw a red "laser scanner" line through the middle to show
// decoding is active
paint.setColor(laserColor);
paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = frame.height() / 2 + frame.top;
canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1,
middle + 2, paint);
float scaleX = frame.width() / (float) previewFrame.width();
float scaleY = frame.height() / (float) previewFrame.height();
List<ResultPoint> currentPossible = possibleResultPoints;
List<ResultPoint> currentLast = lastPossibleResultPoints;
int frameLeft = frame.left;
int frameTop = frame.top;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(CURRENT_POINT_OPACITY);
paint.setColor(resultPointColor);
synchronized (currentPossible) {
for (ResultPoint point : currentPossible) {
canvas.drawCircle(
frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
POINT_SIZE, paint);
}
}
}
if (currentLast != null) {
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
paint.setColor(resultPointColor);
synchronized (currentLast) {
float radius = POINT_SIZE / 2.0f;
for (ResultPoint point : currentLast) {
canvas.drawCircle(
frameLeft + (int) (point.getX() * scaleX),
frameTop + (int) (point.getY() * scaleY),
radius, paint);
}
}
}
// Request another update at the animation interval, but only
// repaint the laser line, not the entire viewfinder mask.
postInvalidateDelayed(ANIMATION_DELAY,
frame.left - POINT_SIZE,
frame.top - POINT_SIZE,
frame.right + POINT_SIZE,
frame.bottom + POINT_SIZE);
}
}
public void drawViewfinder() {
Bitmap resultBitmap = this.resultBitmap;
this.resultBitmap = null;
if (resultBitmap != null) {
resultBitmap.recycle();
}
invalidate();
}
/**
* Draw a bitmap with the result points highlighted instead of the live
* scanning display.
*
* @param barcode An image of the decoded barcode.
*/
public void drawResultBitmap(Bitmap barcode) {
resultBitmap = barcode;
invalidate();
}
public void addPossibleResultPoint(ResultPoint point) {
List<ResultPoint> points = possibleResultPoints;
synchronized (points) {
points.add(point);
int size = points.size();
if (size > MAX_RESULT_POINTS) {
// trim it
points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
}
}
}
public interface FrameProvider {
Rect getFramingRect();
Rect getFramingRectInPreview();
}
}

View File

@@ -4,6 +4,10 @@ targetCompatibility = 1.6
apply plugin: 'witness'
repositories {
jcenter()
}
dependencies {
compile "com.google.dagger:dagger:2.0.2"
compile 'com.google.dagger:dagger-compiler:2.0.2'

View File

@@ -1,28 +0,0 @@
package org.briarproject.api.clients;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.sync.BaseMessageContext;
import org.briarproject.api.sync.MessageId;
import java.util.Collection;
public class BdfMessageContext extends BaseMessageContext {
private final BdfDictionary dictionary;
public BdfMessageContext(BdfDictionary dictionary,
Collection<MessageId> dependencies) {
super(dependencies);
this.dictionary = dictionary;
}
public BdfMessageContext(BdfDictionary dictionary) {
this(dictionary, null);
}
public BdfDictionary getDictionary() {
return dictionary;
}
}

View File

@@ -5,8 +5,6 @@ import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.InvalidMessageException;
import org.briarproject.api.sync.MessageContext;
public interface MessageQueueManager {
@@ -36,11 +34,10 @@ public interface MessageQueueManager {
interface QueueMessageValidator {
/**
* Validates the given message and returns its metadata and
* dependencies.
* Validates the given message and returns its metadata if the message
* is valid, or null if the message is invalid.
*/
MessageContext validateMessage(QueueMessage q, Group g)
throws InvalidMessageException;
Metadata validateMessage(QueueMessage q, Group g);
}
interface IncomingQueueMessageHook {

View File

@@ -1,24 +0,0 @@
package org.briarproject.api.event;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.introduction.IntroductionRequest;
public class ForumInvitationReceivedEvent extends Event {
private final Forum forum;
private final ContactId contactId;
public ForumInvitationReceivedEvent(Forum forum, ContactId contactId) {
this.forum = forum;
this.contactId = contactId;
}
public Forum getForum() {
return forum;
}
public ContactId getContactId() {
return contactId;
}
}

View File

@@ -1,24 +0,0 @@
package org.briarproject.api.event;
import org.briarproject.api.contact.ContactId;
public class ForumInvitationResponseReceivedEvent extends Event {
private final String forumName;
private final ContactId contactId;
public ForumInvitationResponseReceivedEvent(String forumName,
ContactId contactId) {
this.forumName = forumName;
this.contactId = contactId;
}
public String getForumName() {
return forumName;
}
public ContactId getContactId() {
return contactId;
}
}

View File

@@ -1,43 +0,0 @@
package org.briarproject.api.forum;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
public class Forum {
private final Group group;
private final String name;
private final byte[] salt;
public Forum(Group group, String name, byte[] salt) {
this.group = group;
this.name = name;
this.salt = salt;
}
public GroupId getId() {
return group.getId();
}
public Group getGroup() {
return group;
}
public String getName() {
return name;
}
public byte[] getSalt() {
return salt;
}
@Override
public int hashCode() {
return group.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof Forum && group.equals(((Forum) o).group);
}
}

View File

@@ -1,52 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
public interface ForumConstants {
/** The maximum length of a forum's name in UTF-8 bytes. */
int MAX_FORUM_NAME_LENGTH = 100;
/** The length of a forum's random salt in bytes. */
int FORUM_SALT_LENGTH = 32;
/** The maximum length of a forum post's content type in UTF-8 bytes. */
int MAX_CONTENT_TYPE_LENGTH = 50;
/** The maximum length of a forum post's body in bytes. */
int MAX_FORUM_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
/* Forum Sharing Constants */
String CONTACT_ID = "contactId";
String GROUP_ID = "groupId";
String TO_BE_SHARED_BY_US = "toBeSharedByUs";
String SHARED_BY_US = "sharedByUs";
String SHARED_WITH_US = "sharedWithUs";
String TYPE = "type";
String SESSION_ID = "sessionId";
String STORAGE_ID = "storageId";
String STATE = "state";
String LOCAL = "local";
String TIME = "time";
String READ = "read";
String IS_SHARER = "isSharer";
String FORUM_ID = "forumId";
String FORUM_NAME = "forumName";
String FORUM_SALT = "forumSalt";
String INVITATION_MSG = "invitationMsg";
int SHARE_MSG_TYPE_INVITATION = 1;
int SHARE_MSG_TYPE_ACCEPT = 2;
int SHARE_MSG_TYPE_DECLINE = 3;
int SHARE_MSG_TYPE_LEAVE = 4;
int SHARE_MSG_TYPE_ABORT = 5;
String TASK = "task";
int TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US = 0;
int TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US = 1;
int TASK_ADD_SHARED_FORUM = 2;
int TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US = 3;
int TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US = 4;
int TASK_SHARE_FORUM = 5;
int TASK_UNSHARE_FORUM_SHARED_BY_US = 6;
int TASK_UNSHARE_FORUM_SHARED_WITH_US = 7;
}

View File

@@ -1,11 +0,0 @@
package org.briarproject.api.forum;
public interface ForumFactory {
/** Creates a forum with the given name. */
Forum createForum(String name);
/** Creates a forum with the given name and salt. */
Forum createForum(String name, byte[] salt);
}

View File

@@ -1,48 +0,0 @@
package org.briarproject.api.forum;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.messaging.BaseMessage;
import org.briarproject.api.sync.MessageId;
public class ForumInvitationMessage extends BaseMessage {
private final SessionId sessionId;
private final ContactId contactId;
private final String forumName, message;
private final boolean available;
public ForumInvitationMessage(MessageId id, SessionId sessionId,
ContactId contactId, String forumName, String message,
boolean available, long time, boolean local, boolean sent,
boolean seen, boolean read) {
super(id, time, local, read, sent, seen);
this.sessionId = sessionId;
this.contactId = contactId;
this.forumName = forumName;
this.message = message;
this.available = available;
}
public SessionId getSessionId() {
return sessionId;
}
public ContactId getContactId() {
return contactId;
}
public String getForumName() {
return forumName;
}
public String getMessage() {
return message;
}
public boolean isAvailable() {
return available;
}
}

View File

@@ -1,49 +0,0 @@
package org.briarproject.api.forum;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.util.Collection;
public interface ForumManager {
/** Returns the unique ID of the forum client. */
ClientId getClientId();
/** Subscribes to a forum. */
Forum addForum(String name) throws DbException;
/** Unsubscribes from a forum. */
void removeForum(Forum f) throws DbException;
/** Stores a local forum post. */
void addLocalPost(ForumPost p) throws DbException;
/** Returns the forum with the given ID. */
Forum getForum(GroupId g) throws DbException;
/** Returns the forum with the given ID. */
Forum getForum(Transaction txn, GroupId g) throws DbException;
/** Returns all forums to which the user subscribes. */
Collection<Forum> getForums() throws DbException;
/** Returns the body of the forum post with the given ID. */
byte[] getPostBody(MessageId m) throws DbException;
/** Returns the headers of all posts in the given forum. */
Collection<ForumPostHeader> getPostHeaders(GroupId g) throws DbException;
/** Marks a forum post as read or unread. */
void setReadFlag(MessageId m, boolean read) throws DbException;
/** Registers a hook to be called whenever a forum is removed. */
void registerRemoveForumHook(RemoveForumHook hook);
interface RemoveForumHook {
void removingForum(Transaction txn, Forum f) throws DbException;
}
}

View File

@@ -1,37 +0,0 @@
package org.briarproject.api.forum;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
public class ForumPost {
private final Message message;
private final MessageId parent;
private final Author author;
private final String contentType;
public ForumPost(Message message, MessageId parent, Author author,
String contentType) {
this.message = message;
this.parent = parent;
this.author = author;
this.contentType = contentType;
}
public Message getMessage() {
return message;
}
public MessageId getParent() {
return parent;
}
public Author getAuthor() {
return author;
}
public String getContentType() {
return contentType;
}
}

View File

@@ -1,21 +0,0 @@
package org.briarproject.api.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import java.security.GeneralSecurityException;
public interface ForumPostFactory {
ForumPost createAnonymousPost(GroupId groupId, long timestamp,
MessageId parent, String contentType, byte[] body)
throws FormatException;
ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
MessageId parent, Author author, String contentType, byte[] body,
PrivateKey privateKey) throws FormatException,
GeneralSecurityException;
}

View File

@@ -1,48 +0,0 @@
package org.briarproject.api.forum;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.MessageId;
public class ForumPostHeader {
private final MessageId id;
private final long timestamp;
private final Author author;
private final Author.Status authorStatus;
private final String contentType;
private final boolean read;
public ForumPostHeader(MessageId id, long timestamp, Author author,
Author.Status authorStatus, String contentType, boolean read) {
this.id = id;
this.timestamp = timestamp;
this.author = author;
this.authorStatus = authorStatus;
this.contentType = contentType;
this.read = read;
}
public MessageId getId() {
return id;
}
public Author getAuthor() {
return author;
}
public Author.Status getAuthorStatus() {
return authorStatus;
}
public String getContentType() {
return contentType;
}
public long getTimestamp() {
return timestamp;
}
public boolean isRead() {
return read;
}
}

Some files were not shown because too many files have changed in this diff Show More