Removed forums for beta release.

This commit is contained in:
akwizgran
2016-05-13 12:32:09 +01:00
parent b818d6846c
commit 62eda5cd91
86 changed files with 10 additions and 7594 deletions

View File

@@ -1,780 +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.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;
SyncSessionFactory sync0, sync1;
ForumManager forumManager0, forumManager1;
ContactManager contactManager0, contactManager1;
ContactId contactId0, contactId1;
IdentityManager identityManager0, identityManager1;
LocalAuthor author0, author1;
Forum forum0;
SharerListener listener0;
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 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 static final Logger LOG =
Logger.getLogger(ForumSharingIntegrationTest.class.getName());
private ForumSharingIntegrationTestComponent t0, t1;
@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);
identityManager0 = t0.getIdentityManager();
identityManager1 = t1.getIdentityManager();
contactManager0 = t0.getContactManager();
contactManager1 = t1.getContactManager();
forumManager0 = t0.getForumManager();
forumManager1 = t1.getForumManager();
forumSharingManager0 = t0.getForumSharingManager();
forumSharingManager1 = t1.getForumSharingManager();
sync0 = t0.getSyncSessionFactory();
sync1 = t1.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
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(contactId1));
// 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(contactId1));
// invitee no longer gets forum shared by sharer
assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0));
// forum can be shared again
Contact c1 = contactManager0.getContact(contactId1);
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
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
.contains(contactId1));
// 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
assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
.contains(contactId0));
// sharer no longer gets forum shared by invitee
assertFalse(forumSharingManager1.getSharedBy(forum0.getId())
.contains(contact0));
// forum can be shared again
Contact c0 = contactManager1.getContact(contactId0);
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);
contactManager1.removeContact(contactId0);
// 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();
}
}
@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 {
forumSharingManager0.respondToInvitation(f, 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;
InviteeListener(boolean accept) {
this.accept = accept;
}
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;
eventWaiter.assertEquals(contactId0, event.getContactId());
requestReceived = true;
Forum f = event.getForum();
// work-around because the forum does not contain the group
f = forumManager1.createForum(f.getName(), f.getSalt());
try {
forumSharingManager1.respondToInvitation(f, 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();
lifecycleManager0.startServices();
lifecycleManager1.startServices();
lifecycleManager0.waitForStartup();
lifecycleManager1.waitForStartup();
}
private void stopLifecycles() throws InterruptedException {
// Clean up
lifecycleManager0.stopServices();
lifecycleManager1.stopServices();
lifecycleManager0.waitForShutdown();
lifecycleManager1.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);
}
private void addDefaultContacts() throws DbException {
// sharer adds invitee as contact
contactId1 = contactManager0.addContact(author1,
author0.getId(), master, clock.currentTimeMillis(), true,
true
);
// invitee adds sharer back
contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
}
private void addForumForSharer() throws DbException {
// sharer creates forum
forum0 = forumManager0.createForum("Test Forum");
forumManager0.addForum(forum0);
}
private void listenToEvents(boolean accept) {
listener0 = new SharerListener();
t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(accept);
t1.getEventBus().addListener(listener1);
}
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

@@ -2,11 +2,6 @@ package org.briarproject;
import org.briarproject.api.UniqueId; import org.briarproject.api.UniqueId;
import org.briarproject.api.crypto.CryptoComponent; 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.identity.AuthorFactory;
import org.briarproject.api.messaging.MessagingConstants; import org.briarproject.api.messaging.MessagingConstants;
import org.briarproject.api.messaging.PrivateMessage; import org.briarproject.api.messaging.PrivateMessage;
@@ -18,9 +13,6 @@ import org.junit.Test;
import javax.inject.Inject; 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.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -33,8 +25,6 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
AuthorFactory authorFactory; AuthorFactory authorFactory;
@Inject @Inject
PrivateMessageFactory privateMessageFactory; PrivateMessageFactory privateMessageFactory;
@Inject
ForumPostFactory forumPostFactory;
public MessageSizeIntegrationTest() throws Exception { public MessageSizeIntegrationTest() throws Exception {
MessageSizeIntegrationTestComponent component = MessageSizeIntegrationTestComponent component =
@@ -61,30 +51,4 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
+ MAX_PRIVATE_MESSAGE_BODY_LENGTH); + MAX_PRIVATE_MESSAGE_BODY_LENGTH);
assertTrue(length <= MAX_PACKET_PAYLOAD_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.data.DataModule;
import org.briarproject.db.DatabaseModule; import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule; import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule; import org.briarproject.identity.IdentityModule;
import org.briarproject.messaging.MessagingModule; import org.briarproject.messaging.MessagingModule;
import org.briarproject.sync.SyncModule; import org.briarproject.sync.SyncModule;
@@ -25,7 +24,6 @@ import dagger.Component;
DataModule.class, DataModule.class,
DatabaseModule.class, DatabaseModule.class,
EventModule.class, EventModule.class,
ForumModule.class,
IdentityModule.class, IdentityModule.class,
MessagingModule.class, MessagingModule.class,
SyncModule.class, SyncModule.class,

View File

@@ -98,68 +98,6 @@
/> />
</activity> </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.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 <activity
android:name=".android.identity.CreateIdentityActivity" android:name=".android.identity.CreateIdentityActivity"
android:label="@string/new_identity_title" android:label="@string/new_identity_title"

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,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,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

@@ -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

@@ -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

@@ -46,21 +46,6 @@
android:layout_height="@dimen/nav_separator_height" android:layout_height="@dimen/nav_separator_height"
android:layout_marginLeft="@dimen/margin_large"/> 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.support.v7.widget.AppCompatButton
android:id="@+id/nav_btn_settings" android:id="@+id/nav_btn_settings"
style="@style/NavMenuButton" 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,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
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">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/avatarBackground"
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"
app:civ_border_color="@color/briar_primary"
app:civ_border_width="@dimen/avatar_border_width"/>
<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,24 +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_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="private_message_date_inverse">#e0e0e0</color>
<color name="unread_background">#FFFFFF</color> <color name="unread_background">#FFFFFF</color>
<color name="horizontal_border">#CCCCCC</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="no_private_messages">#AAAAAA</color>
<color name="forum_avatar_shadow">#b3b3b3</color>
<color name="briar_primary">@color/briar_blue</color> <color name="briar_primary">@color/briar_blue</color>
<color name="briar_primary_dark">@color/briar_blue_dark</color> <color name="briar_primary_dark">@color/briar_blue_dark</color>

View File

@@ -35,7 +35,6 @@
<string name="contact_list_button">Contacts</string> <string name="contact_list_button">Contacts</string>
<string name="delete_contact">Delete contact</string> <string name="delete_contact">Delete contact</string>
<string name="contact_deleted_toast">Contact deleted</string> <string name="contact_deleted_toast">Contact deleted</string>
<string name="forums_button">Forums</string>
<string name="settings_button">Settings</string> <string name="settings_button">Settings</string>
<string name="sign_out_button">Sign Out</string> <string name="sign_out_button">Sign Out</string>
<string name="contact_list_title">Contacts</string> <string name="contact_list_title">Contacts</string>
@@ -66,63 +65,26 @@
<string name="qr_code_invalid">The QR code is invalid</string> <string name="qr_code_invalid">The QR code is invalid</string>
<string name="connecting_to_device">Connecting to device\u2026</string> <string name="connecting_to_device">Connecting to device\u2026</string>
<string name="authenticating_with_device">Authenticating with 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_local">Connection failed</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_remote">Connection failed</string>
<string name="no_private_messages">No messages</string> <string name="no_private_messages">No messages</string>
<string name="private_message_hint">Type message</string> <string name="private_message_hint">Type message</string>
<string name="message_sent_toast">Message sent</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="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="anonymous">Anonymous</string>
<string name="new_identity_item">New identity\u2026</string> <string name="new_identity_item">New identity\u2026</string>
<string name="new_identity_title">New Identity</string> <string name="new_identity_title">New Identity</string>
<string name="create_identity_button">Create Identity</string> <string name="create_identity_button">Create Identity</string>
<string name="identity_created_toast">Identity created</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="shared_by_format">Shared by %s</string>
<string name="no_contacts_prompt">You don\'t have any contacts. Add a contact now?</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="add_button">Add</string>
<string name="cancel_button">Cancel</string> <string name="cancel_button">Cancel</string>
<string name="done_button">Done</string> <string name="done_button">Done</string>
<string name="delete_button">Delete</string> <string name="delete_button">Delete</string>
<string name="post_sent_toast">Forum post sent</string>
<plurals name="private_message_notification_text"> <plurals name="private_message_notification_text">
<item quantity="one">New private message.</item> <item quantity="one">New private message.</item>
<item quantity="other">%d new private messages.</item> <item quantity="other">%d new private messages.</item>
</plurals> </plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">New forum post.</item>
<item quantity="other">%d new forum posts.</item>
</plurals>
<!-- Settings --> <!-- Settings -->
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>
@@ -138,7 +100,6 @@
<string name="panic_setting_hint">Configure how Briar will react when you use a panic button app</string> <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="notification_settings_title">Notifications</string>
<string name="notify_private_messages_setting">Show alerts for private messages</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_vibration_setting">Vibrate</string>
<string name="notify_sound_setting">Sound</string> <string name="notify_sound_setting">Sound</string>
<string name="notify_sound_setting_default">Default ringtone</string> <string name="notify_sound_setting_default">Default ringtone</string>
@@ -205,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_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_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_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_ok">OK</string>
<string name="dialog_button_leave">Leave</string> <string name="dialog_button_leave">Leave</string>
<string name="dialog_button_introduce">Introduce</string> <string name="dialog_button_introduce">Introduce</string>
@@ -216,8 +175,6 @@
<string name="dashboard_toolbar_header">Briar</string> <string name="dashboard_toolbar_header">Briar</string>
<string name="settings_toolbar_header">Settings</string> <string name="settings_toolbar_header">Settings</string>
<string name="contacts_toolbar_header">Contacts</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 --> <!-- Progress titles -->
<string name="progress_title_logout">Signing out of Briar..</string> <string name="progress_title_logout">Signing out of Briar..</string>
<string name="progress_title_please_wait">Please wait..</string> <string name="progress_title_please_wait">Please wait..</string>

View File

@@ -49,12 +49,6 @@
android:persistent="false" android:persistent="false"
android:title="@string/notify_private_messages_setting"/> 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 <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="pref_key_notify_vibration" android:key="pref_key_notify_vibration"

View File

@@ -3,14 +3,6 @@ package org.briarproject.android;
import android.app.Activity; import android.app.Activity;
import org.briarproject.android.contact.ConversationActivity; 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.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.fragment.BaseFragment;
import org.briarproject.android.identity.CreateIdentityActivity; import org.briarproject.android.identity.CreateIdentityActivity;
import org.briarproject.android.introduction.IntroductionActivity; import org.briarproject.android.introduction.IntroductionActivity;
@@ -51,18 +43,6 @@ public interface ActivityComponent {
void inject(CreateIdentityActivity activity); void inject(CreateIdentityActivity activity);
void inject(AvailableForumsActivity activity);
void inject(WriteForumPostActivity activity);
void inject(CreateForumActivity activity);
void inject(ShareForumActivity activity);
void inject(ReadForumPostActivity activity);
void inject(ForumActivity activity);
void inject(SettingsActivity activity); void inject(SettingsActivity activity);
void inject(IntroductionActivity activity); void inject(IntroductionActivity activity);
@@ -70,9 +50,6 @@ public interface ActivityComponent {
@Named("ContactListFragment") @Named("ContactListFragment")
BaseFragment newContactListFragment(); BaseFragment newContactListFragment();
@Named("ForumListFragment")
BaseFragment newForumListFragment();
@Named("ChooseIdentityFragment") @Named("ChooseIdentityFragment")
BaseFragment newChooseIdentityFragment(); BaseFragment newChooseIdentityFragment();
@@ -82,12 +59,6 @@ public interface ActivityComponent {
@Named("ContactChooserFragment") @Named("ContactChooserFragment")
BaseFragment newContactChooserFragment(); BaseFragment newContactChooserFragment();
@Named("ContactSelectorFragment")
ContactSelectorFragment newContactSelectorFragment();
@Named("ShareForumMessageFragment")
ShareForumMessageFragment newShareForumMessageFragment();
@Named("IntroductionMessageFragment") @Named("IntroductionMessageFragment")
IntroductionMessageFragment newIntroductionMessageFragment(); 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.SetupController;
import org.briarproject.android.controller.SetupControllerImpl; import org.briarproject.android.controller.SetupControllerImpl;
import org.briarproject.android.controller.TransportStateListener; 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.fragment.BaseFragment;
import org.briarproject.android.introduction.ContactChooserFragment; import org.briarproject.android.introduction.ContactChooserFragment;
import org.briarproject.android.introduction.IntroductionMessageFragment; import org.briarproject.android.introduction.IntroductionMessageFragment;
@@ -116,13 +113,6 @@ public class ActivityModule {
return new BriarServiceConnection(); return new BriarServiceConnection();
} }
@Provides
@Named("ForumListFragment")
BaseFragment provideForumListFragment(ForumListFragment fragment) {
fragment.setArguments(new Bundle());
return fragment;
}
@Provides @Provides
@Named("ContactListFragment") @Named("ContactListFragment")
BaseFragment provideContactListFragment(ContactListFragment fragment) { BaseFragment provideContactListFragment(ContactListFragment fragment) {
@@ -153,22 +143,6 @@ public class ActivityModule {
return fragment; 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 @Provides
@Named("IntroductionMessageFragment") @Named("IntroductionMessageFragment")
IntroductionMessageFragment provideIntroductionMessageFragment( 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.DatabaseConfig;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.event.EventBus; 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.AuthorFactory;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.introduction.IntroductionManager;
@@ -89,12 +86,6 @@ public interface AndroidComponent extends CoreEagerSingletons {
TransportPropertyManager transportPropertyManager(); TransportPropertyManager transportPropertyManager();
ForumManager forumManager();
ForumSharingManager forumSharingManager();
ForumPostFactory forumPostFactory();
SettingsManager settingsManager(); SettingsManager settingsManager();
ContactExchangeTask contactExchangeTask(); ContactExchangeTask contactExchangeTask();

View File

@@ -13,20 +13,17 @@ import org.briarproject.R;
import org.briarproject.android.api.AndroidExecutor; import org.briarproject.android.api.AndroidExecutor;
import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.contact.ConversationActivity; import org.briarproject.android.contact.ConversationActivity;
import org.briarproject.android.forum.ForumActivity;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent; import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent; import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.IntroductionSucceededEvent; import org.briarproject.api.event.IntroductionSucceededEvent;
import org.briarproject.api.event.MessageValidatedEvent; import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.event.SettingsUpdatedEvent; import org.briarproject.api.event.SettingsUpdatedEvent;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.lifecycle.Service; import org.briarproject.api.lifecycle.Service;
import org.briarproject.api.lifecycle.ServiceException; import org.briarproject.api.lifecycle.ServiceException;
import org.briarproject.api.messaging.MessagingManager; 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_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_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_MESSAGE;
import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.BriarActivity.GROUP_ID;
@@ -64,12 +60,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
Service, EventListener { Service, EventListener {
private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3; 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 int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 5;
private static final String CONTACT_URI = private static final String CONTACT_URI =
"content://org.briarproject/contact"; "content://org.briarproject/contact";
private static final String FORUM_URI =
"content://org.briarproject/forum";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidNotificationManagerImpl.class.getName()); Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
@@ -77,16 +70,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
private final Executor dbExecutor; private final Executor dbExecutor;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ForumManager forumManager;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Context appContext; private final Context appContext;
// The following must only be accessed on the main UI thread // The following must only be accessed on the main UI thread
private final Map<GroupId, Integer> contactCounts = new HashMap<>(); private final Map<GroupId, Integer> contactCounts = new HashMap<>();
private final Map<GroupId, Integer> forumCounts = new HashMap<>();
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
private int contactTotal = 0, forumTotal = 0; private int contactTotal = 0;
private int nextRequestId = 0; private int nextRequestId = 0;
private GroupId visibleGroup = null; private GroupId visibleGroup = null;
@@ -95,12 +86,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Inject @Inject
public AndroidNotificationManagerImpl(@DatabaseExecutor Executor dbExecutor, public AndroidNotificationManagerImpl(@DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager, MessagingManager messagingManager, SettingsManager settingsManager, MessagingManager messagingManager,
ForumManager forumManager, AndroidExecutor androidExecutor, AndroidExecutor androidExecutor, Application app) {
Application app) {
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.forumManager = forumManager;
this.androidExecutor = androidExecutor; this.androidExecutor = androidExecutor;
appContext = app.getApplicationContext(); appContext = app.getApplicationContext();
} }
@@ -121,7 +110,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Override @Override
public Void call() { public Void call() {
clearPrivateMessageNotification(); clearPrivateMessageNotification();
clearForumPostNotification();
clearIntroductionSuccessNotification(); clearIntroductionSuccessNotification();
return null; return null;
} }
@@ -139,12 +127,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID); 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() { private void clearIntroductionSuccessNotification() {
Object o = appContext.getSystemService(NOTIFICATION_SERVICE); Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o; NotificationManager nm = (NotificationManager) o;
@@ -162,8 +144,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
ClientId c = m.getClientId(); ClientId c = m.getClientId();
if (c.equals(messagingManager.getClientId())) if (c.equals(messagingManager.getClientId()))
showPrivateMessageNotification(m.getMessage().getGroupId()); showPrivateMessageNotification(m.getMessage().getGroupId());
else if (c.equals(forumManager.getClientId()))
showForumPostNotification(m.getMessage().getGroupId());
} }
} else if (e instanceof IntroductionRequestReceivedEvent) { } else if (e instanceof IntroductionRequestReceivedEvent) {
ContactId c = ((IntroductionRequestReceivedEvent) e).getContactId(); ContactId c = ((IntroductionRequestReceivedEvent) e).getContactId();
@@ -174,9 +154,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
} else if (e instanceof IntroductionSucceededEvent) { } else if (e instanceof IntroductionSucceededEvent) {
Contact c = ((IntroductionSucceededEvent) e).getContact(); Contact c = ((IntroductionSucceededEvent) e).getContact();
showIntroductionSucceededNotification(c); showIntroductionSucceededNotification(c);
} else if (e instanceof ForumInvitationReceivedEvent) {
ContactId c = ((ForumInvitationReceivedEvent) e).getContactId();
showNotificationForPrivateConversation(c);
} }
} }
@@ -282,82 +259,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
return defaults; 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 @Override
public void blockNotification(final GroupId g) { public void blockNotification(final GroupId g) {
androidExecutor.execute(new Runnable() { 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.R;
import org.briarproject.android.contact.ContactListFragment; import org.briarproject.android.contact.ContactListFragment;
import org.briarproject.android.forum.ForumListFragment;
import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.fragment.BaseFragment;
/** /**
@@ -24,8 +23,6 @@ public abstract class BriarFragmentActivity extends BriarActivity {
if (fragmentTag.equals(ContactListFragment.TAG)) { if (fragmentTag.equals(ContactListFragment.TAG)) {
actionBar.setTitle(R.string.contacts_toolbar_header); actionBar.setTitle(R.string.contacts_toolbar_header);
} else if (fragmentTag.equals(ForumListFragment.TAG)) {
actionBar.setTitle(R.string.forums_toolbar_header);
} }
} }

View File

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

View File

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

View File

@@ -29,8 +29,6 @@ import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageValidatedEvent; 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.IdentityManager;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.introduction.IntroductionManager;
@@ -76,8 +74,6 @@ public class ContactListFragment extends BaseFragment implements EventListener {
protected volatile MessagingManager messagingManager; protected volatile MessagingManager messagingManager;
@Inject @Inject
protected volatile IntroductionManager introductionManager; protected volatile IntroductionManager introductionManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
@Inject @Inject
public ContactListFragment() { public ContactListFragment() {
@@ -225,8 +221,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
MessageValidatedEvent m = (MessageValidatedEvent) e; MessageValidatedEvent m = (MessageValidatedEvent) e;
ClientId c = m.getClientId(); ClientId c = m.getClientId();
if (m.isValid() && (c.equals(messagingManager.getClientId()) || if (m.isValid() && (c.equals(messagingManager.getClientId()) ||
c.equals(introductionManager.getClientId()) || c.equals(introductionManager.getClientId()))) {
c.equals(forumSharingManager.getClientId()))) {
LOG.info("Message added, reloading"); LOG.info("Message added, reloading");
reloadConversation(m.getMessage().getGroupId()); reloadConversation(m.getMessage().getGroupId());
} }
@@ -320,16 +315,6 @@ public class ContactListFragment extends BaseFragment implements EventListener {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading introduction messages took " + duration + " ms"); 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; return messages;
} }
} }

View File

@@ -43,14 +43,11 @@ import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.event.IntroductionRequestReceivedEvent; import org.briarproject.api.event.IntroductionRequestReceivedEvent;
import org.briarproject.api.event.IntroductionResponseReceivedEvent; import org.briarproject.api.event.IntroductionResponseReceivedEvent;
import org.briarproject.api.event.MessageValidatedEvent; import org.briarproject.api.event.MessageValidatedEvent;
import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.event.MessagesAckedEvent;
import org.briarproject.api.event.MessagesSentEvent; 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.IntroductionManager;
import org.briarproject.api.introduction.IntroductionMessage; import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionRequest;
@@ -122,8 +119,6 @@ public class ConversationActivity extends BriarActivity
protected volatile PrivateMessageFactory privateMessageFactory; protected volatile PrivateMessageFactory privateMessageFactory;
@Inject @Inject
protected volatile IntroductionManager introductionManager; protected volatile IntroductionManager introductionManager;
@Inject
protected volatile ForumSharingManager forumSharingManager;
private volatile GroupId groupId = null; private volatile GroupId groupId = null;
private volatile ContactId contactId = null; private volatile ContactId contactId = null;
@@ -297,13 +292,10 @@ public class ConversationActivity extends BriarActivity
Collection<IntroductionMessage> introductions = Collection<IntroductionMessage> introductions =
introductionManager introductionManager
.getIntroductionMessages(contactId); .getIntroductionMessages(contactId);
Collection<ForumInvitationMessage> invitations =
forumSharingManager
.getForumInvitationMessages(contactId);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading headers took " + duration + " ms"); LOG.info("Loading headers took " + duration + " ms");
displayMessages(headers, introductions, invitations); displayMessages(headers, introductions);
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
finishOnUiThread(); finishOnUiThread();
} catch (DbException e) { } catch (DbException e) {
@@ -315,14 +307,12 @@ public class ConversationActivity extends BriarActivity
} }
private void displayMessages(final Collection<PrivateMessageHeader> headers, private void displayMessages(final Collection<PrivateMessageHeader> headers,
final Collection<IntroductionMessage> introductions, final Collection<IntroductionMessage> introductions) {
final Collection<ForumInvitationMessage> invitations) {
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
sendButton.setEnabled(true); sendButton.setEnabled(true);
if (headers.isEmpty() && introductions.isEmpty() && if (headers.isEmpty() && introductions.isEmpty()) {
invitations.isEmpty()) {
// we have no messages, // we have no messages,
// so let the list know to hide progress bar // so let the list know to hide progress bar
list.showData(); list.showData();
@@ -350,10 +340,6 @@ public class ConversationActivity extends BriarActivity
} }
items.add(item); items.add(item);
} }
for (ForumInvitationMessage i : invitations) {
ConversationItem item = ConversationItem.from(i);
items.add(item);
}
adapter.addAll(items); adapter.addAll(items);
// Scroll to the bottom // Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1); list.scrollToPosition(adapter.getItemCount() - 1);
@@ -507,12 +493,6 @@ public class ConversationActivity extends BriarActivity
ConversationItem.from(this, contactName, ir); ConversationItem.from(this, contactName, ir);
addIntroduction(item); 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; package org.briarproject.android.contact;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.support.v7.util.SortedList; import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils; import android.text.format.DateUtils;
@@ -14,9 +13,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.android.forum.AvailableForumsActivity;
import org.briarproject.api.clients.SessionId; import org.briarproject.api.clients.SessionId;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.util.StringUtils; 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.util.SortedList.INVALID_POSITION;
import static android.support.v7.widget.RecyclerView.ViewHolder; 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_IN;
import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_OUT; import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_OUT;
import static org.briarproject.android.contact.ConversationItem.IncomingItem; import static org.briarproject.android.contact.ConversationItem.IncomingItem;
@@ -87,14 +82,6 @@ class ConversationAdapter extends RecyclerView.Adapter {
v = LayoutInflater.from(viewGroup.getContext()).inflate( v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_notice_out, viewGroup, false); R.layout.list_item_notice_out, viewGroup, false);
return new NoticeHolder(v, type); 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) // incoming message (non-local)
else { else {
@@ -119,12 +106,6 @@ class ConversationAdapter extends RecyclerView.Adapter {
bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item); bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item);
} else if (item instanceof ConversationNoticeInItem) { } else if (item instanceof ConversationNoticeInItem) {
bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item); 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 { } else {
throw new IllegalArgumentException("Unhandled Conversation Item"); 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 @Override
public int getItemCount() { public int getItemCount() {
return items.size(); 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> { private class ListCallbacks extends SortedList.Callback<ConversationItem> {
@Override @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 android.content.Context;
import org.briarproject.R; import org.briarproject.R;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.introduction.IntroductionMessage; import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.introduction.IntroductionResponse; import org.briarproject.api.introduction.IntroductionResponse;
@@ -21,8 +20,6 @@ public abstract class ConversationItem {
final static int INTRODUCTION_OUT = 4; final static int INTRODUCTION_OUT = 4;
final static int NOTICE_IN = 5; final static int NOTICE_IN = 5;
final static int NOTICE_OUT = 6; final static int NOTICE_OUT = 6;
final static int FORUM_INVITATION_IN = 7;
final static int FORUM_INVITATION_OUT = 8;
private MessageId id; private MessageId id;
private long time; 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, * This method should not be used to get user-facing objects,
* Its purpose is to provider data for the contact list. * Its purpose is to provider data for the contact list.

View File

@@ -1,178 +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.getForum(), 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 Forum f, final boolean accept) {
runOnDbThread(new Runnable() {
@Override
public void run() {
try {
forumSharingManager.respondToInvitation(f, 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,162 +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.createForum(name);
forumManager.addForum(f);
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,442 +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) {
// 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());
ActivityOptionsCompat options = ActivityOptionsCompat
.makeCustomAnimation(this, android.R.anim.slide_in_left,
android.R.anim.slide_out_right);
ActivityCompat
.startActivityForResult(this, i2, REQUEST_FORUM_SHARED,
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,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 enableBluetooth;
private ListPreference torOverMobile; private ListPreference torOverMobile;
private CheckBoxPreference notifyPrivateMessages; private CheckBoxPreference notifyPrivateMessages;
private CheckBoxPreference notifyForumPosts;
private CheckBoxPreference notifyVibration; private CheckBoxPreference notifyVibration;
private Preference notifySound; private Preference notifySound;
@@ -94,8 +93,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
(ListPreference) findPreference("pref_key_tor_mobile"); (ListPreference) findPreference("pref_key_tor_mobile");
notifyPrivateMessages = (CheckBoxPreference) findPreference( notifyPrivateMessages = (CheckBoxPreference) findPreference(
"pref_key_notify_private_messages"); "pref_key_notify_private_messages");
notifyForumPosts = (CheckBoxPreference) findPreference(
"pref_key_notify_forum_posts");
notifyVibration = (CheckBoxPreference) findPreference( notifyVibration = (CheckBoxPreference) findPreference(
"pref_key_notify_vibration"); "pref_key_notify_vibration");
notifySound = findPreference("pref_key_notify_sound"); notifySound = findPreference("pref_key_notify_sound");
@@ -103,7 +100,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
enableBluetooth.setOnPreferenceChangeListener(this); enableBluetooth.setOnPreferenceChangeListener(this);
torOverMobile.setOnPreferenceChangeListener(this); torOverMobile.setOnPreferenceChangeListener(this);
notifyPrivateMessages.setOnPreferenceChangeListener(this); notifyPrivateMessages.setOnPreferenceChangeListener(this);
notifyForumPosts.setOnPreferenceChangeListener(this);
notifyVibration.setOnPreferenceChangeListener(this); notifyVibration.setOnPreferenceChangeListener(this);
notifySound.setOnPreferenceClickListener( notifySound.setOnPreferenceClickListener(
@@ -197,9 +193,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
notifyPrivateMessages.setChecked(settings.getBoolean( notifyPrivateMessages.setChecked(settings.getBoolean(
"notifyPrivateMessages", true)); "notifyPrivateMessages", true));
notifyForumPosts.setChecked(settings.getBoolean(
"notifyForumPosts", true));
notifyVibration.setChecked(settings.getBoolean( notifyVibration.setChecked(settings.getBoolean(
"notifyVibration", true)); "notifyVibration", true));
@@ -241,10 +234,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
Settings s = new Settings(); Settings s = new Settings();
s.putBoolean("notifyPrivateMessages", (Boolean) o); s.putBoolean("notifyPrivateMessages", (Boolean) o);
storeSettings(s); storeSettings(s);
} else if (preference == notifyForumPosts) {
Settings s = new Settings();
s.putBoolean("notifyForumPosts", (Boolean) o);
storeSettings(s);
} else if (preference == notifyVibration) { } else if (preference == notifyVibration) {
Settings s = new Settings(); Settings s = new Settings();
s.putBoolean("notifyVibration", (Boolean) o); s.putBoolean("notifyVibration", (Boolean) o);

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,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,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,55 +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();
/** 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);
/** Subscribes to a forum. */
void addForum(Forum f) 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;
}
}

View File

@@ -1,48 +0,0 @@
package org.briarproject.api.forum;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DbException;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import java.util.Collection;
public interface ForumSharingManager {
/** Returns the unique ID of the forum sharing client. */
ClientId getClientId();
/**
* Sends an invitation to share the given forum with the given contact
* and sends an optional message along with it.
*/
void sendForumInvitation(GroupId groupId, ContactId contactId,
String message) throws DbException;
/**
* Responds to a pending forum invitation
*/
void respondToInvitation(Forum f, boolean accept) throws DbException;
/**
* Returns all forum sharing messages sent by the Contact
* identified by contactId.
*/
Collection<ForumInvitationMessage> getForumInvitationMessages(
ContactId contactId) throws DbException;
/** Returns all forums to which the user could subscribe. */
Collection<Forum> getAvailableForums() throws DbException;
/** Returns all contacts who are sharing the given forum with us. */
Collection<Contact> getSharedBy(GroupId g) throws DbException;
/** Returns the IDs of all contacts with whom the given forum is shared. */
Collection<ContactId> getSharedWith(GroupId g) throws DbException;
/** Returns true if the forum not already shared and no invitation is open */
boolean canBeShared(GroupId g, Contact c) throws DbException;
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
public enum InviteeAction {
LOCAL_ACCEPT,
LOCAL_DECLINE,
LOCAL_LEAVE,
LOCAL_ABORT,
REMOTE_INVITATION,
REMOTE_LEAVE,
REMOTE_ABORT;
public static InviteeAction getLocal(long type) {
if (type == SHARE_MSG_TYPE_ACCEPT) return LOCAL_ACCEPT;
if (type == SHARE_MSG_TYPE_DECLINE) return LOCAL_DECLINE;
if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT;
return null;
}
public static InviteeAction getRemote(long type) {
if (type == SHARE_MSG_TYPE_INVITATION) return REMOTE_INVITATION;
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
return null;
}
}

View File

@@ -1,62 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT;
import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE;
import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION;
import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE;
public enum InviteeProtocolState {
ERROR(0),
AWAIT_INVITATION(1) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE;
return ERROR;
}
},
AWAIT_LOCAL_RESPONSE(2) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED;
if (a == REMOTE_LEAVE) return LEFT;
return ERROR;
}
},
FINISHED(3) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
return FINISHED;
}
},
LEFT(4) {
@Override
public InviteeProtocolState next(InviteeAction a) {
if (a == LOCAL_LEAVE) return ERROR;
return LEFT;
}
};
private final int value;
InviteeProtocolState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static InviteeProtocolState fromValue(int value) {
for (InviteeProtocolState s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public InviteeProtocolState next(InviteeAction a) {
return this;
}
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
public enum SharerAction {
LOCAL_INVITATION,
LOCAL_LEAVE,
LOCAL_ABORT,
REMOTE_ACCEPT,
REMOTE_DECLINE,
REMOTE_LEAVE,
REMOTE_ABORT;
public static SharerAction getLocal(long type) {
if (type == SHARE_MSG_TYPE_INVITATION) return LOCAL_INVITATION;
if (type == SHARE_MSG_TYPE_LEAVE) return LOCAL_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return LOCAL_ABORT;
return null;
}
public static SharerAction getRemote(long type) {
if (type == SHARE_MSG_TYPE_ACCEPT) return REMOTE_ACCEPT;
if (type == SHARE_MSG_TYPE_DECLINE) return REMOTE_DECLINE;
if (type == SHARE_MSG_TYPE_LEAVE) return REMOTE_LEAVE;
if (type == SHARE_MSG_TYPE_ABORT) return REMOTE_ABORT;
return null;
}
}

View File

@@ -1,62 +0,0 @@
package org.briarproject.api.forum;
import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION;
import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT;
import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE;
import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE;
public enum SharerProtocolState {
ERROR(0),
PREPARE_INVITATION(1) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == LOCAL_INVITATION) return AWAIT_RESPONSE;
return ERROR;
}
},
AWAIT_RESPONSE(2) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED;
if (a == LOCAL_LEAVE) return LEFT;
return ERROR;
}
},
FINISHED(3) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
return FINISHED;
}
},
LEFT(4) {
@Override
public SharerProtocolState next(SharerAction a) {
if (a == LOCAL_LEAVE) return ERROR;
return LEFT;
}
};
private final int value;
SharerProtocolState(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static SharerProtocolState fromValue(int value) {
for (SharerProtocolState s : values()) {
if (s.value == value) return s;
}
throw new IllegalArgumentException();
}
public SharerProtocolState next(SharerAction a) {
return this;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject;
import org.briarproject.contact.ContactModule; import org.briarproject.contact.ContactModule;
import org.briarproject.crypto.CryptoModule; import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseExecutorModule; import org.briarproject.db.DatabaseExecutorModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.introduction.IntroductionModule; import org.briarproject.introduction.IntroductionModule;
import org.briarproject.lifecycle.LifecycleModule; import org.briarproject.lifecycle.LifecycleModule;
import org.briarproject.messaging.MessagingModule; import org.briarproject.messaging.MessagingModule;
@@ -21,8 +20,6 @@ public interface CoreEagerSingletons {
void inject(DatabaseExecutorModule.EagerSingletons init); void inject(DatabaseExecutorModule.EagerSingletons init);
void inject(ForumModule.EagerSingletons init);
void inject(IntroductionModule.EagerSingletons init); void inject(IntroductionModule.EagerSingletons init);
void inject(LifecycleModule.EagerSingletons init); void inject(LifecycleModule.EagerSingletons init);

View File

@@ -7,7 +7,6 @@ import org.briarproject.data.DataModule;
import org.briarproject.db.DatabaseExecutorModule; import org.briarproject.db.DatabaseExecutorModule;
import org.briarproject.db.DatabaseModule; import org.briarproject.db.DatabaseModule;
import org.briarproject.event.EventModule; import org.briarproject.event.EventModule;
import org.briarproject.forum.ForumModule;
import org.briarproject.identity.IdentityModule; import org.briarproject.identity.IdentityModule;
import org.briarproject.introduction.IntroductionModule; import org.briarproject.introduction.IntroductionModule;
import org.briarproject.invitation.InvitationModule; import org.briarproject.invitation.InvitationModule;
@@ -33,7 +32,6 @@ import dagger.Module;
DatabaseModule.class, DatabaseModule.class,
DatabaseExecutorModule.class, DatabaseExecutorModule.class,
EventModule.class, EventModule.class,
ForumModule.class,
IdentityModule.class, IdentityModule.class,
IntroductionModule.class, IntroductionModule.class,
InvitationModule.class, InvitationModule.class,
@@ -55,7 +53,6 @@ public class CoreModule {
c.inject(new ContactModule.EagerSingletons()); c.inject(new ContactModule.EagerSingletons());
c.inject(new CryptoModule.EagerSingletons()); c.inject(new CryptoModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons()); c.inject(new DatabaseExecutorModule.EagerSingletons());
c.inject(new ForumModule.EagerSingletons());
c.inject(new LifecycleModule.EagerSingletons()); c.inject(new LifecycleModule.EagerSingletons());
c.inject(new MessagingModule.EagerSingletons()); c.inject(new MessagingModule.EagerSingletons());
c.inject(new PluginsModule.EagerSingletons()); c.inject(new PluginsModule.EagerSingletons());

View File

@@ -1,271 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.contact.Contact;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.api.identity.Author.Status.ANONYMOUS;
import static org.briarproject.api.identity.Author.Status.UNKNOWN;
import static org.briarproject.api.identity.Author.Status.VERIFIED;
class ForumManagerImpl implements ForumManager {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"859a7be50dca035b64bd6902fb797097"
+ "795af837abbf8c16d750b3c2ccc186ea"));
private final DatabaseComponent db;
private final ClientHelper clientHelper;
private final GroupFactory groupFactory;
private final SecureRandom random;
private final List<RemoveForumHook> removeHooks;
@Inject
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
GroupFactory groupFactory, SecureRandom random) {
this.db = db;
this.clientHelper = clientHelper;
this.groupFactory = groupFactory;
this.random = random;
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
}
@Override
public ClientId getClientId() {
return CLIENT_ID;
}
@Override
public Forum createForum(String name) {
int length = StringUtils.toUtf8(name).length;
if (length == 0) throw new IllegalArgumentException();
if (length > MAX_FORUM_NAME_LENGTH)
throw new IllegalArgumentException();
byte[] salt = new byte[FORUM_SALT_LENGTH];
random.nextBytes(salt);
return createForum(name, salt);
}
@Override
public Forum createForum(String name, byte[] salt) {
try {
BdfList forum = BdfList.of(name, salt);
byte[] descriptor = clientHelper.toByteArray(forum);
Group g = groupFactory.createGroup(getClientId(), descriptor);
return new Forum(g, name, salt);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public void addForum(Forum f) throws DbException {
Transaction txn = db.startTransaction(false);
try {
db.addGroup(txn, f.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
}
@Override
public void removeForum(Forum f) throws DbException {
Transaction txn = db.startTransaction(false);
try {
for (RemoveForumHook hook : removeHooks)
hook.removingForum(txn, f);
db.removeGroup(txn, f.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
}
@Override
public void addLocalPost(ForumPost p) throws DbException {
try {
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", p.getMessage().getTimestamp());
if (p.getParent() != null) meta.put("parent", p.getParent());
if (p.getAuthor() != null) {
Author a = p.getAuthor();
BdfDictionary authorMeta = new BdfDictionary();
authorMeta.put("id", a.getId());
authorMeta.put("name", a.getName());
authorMeta.put("publicKey", a.getPublicKey());
meta.put("author", authorMeta);
}
meta.put("contentType", p.getContentType());
meta.put("local", true);
meta.put("read", true);
clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public Forum getForum(GroupId g) throws DbException {
Forum forum;
Transaction txn = db.startTransaction(true);
try {
forum = getForum(txn, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return forum;
}
@Override
public Forum getForum(Transaction txn, GroupId g) throws DbException {
try {
Group group = db.getGroup(txn, g);
return parseForum(group);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public Collection<Forum> getForums() throws DbException {
try {
Collection<Group> groups;
Transaction txn = db.startTransaction(true);
try {
groups = db.getGroups(txn, CLIENT_ID);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
List<Forum> forums = new ArrayList<Forum>();
for (Group g : groups) forums.add(parseForum(g));
return Collections.unmodifiableList(forums);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public byte[] getPostBody(MessageId m) throws DbException {
try {
// Parent ID, author, content type, forum post body, signature
BdfList message = clientHelper.getMessageAsList(m);
return message.getRaw(3);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public Collection<ForumPostHeader> getPostHeaders(GroupId g)
throws DbException {
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
Map<MessageId, BdfDictionary> metadata;
Transaction txn = db.startTransaction(true);
try {
// Load the IDs of the user's identities
for (LocalAuthor a : db.getLocalAuthors(txn))
localAuthorIds.add(a.getId());
// Load the IDs of contacts' identities
for (Contact c : db.getContacts(txn))
contactAuthorIds.add(c.getAuthor().getId());
// Load the metadata
metadata = clientHelper.getMessageMetadataAsDictionary(txn, g);
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
// Parse the metadata
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
try {
BdfDictionary meta = entry.getValue();
long timestamp = meta.getLong("timestamp");
Author author = null;
Author.Status authorStatus = ANONYMOUS;
BdfDictionary d1 = meta.getDictionary("author", null);
if (d1 != null) {
AuthorId authorId = new AuthorId(d1.getRaw("id"));
String name = d1.getString("name");
byte[] publicKey = d1.getRaw("publicKey");
author = new Author(authorId, name, publicKey);
if (localAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else if (contactAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
}
String contentType = meta.getString("contentType");
boolean read = meta.getBoolean("read");
headers.add(new ForumPostHeader(entry.getKey(), timestamp,
author, authorStatus, contentType, read));
} catch (FormatException e) {
throw new DbException(e);
}
}
return headers;
}
@Override
public void setReadFlag(MessageId m, boolean read) throws DbException {
try {
BdfDictionary meta = new BdfDictionary();
meta.put("read", read);
clientHelper.mergeMessageMetadata(m, meta);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
@Override
public void registerRemoveForumHook(RemoveForumHook hook) {
removeHooks.add(hook);
}
private Forum parseForum(Group g) throws FormatException {
byte[] descriptor = g.getDescriptor();
// Name, salt
BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
return new Forum(g, forum.getString(0), forum.getRaw(1));
}
}

View File

@@ -1,97 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.MessageQueueManager;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.db.DatabaseComponent;
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.lifecycle.LifecycleManager;
import org.briarproject.api.sync.GroupFactory;
import org.briarproject.api.sync.ValidationManager;
import org.briarproject.api.system.Clock;
import java.security.SecureRandom;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class ForumModule {
public static class EagerSingletons {
@Inject
ForumPostValidator forumPostValidator;
@Inject
ForumSharingValidator forumSharingValidator;
@Inject
ForumSharingManager forumSharingManager;
}
@Provides
@Singleton
ForumManager provideForumManager(DatabaseComponent db,
ClientHelper clientHelper,
GroupFactory groupFactory, SecureRandom random) {
return new ForumManagerImpl(db, clientHelper, groupFactory, random);
}
@Provides
ForumPostFactory provideForumPostFactory(CryptoComponent crypto,
ClientHelper clientHelper) {
return new ForumPostFactoryImpl(crypto, clientHelper);
}
@Provides
@Singleton
ForumPostValidator provideForumPostValidator(
ValidationManager validationManager, CryptoComponent crypto,
AuthorFactory authorFactory, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
ForumPostValidator validator = new ForumPostValidator(crypto,
authorFactory, clientHelper, metadataEncoder, clock);
validationManager.registerMessageValidator(
ForumManagerImpl.CLIENT_ID, validator);
return validator;
}
@Provides
@Singleton
ForumSharingValidator provideSharingValidator(
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
ForumSharingValidator validator = new ForumSharingValidator(clientHelper,
metadataEncoder, clock);
messageQueueManager.registerMessageValidator(
ForumSharingManagerImpl.CLIENT_ID, validator);
return validator;
}
@Provides
@Singleton
ForumSharingManager provideForumSharingManager(
LifecycleManager lifecycleManager,
ContactManager contactManager,
MessageQueueManager messageQueueManager,
ForumManager forumManager,
ForumSharingManagerImpl forumSharingManager) {
lifecycleManager.registerClient(forumSharingManager);
contactManager.registerAddContactHook(forumSharingManager);
contactManager.registerRemoveContactHook(forumSharingManager);
messageQueueManager.registerIncomingMessageHook(
ForumSharingManagerImpl.CLIENT_ID, forumSharingManager);
forumManager.registerRemoveForumHook(forumSharingManager);
return forumSharingManager;
}
}

View File

@@ -1,76 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.PrivateKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostFactory;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.security.GeneralSecurityException;
import javax.inject.Inject;
import static org.briarproject.api.forum.ForumConstants.MAX_CONTENT_TYPE_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
class ForumPostFactoryImpl implements ForumPostFactory {
private final CryptoComponent crypto;
private final ClientHelper clientHelper;
@Inject
ForumPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
this.crypto = crypto;
this.clientHelper = clientHelper;
}
@Override
public ForumPost createAnonymousPost(GroupId groupId, long timestamp,
MessageId parent, String contentType, byte[] body)
throws FormatException {
// Validate the arguments
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
throw new IllegalArgumentException();
// Serialise the message
BdfList message = BdfList.of(parent, null, contentType, body, null);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new ForumPost(m, parent, null, contentType);
}
@Override
public ForumPost createPseudonymousPost(GroupId groupId, long timestamp,
MessageId parent, Author author, String contentType, byte[] body,
PrivateKey privateKey) throws FormatException,
GeneralSecurityException {
// Validate the arguments
if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
throw new IllegalArgumentException();
if (body.length > MAX_FORUM_POST_BODY_LENGTH)
throw new IllegalArgumentException();
// Serialise the data to be signed
BdfList authorList = BdfList.of(author.getName(),
author.getPublicKey());
BdfList signed = BdfList.of(groupId, timestamp, parent, authorList,
contentType, body);
// Generate the signature
Signature signature = crypto.getSignature();
signature.initSign(privateKey);
signature.update(clientHelper.toByteArray(signed));
byte[] sig = signature.sign();
// Serialise the signed message
BdfList message = BdfList.of(parent, authorList, contentType, body,
sig);
Message m = clientHelper.createMessage(groupId, timestamp, message);
return new ForumPost(m, parent, author, contentType);
}
}

View File

@@ -1,116 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.UniqueId;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.KeyParser;
import org.briarproject.api.crypto.PublicKey;
import org.briarproject.api.crypto.Signature;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorFactory;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.Message;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator;
import java.security.GeneralSecurityException;
import static org.briarproject.api.forum.ForumConstants.MAX_CONTENT_TYPE_LENGTH;
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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
class ForumPostValidator extends BdfMessageValidator {
private final CryptoComponent crypto;
private final AuthorFactory authorFactory;
ForumPostValidator(CryptoComponent crypto, AuthorFactory authorFactory,
ClientHelper clientHelper, MetadataEncoder metadataEncoder,
Clock clock) {
super(clientHelper, metadataEncoder, clock);
this.crypto = crypto;
this.authorFactory = authorFactory;
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
BdfList body) throws FormatException {
// Parent ID, author, content type, forum post body, signature
checkSize(body, 5);
// Parent ID is optional
byte[] parent = body.getOptionalRaw(0);
checkLength(parent, UniqueId.LENGTH);
// Author is optional
Author author = null;
BdfList authorList = body.getOptionalList(1);
if (authorList != null) {
// Name, public key
checkSize(authorList, 2);
String name = authorList.getString(0);
checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
byte[] publicKey = authorList.getRaw(1);
checkLength(publicKey, 0, MAX_PUBLIC_KEY_LENGTH);
author = authorFactory.createAuthor(name, publicKey);
}
// Content type
String contentType = body.getString(2);
checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
// Forum post body
byte[] forumPostBody = body.getRaw(3);
checkLength(forumPostBody, 0, MAX_FORUM_POST_BODY_LENGTH);
// Signature is optional
byte[] sig = body.getOptionalRaw(4);
checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
// If there's an author there must be a signature and vice versa
if (author != null && sig == null) {
LOG.info("Author without signature");
return null;
}
if (author == null && sig != null) {
LOG.info("Signature without author");
return null;
}
// Verify the signature, if any
if (author != null) {
try {
// Parse the public key
KeyParser keyParser = crypto.getSignatureKeyParser();
PublicKey key = keyParser.parsePublicKey(author.getPublicKey());
// Serialise the data to be signed
BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), parent,
authorList, contentType, forumPostBody);
// Verify the signature
Signature signature = crypto.getSignature();
signature.initVerify(key);
signature.update(clientHelper.toByteArray(signed));
if (!signature.verify(sig)) {
LOG.info("Invalid signature");
return null;
}
} catch (GeneralSecurityException e) {
LOG.info("Invalid public key");
return null;
}
}
// Return the metadata
BdfDictionary meta = new BdfDictionary();
meta.put("timestamp", m.getTimestamp());
if (parent != null) meta.put("parent", parent);
if (author != null) {
BdfDictionary authorMeta = new BdfDictionary();
authorMeta.put("id", author.getId());
authorMeta.put("name", author.getName());
authorMeta.put("publicKey", author.getPublicKey());
meta.put("author", authorMeta);
}
meta.put("contentType", contentType);
meta.put("read", false);
return meta;
}
}

View File

@@ -1,878 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.Bytes;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.Client;
import org.briarproject.api.clients.ClientHelper;
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.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event;
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.forum.InviteeAction;
import org.briarproject.api.forum.InviteeProtocolState;
import org.briarproject.api.forum.SharerAction;
import org.briarproject.api.forum.SharerProtocolState;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.Message;
import org.briarproject.api.sync.MessageId;
import org.briarproject.api.sync.MessageStatus;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfIncomingMessageHook;
import org.briarproject.util.StringUtils;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.api.clients.ProtocolEngine.StateUpdate;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
import static org.briarproject.api.forum.ForumConstants.LOCAL;
import static org.briarproject.api.forum.ForumConstants.READ;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.STORAGE_ID;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TIME;
import static org.briarproject.api.forum.ForumConstants.TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.forum.ForumManager.RemoveForumHook;
import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_INVITATION;
import static org.briarproject.api.forum.InviteeProtocolState.AWAIT_LOCAL_RESPONSE;
import static org.briarproject.api.forum.SharerProtocolState.PREPARE_INVITATION;
class ForumSharingManagerImpl extends BdfIncomingMessageHook
implements ForumSharingManager, Client, RemoveForumHook,
AddContactHook, RemoveContactHook {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"cd11a5d04dccd9e2931d6fc3df456313"
+ "63bb3e9d9d0e9405fccdb051f41f5449"));
private static final Logger LOG =
Logger.getLogger(ForumSharingManagerImpl.class.getName());
private final DatabaseComponent db;
private final ForumManager forumManager;
private final MessageQueueManager messageQueueManager;
private final MetadataEncoder metadataEncoder;
private final SecureRandom random;
private final PrivateGroupFactory privateGroupFactory;
private final Clock clock;
private final Group localGroup;
@Inject
ForumSharingManagerImpl(DatabaseComponent db, ForumManager forumManager,
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
MetadataParser metadataParser, MetadataEncoder metadataEncoder,
SecureRandom random, PrivateGroupFactory privateGroupFactory,
Clock clock) {
super(clientHelper, metadataParser);
this.db = db;
this.forumManager = forumManager;
this.messageQueueManager = messageQueueManager;
this.metadataEncoder = metadataEncoder;
this.random = random;
this.privateGroupFactory = privateGroupFactory;
this.clock = clock;
localGroup = privateGroupFactory.createLocalGroup(getClientId());
}
@Override
public void createLocalState(Transaction txn) throws DbException {
db.addGroup(txn, localGroup);
// Ensure we've set things up for any pre-existing contacts
for (Contact c : db.getContacts(txn)) addingContact(txn, c);
}
@Override
public void addingContact(Transaction txn, Contact c) throws DbException {
try {
// Create a group to share with the contact
Group g = getContactGroup(c);
// Return if we've already set things up for this contact
if (db.containsGroup(txn, g.getId())) return;
// Store the group and share it with the contact
db.addGroup(txn, g);
db.setVisibleToContact(txn, c.getId(), g.getId(), true);
// Attach the contact ID to the group
BdfDictionary meta = new BdfDictionary();
meta.put(CONTACT_ID, c.getId().getInt());
meta.put(TO_BE_SHARED_BY_US, new BdfList());
meta.put(SHARED_BY_US, new BdfList());
meta.put(SHARED_WITH_US, new BdfList());
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public void removingContact(Transaction txn, Contact c) throws DbException {
// clean up session states with that contact from localGroup
Long id = (long) c.getId().getInt();
try {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
BdfDictionary d = entry.getValue();
if (id.equals(d.getLong(CONTACT_ID))) {
deleteMessage(txn, entry.getKey());
}
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
// remove the contact group (all messages will be removed with it)
db.removeGroup(txn, getContactGroup(c));
}
@Override
protected void incomingMessage(Transaction txn, Message m, BdfList body,
BdfDictionary msg) throws DbException, FormatException {
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
long type = msg.getLong(TYPE);
if (type == SHARE_MSG_TYPE_INVITATION) {
// we are an invitee who just received a new invitation
boolean stateExists = true;
try {
// check if we have a session with that ID already
getSessionState(txn, sessionId, false);
} catch (FormatException e) {
// this is what we would expect under normal circumstances
stateExists = false;
}
try {
// check if we already have a state with that sessionId
if (stateExists) throw new FormatException();
// check if forum can be shared
Forum f = forumManager.createForum(msg.getString(FORUM_NAME),
msg.getRaw(FORUM_SALT));
ContactId contactId = getContactId(txn, m.getGroupId());
Contact contact = db.getContact(txn, contactId);
if (!canBeShared(txn, f.getId(), contact))
throw new FormatException();
// initialize state and process invitation
BdfDictionary state =
initializeInviteeState(txn, contactId, msg);
InviteeEngine engine = new InviteeEngine();
processStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg));
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
deleteMessage(txn, m.getId());
}
} else if (type == SHARE_MSG_TYPE_ACCEPT ||
type == SHARE_MSG_TYPE_DECLINE) {
// we are a sharer who just received a response
BdfDictionary state = getSessionState(txn, sessionId, true);
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg));
} else if (type == SHARE_MSG_TYPE_LEAVE ||
type == SHARE_MSG_TYPE_ABORT) {
// we don't know who we are, so figure it out
BdfDictionary state = getSessionState(txn, sessionId, true);
if (state.getBoolean(IS_SHARER)) {
// we are a sharer and the invitee wants to leave or abort
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg));
} else {
// we are an invitee and the sharer wants to leave or abort
InviteeEngine engine = new InviteeEngine();
processStateUpdate(txn, m.getId(),
engine.onMessageReceived(state, msg));
}
} else {
// message has passed validator, so that should never happen
throw new RuntimeException("Illegal Forum Sharing Message");
}
}
@Override
public ClientId getClientId() {
return CLIENT_ID;
}
@Override
public void sendForumInvitation(GroupId groupId, ContactId contactId,
String msg) throws DbException {
Transaction txn = db.startTransaction(false);
try {
// initialize local state for sharer
Forum f = forumManager.getForum(txn, groupId);
BdfDictionary localState = initializeSharerState(txn, f, contactId);
// define action
BdfDictionary localAction = new BdfDictionary();
localAction.put(TYPE, SHARE_MSG_TYPE_INVITATION);
if (!StringUtils.isNullOrEmpty(msg)) {
localAction.put(INVITATION_MSG, msg);
}
// start engine and process its state update
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, null,
engine.onLocalAction(localState, localAction));
txn.setComplete();
} catch (FormatException e) {
throw new DbException();
} finally {
db.endTransaction(txn);
}
}
@Override
public void respondToInvitation(Forum f, boolean accept)
throws DbException {
Transaction txn = db.startTransaction(false);
try {
// find session state based on forum
BdfDictionary localState = getSessionStateForResponse(txn, f);
// define action
BdfDictionary localAction = new BdfDictionary();
if (accept) {
localAction.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
} else {
localAction.put(TYPE, SHARE_MSG_TYPE_DECLINE);
}
// start engine and process its state update
InviteeEngine engine = new InviteeEngine();
processStateUpdate(txn, null,
engine.onLocalAction(localState, localAction));
txn.setComplete();
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
}
@Override
public Collection<ForumInvitationMessage> getForumInvitationMessages(
ContactId contactId) throws DbException {
Transaction txn = db.startTransaction(false);
try {
Contact contact = db.getContact(txn, contactId);
Group group = getContactGroup(contact);
Collection<ForumInvitationMessage> list =
new ArrayList<ForumInvitationMessage>();
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, group.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary msg = m.getValue();
try {
if (msg.getLong(TYPE) != SHARE_MSG_TYPE_INVITATION)
continue;
MessageStatus status =
db.getMessageStatus(txn, contactId, m.getKey());
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
String name = msg.getString(FORUM_NAME);
String message = msg.getOptionalString(INVITATION_MSG);
long time = msg.getLong(TIME);
boolean local = msg.getBoolean(LOCAL);
boolean read = msg.getBoolean(READ, false);
boolean available = false;
if (!local) {
// figure out whether the forum is still available
BdfDictionary sessionState =
getSessionState(txn, sessionId, true);
InviteeProtocolState state = InviteeProtocolState
.fromValue(
sessionState.getLong(STATE).intValue());
available = state == AWAIT_LOCAL_RESPONSE;
}
ForumInvitationMessage im =
new ForumInvitationMessage(m.getKey(), sessionId,
contactId, name, message, available, time,
local, status.isSent(), status.isSeen(),
read);
list.add(im);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
txn.setComplete();
return list;
} catch (FormatException e) {
throw new DbException(e);
} finally {
db.endTransaction(txn);
}
}
@Override
public void removingForum(Transaction txn, Forum f) throws DbException {
try {
for (Contact c : db.getContacts(txn)) {
GroupId g = getContactGroup(c).getId();
if (removeFromList(txn, g, TO_BE_SHARED_BY_US, f)) {
leaveForum(txn, c.getId(), f);
}
if (removeFromList(txn, g, SHARED_BY_US, f)) {
leaveForum(txn, c.getId(), f);
}
if (removeFromList(txn, g, SHARED_WITH_US, f)) {
leaveForum(txn, c.getId(), f);
}
}
} catch (IOException e) {
throw new DbException(e);
}
}
@Override
public Collection<Forum> getAvailableForums() throws DbException {
try {
Set<Forum> available = new HashSet<Forum>();
Transaction txn = db.startTransaction(true);
try {
// Get any forums we subscribe to
Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn,
forumManager.getClientId()));
// Get all forums shared by contacts
for (Contact c : db.getContacts(txn)) {
Group g = getContactGroup(c);
List<Forum> forums =
getForumList(txn, g.getId(), SHARED_WITH_US);
for (Forum f : forums) {
if (!subscribed.contains(f.getGroup()))
available.add(f);
}
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return Collections.unmodifiableSet(available);
} catch (IOException e) {
throw new DbException(e);
}
}
@Override
public Collection<Contact> getSharedBy(GroupId g) throws DbException {
try {
List<Contact> subscribers = new ArrayList<Contact>();
Transaction txn = db.startTransaction(true);
try {
for (Contact c : db.getContacts(txn)) {
GroupId contactGroup = getContactGroup(c).getId();
if (listContains(txn, contactGroup, g, SHARED_WITH_US))
subscribers.add(c);
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return Collections.unmodifiableList(subscribers);
} catch (IOException e) {
throw new DbException(e);
}
}
@Override
public Collection<ContactId> getSharedWith(GroupId g) throws DbException {
try {
List<ContactId> shared = new ArrayList<ContactId>();
Transaction txn = db.startTransaction(true);
try {
for (Contact c : db.getContacts(txn)) {
GroupId contactGroup = getContactGroup(c).getId();
if (listContains(txn, contactGroup, g, SHARED_BY_US))
shared.add(c.getId());
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return Collections.unmodifiableList(shared);
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public boolean canBeShared(GroupId g, Contact c) throws DbException {
boolean canBeShared;
Transaction txn = db.startTransaction(true);
try {
canBeShared = canBeShared(txn, g, c);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return canBeShared;
}
private boolean canBeShared(Transaction txn, GroupId g, Contact c)
throws DbException {
try {
GroupId contactGroup = getContactGroup(c).getId();
return !listContains(txn, contactGroup, g, SHARED_BY_US) &&
!listContains(txn, contactGroup, g, SHARED_WITH_US) &&
!listContains(txn, contactGroup, g, TO_BE_SHARED_BY_US);
} catch (FormatException e) {
throw new DbException(e);
}
}
private BdfDictionary initializeSharerState(Transaction txn, Forum f,
ContactId contactId) throws FormatException, DbException {
Contact c = db.getContact(txn, contactId);
Group group = getContactGroup(c);
// create local message to keep engine state
long now = clock.currentTimeMillis();
Bytes salt = new Bytes(new byte[FORUM_SALT_LENGTH]);
random.nextBytes(salt.getBytes());
Message m = clientHelper.createMessage(localGroup.getId(), now,
BdfList.of(salt));
MessageId sessionId = m.getId();
BdfDictionary d = new BdfDictionary();
d.put(SESSION_ID, sessionId);
d.put(STORAGE_ID, sessionId);
d.put(GROUP_ID, group.getId());
d.put(IS_SHARER, true);
d.put(STATE, PREPARE_INVITATION.getValue());
d.put(CONTACT_ID, contactId.getInt());
d.put(FORUM_ID, f.getId());
d.put(FORUM_NAME, f.getName());
d.put(FORUM_SALT, f.getSalt());
// save local state to database
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
return d;
}
private BdfDictionary initializeInviteeState(Transaction txn,
ContactId contactId, BdfDictionary msg)
throws FormatException, DbException {
Contact c = db.getContact(txn, contactId);
Group group = getContactGroup(c);
String name = msg.getString(FORUM_NAME);
byte[] salt = msg.getRaw(FORUM_SALT);
Forum f = forumManager.createForum(name, salt);
// create local message to keep engine state
long now = clock.currentTimeMillis();
Bytes mSalt = new Bytes(new byte[FORUM_SALT_LENGTH]);
random.nextBytes(mSalt.getBytes());
Message m = clientHelper.createMessage(localGroup.getId(), now,
BdfList.of(mSalt));
BdfDictionary d = new BdfDictionary();
d.put(SESSION_ID, msg.getRaw(SESSION_ID));
d.put(STORAGE_ID, m.getId());
d.put(GROUP_ID, group.getId());
d.put(IS_SHARER, false);
d.put(STATE, AWAIT_INVITATION.getValue());
d.put(CONTACT_ID, contactId.getInt());
d.put(FORUM_ID, f.getId());
d.put(FORUM_NAME, name);
d.put(FORUM_SALT, salt);
// save local state to database
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
return d;
}
private BdfDictionary getSessionState(Transaction txn, SessionId sessionId,
boolean warn) throws DbException, FormatException {
try {
// we should be able to get the sharer state directly from sessionId
return clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
} catch (NoSuchMessageException e) {
// State not found directly, so iterate over all states
// to find state for invitee
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary state = m.getValue();
if (Arrays.equals(state.getRaw(SESSION_ID),
sessionId.getBytes())) {
return state;
}
}
if (warn && LOG.isLoggable(WARNING)) {
LOG.warning(
"No session state found for message with session ID " +
Arrays.hashCode(sessionId.getBytes()));
}
throw new FormatException();
}
}
private BdfDictionary getSessionStateForResponse(Transaction txn, Forum f)
throws DbException, FormatException {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary d = m.getValue();
try {
InviteeProtocolState state = InviteeProtocolState
.fromValue(d.getLong(STATE).intValue());
if (state == AWAIT_LOCAL_RESPONSE) {
byte[] id = d.getRaw(FORUM_ID);
if (Arrays.equals(f.getId().getBytes(), id)) {
// Note that there should always be only one session
// in this state for the same forum
return d;
}
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
throw new DbException();
}
private BdfDictionary getSessionStateForLeaving(Transaction txn, Forum f,
ContactId c) throws DbException, FormatException {
Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId());
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
BdfDictionary d = m.getValue();
try {
// check that this session is with the right contact
if (c.getInt() != d.getLong(CONTACT_ID)) continue;
// check that a forum get be left in current session
int intState = d.getLong(STATE).intValue();
if (d.getBoolean(IS_SHARER)) {
SharerProtocolState state =
SharerProtocolState.fromValue(intState);
if (state.next(SharerAction.LOCAL_LEAVE) ==
SharerProtocolState.ERROR) continue;
} else {
InviteeProtocolState state = InviteeProtocolState
.fromValue(intState);
if (state.next(InviteeAction.LOCAL_LEAVE) ==
InviteeProtocolState.ERROR) continue;
}
// check that this state actually concerns this forum
String name = d.getString(FORUM_NAME);
byte[] salt = d.getRaw(FORUM_SALT);
if (name.equals(f.getName()) &&
Arrays.equals(salt, f.getSalt())) {
// TODO what happens when there is more than one invitation?
return d;
}
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
throw new FormatException();
}
private void processStateUpdate(Transaction txn, MessageId messageId,
StateUpdate<BdfDictionary, BdfDictionary> result)
throws DbException, FormatException {
// perform actions based on new local state
performTasks(txn, result.localState);
// save new local state
MessageId storageId =
new MessageId(result.localState.getRaw(STORAGE_ID));
clientHelper.mergeMessageMetadata(txn, storageId, result.localState);
// send messages
for (BdfDictionary d : result.toSend) {
sendMessage(txn, d);
}
// broadcast events
for (Event event : result.toBroadcast) {
txn.attach(event);
}
// delete message
if (result.deleteMessage && messageId != null) {
if (LOG.isLoggable(INFO)) {
LOG.info("Deleting message with id " + messageId.hashCode());
}
db.deleteMessage(txn, messageId);
db.deleteMessageMetadata(txn, messageId);
}
}
private void performTasks(Transaction txn, BdfDictionary localState)
throws FormatException, DbException {
if (!localState.containsKey(TASK)) return;
// remember task and remove it from localState
long task = localState.getLong(TASK);
localState.put(TASK, BdfDictionary.NULL_VALUE);
// get group ID for later
GroupId groupId = new GroupId(localState.getRaw(GROUP_ID));
// get contact ID for later
ContactId contactId =
new ContactId(localState.getLong(CONTACT_ID).intValue());
// get forum for later
String name = localState.getString(FORUM_NAME);
byte[] salt = localState.getRaw(FORUM_SALT);
Forum f = forumManager.createForum(name, salt);
// perform tasks
if (task == TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US) {
addToList(txn, groupId, SHARED_WITH_US, f);
}
else if (task == TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US) {
removeFromList(txn, groupId, SHARED_WITH_US, f);
}
else if (task == TASK_ADD_SHARED_FORUM) {
db.addGroup(txn, f.getGroup());
db.setVisibleToContact(txn, contactId, f.getId(), true);
}
else if (task == TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US) {
addToList(txn, groupId, TO_BE_SHARED_BY_US, f);
}
else if (task == TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US) {
removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
}
else if (task == TASK_SHARE_FORUM) {
db.setVisibleToContact(txn, contactId, f.getId(), true);
removeFromList(txn, groupId, TO_BE_SHARED_BY_US, f);
addToList(txn, groupId, SHARED_BY_US, f);
}
else if (task == TASK_UNSHARE_FORUM_SHARED_BY_US) {
db.setVisibleToContact(txn, contactId, f.getId(), false);
removeFromList(txn, groupId, SHARED_BY_US, f);
} else if (task == TASK_UNSHARE_FORUM_SHARED_WITH_US) {
db.setVisibleToContact(txn, contactId, f.getId(), false);
removeFromList(txn, groupId, SHARED_WITH_US, f);
}
}
private void sendMessage(Transaction txn, BdfDictionary m)
throws FormatException, DbException {
BdfList list = encodeMessage(m);
byte[] body = clientHelper.toByteArray(list);
GroupId groupId = new GroupId(m.getRaw(GROUP_ID));
Group group = db.getGroup(txn, groupId);
long timestamp = clock.currentTimeMillis();
// add message itself as metadata
m.put(LOCAL, true);
m.put(TIME, timestamp);
Metadata meta = metadataEncoder.encode(m);
messageQueueManager
.sendMessage(txn, group, timestamp, body, meta);
}
private BdfList encodeMessage(BdfDictionary m) throws FormatException {
long type = m.getLong(TYPE);
BdfList list;
if (type == SHARE_MSG_TYPE_INVITATION) {
list = BdfList.of(type,
m.getRaw(SESSION_ID),
m.getString(FORUM_NAME),
m.getRaw(FORUM_SALT)
);
String msg = m.getOptionalString(INVITATION_MSG);
if (msg != null) list.add(msg);
} else if (type == SHARE_MSG_TYPE_ACCEPT) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_DECLINE) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_LEAVE) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else if (type == SHARE_MSG_TYPE_ABORT) {
list = BdfList.of(type, m.getRaw(SESSION_ID));
} else {
throw new FormatException();
}
return list;
}
private Group getContactGroup(Contact c) {
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
}
private ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException {
BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn,
contactGroupId);
return new ContactId(meta.getLong(CONTACT_ID).intValue());
}
private void leaveForum(Transaction txn, ContactId c, Forum f)
throws DbException, FormatException {
BdfDictionary state = getSessionStateForLeaving(txn, f, c);
BdfDictionary action = new BdfDictionary();
action.put(TYPE, SHARE_MSG_TYPE_LEAVE);
if (state.getBoolean(IS_SHARER)) {
SharerEngine engine = new SharerEngine();
processStateUpdate(txn, null,
engine.onLocalAction(state, action));
} else {
InviteeEngine engine = new InviteeEngine();
processStateUpdate(txn, null,
engine.onLocalAction(state, action));
}
}
private boolean listContains(Transaction txn, GroupId contactGroup,
GroupId forum, String key) throws DbException, FormatException {
List<Forum> list = getForumList(txn, contactGroup, key);
for (Forum f : list) {
if (f.getId().equals(forum)) return true;
}
return false;
}
private boolean addToList(Transaction txn, GroupId groupId, String key,
Forum f) throws DbException, FormatException {
List<Forum> forums = getForumList(txn, groupId, key);
if (forums.contains(f)) return false;
forums.add(f);
storeForumList(txn, groupId, key, forums);
return true;
}
private boolean removeFromList(Transaction txn, GroupId groupId, String key,
Forum f) throws DbException, FormatException {
List<Forum> forums = getForumList(txn, groupId, key);
if (forums.remove(f)) {
storeForumList(txn, groupId, key, forums);
return true;
}
return false;
}
private List<Forum> getForumList(Transaction txn, GroupId groupId,
String key) throws DbException, FormatException {
BdfDictionary metadata =
clientHelper.getGroupMetadataAsDictionary(txn, groupId);
BdfList list = metadata.getList(key);
return parseForumList(list);
}
private void storeForumList(Transaction txn, GroupId groupId, String key,
List<Forum> forums) throws DbException, FormatException {
BdfList list = encodeForumList(forums);
BdfDictionary metadata = BdfDictionary.of(
new BdfEntry(key, list)
);
clientHelper.mergeGroupMetadata(txn, groupId, metadata);
}
private BdfList encodeForumList(List<Forum> forums) {
BdfList forumList = new BdfList();
for (Forum f : forums)
forumList.add(BdfList.of(f.getName(), f.getSalt()));
return forumList;
}
private List<Forum> parseForumList(BdfList list) throws FormatException {
List<Forum> forums = new ArrayList<Forum>(list.size());
for (int i = 0; i < list.size(); i++) {
BdfList forum = list.getList(i);
forums.add(forumManager
.createForum(forum.getString(0), forum.getRaw(1)));
}
return forums;
}
private void deleteMessage(Transaction txn, MessageId messageId)
throws DbException {
if (LOG.isLoggable(INFO))
LOG.info("Deleting message with ID: " + messageId.hashCode());
db.deleteMessage(txn, messageId);
db.deleteMessageMetadata(txn, messageId);
}
}

View File

@@ -1,81 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ClientHelper;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfList;
import org.briarproject.api.data.MetadataEncoder;
import org.briarproject.api.sync.Group;
import org.briarproject.api.sync.Message;
import org.briarproject.api.system.Clock;
import org.briarproject.clients.BdfMessageValidator;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
import static org.briarproject.api.forum.ForumConstants.LOCAL;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.TIME;
import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
class ForumSharingValidator extends BdfMessageValidator {
ForumSharingValidator(ClientHelper clientHelper,
MetadataEncoder metadataEncoder, Clock clock) {
super(clientHelper, metadataEncoder, clock);
}
@Override
protected BdfDictionary validateMessage(Message m, Group g,
BdfList body) throws FormatException {
BdfDictionary d = new BdfDictionary();
long type = body.getLong(0);
byte[] id = body.getRaw(1);
checkLength(id, SessionId.LENGTH);
if (type == SHARE_MSG_TYPE_INVITATION) {
checkSize(body, 4, 5);
String name = body.getString(2);
checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
byte[] salt = body.getRaw(3);
checkLength(salt, FORUM_SALT_LENGTH);
d.put(FORUM_NAME, name);
d.put(FORUM_SALT, salt);
if (body.size() > 4) {
String msg = body.getString(4);
checkLength(msg, 0, MAX_MESSAGE_BODY_LENGTH);
d.put(INVITATION_MSG, msg);
}
} else {
checkSize(body, 2);
if (type != SHARE_MSG_TYPE_ACCEPT &&
type != SHARE_MSG_TYPE_DECLINE &&
type != SHARE_MSG_TYPE_LEAVE &&
type != SHARE_MSG_TYPE_ABORT) {
throw new FormatException();
}
}
// Return the metadata
d.put(TYPE, type);
d.put(SESSION_ID, id);
d.put(GROUP_ID, m.getGroupId());
d.put(LOCAL, false);
d.put(TIME, m.getTimestamp());
return d;
}
}

View File

@@ -1,265 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ProtocolEngine;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfEntry;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationReceivedEvent;
import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.InviteeAction;
import org.briarproject.api.forum.InviteeProtocolState;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_SHARED_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_WITH_US;
import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ABORT;
import static org.briarproject.api.forum.InviteeAction.LOCAL_ACCEPT;
import static org.briarproject.api.forum.InviteeAction.LOCAL_DECLINE;
import static org.briarproject.api.forum.InviteeAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.InviteeAction.REMOTE_INVITATION;
import static org.briarproject.api.forum.InviteeAction.REMOTE_LEAVE;
import static org.briarproject.api.forum.InviteeProtocolState.ERROR;
import static org.briarproject.api.forum.InviteeProtocolState.FINISHED;
import static org.briarproject.api.forum.InviteeProtocolState.LEFT;
public class InviteeEngine
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
private static final Logger LOG =
Logger.getLogger(SharerEngine.class.getName());
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
try {
InviteeProtocolState currentState =
getState(localState.getLong(STATE));
long type = localAction.getLong(TYPE);
InviteeAction action = InviteeAction.getLocal(type);
InviteeProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState);
}
if (nextState == ERROR) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Error: Invalid action in state " +
currentState.name());
}
return noUpdate(localState, true);
}
List<BdfDictionary> messages;
List<Event> events = Collections.emptyList();
if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
BdfDictionary msg = BdfDictionary.of(
new BdfEntry(SESSION_ID, localState.getRaw(SESSION_ID)),
new BdfEntry(GROUP_ID, localState.getRaw(GROUP_ID))
);
if (action == LOCAL_ACCEPT) {
localState.put(TASK, TASK_ADD_SHARED_FORUM);
msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
} else {
localState.put(TASK,
TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
msg.put(TYPE, SHARE_MSG_TYPE_DECLINE);
}
messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg);
}
else if (action == LOCAL_LEAVE) {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg);
}
else {
throw new IllegalArgumentException("Unknown Local Action");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
try {
InviteeProtocolState currentState =
getState(localState.getLong(STATE));
long type = msg.getLong(TYPE);
InviteeAction action = InviteeAction.getRemote(type);
InviteeProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
logMessageReceived(currentState, nextState, type, msg);
if (nextState == ERROR) {
if (currentState != ERROR) {
return abortSession(currentState, localState);
} else {
return noUpdate(localState, true);
}
}
List<BdfDictionary> messages = Collections.emptyList();
List<Event> events = Collections.emptyList();
boolean deleteMsg = false;
if (currentState == LEFT) {
// ignore and delete messages coming in while in that state
deleteMsg = true;
}
// the sharer left the forum she had shared with us
else if (action == REMOTE_LEAVE && currentState == FINISHED) {
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_WITH_US);
}
else if (currentState == FINISHED) {
// ignore and delete messages coming in while in that state
// note that LEAVE is possible, but was handled above
deleteMsg = true;
}
// the sharer left the forum before we couldn't even respond
else if (action == REMOTE_LEAVE) {
localState.put(TASK, TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
}
// we have just received our invitation
else if (action == REMOTE_INVITATION) {
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
// TODO how to get the proper group here?
Forum forum = new Forum(null, localState.getString(FORUM_NAME),
localState.getRaw(FORUM_SALT));
ContactId contactId = new ContactId(
localState.getLong(CONTACT_ID).intValue());
Event event = new ForumInvitationReceivedEvent(forum, contactId);
events = Collections.singletonList(event);
}
else {
throw new IllegalArgumentException("Bad state");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
private void logLocalAction(InviteeProtocolState state,
BdfDictionary localState, BdfDictionary msg) {
if (!LOG.isLoggable(INFO)) return;
String a = "response";
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
try {
LOG.info("Sending " + a + " in state " + state.name() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " +
getState(localState.getLong(STATE)).name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void logMessageReceived(InviteeProtocolState currentState,
InviteeProtocolState nextState, long type, BdfDictionary msg) {
if (!LOG.isLoggable(INFO)) return;
try {
String t = "unknown";
if (type == SHARE_MSG_TYPE_INVITATION) t = "INVITE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " + nextState.name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
try {
return noUpdate(localState, false);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
private InviteeProtocolState getState(Long state) {
return InviteeProtocolState.fromValue(state.intValue());
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
InviteeProtocolState currentState, BdfDictionary localState)
throws FormatException {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Aborting protocol session " +
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
" in state " + currentState.name());
}
localState.put(STATE, ERROR.getValue());
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
List<BdfDictionary> messages = Collections.singletonList(msg);
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState, boolean delete) throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
localState, Collections.<BdfDictionary>emptyList(),
Collections.<Event>emptyList());
}
}

View File

@@ -1,264 +0,0 @@
package org.briarproject.forum;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.ProtocolEngine;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.ForumInvitationResponseReceivedEvent;
import org.briarproject.api.forum.SharerAction;
import org.briarproject.api.forum.SharerProtocolState;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.CONTACT_ID;
import static org.briarproject.api.forum.ForumConstants.FORUM_NAME;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT;
import static org.briarproject.api.forum.ForumConstants.GROUP_ID;
import static org.briarproject.api.forum.ForumConstants.INVITATION_MSG;
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ACCEPT;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_DECLINE;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_INVITATION;
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_LEAVE;
import static org.briarproject.api.forum.ForumConstants.STATE;
import static org.briarproject.api.forum.ForumConstants.TASK;
import static org.briarproject.api.forum.ForumConstants.TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TASK_SHARE_FORUM;
import static org.briarproject.api.forum.ForumConstants.TASK_UNSHARE_FORUM_SHARED_BY_US;
import static org.briarproject.api.forum.ForumConstants.TYPE;
import static org.briarproject.api.forum.SharerAction.LOCAL_ABORT;
import static org.briarproject.api.forum.SharerAction.LOCAL_INVITATION;
import static org.briarproject.api.forum.SharerAction.LOCAL_LEAVE;
import static org.briarproject.api.forum.SharerAction.REMOTE_ACCEPT;
import static org.briarproject.api.forum.SharerAction.REMOTE_DECLINE;
import static org.briarproject.api.forum.SharerAction.REMOTE_LEAVE;
import static org.briarproject.api.forum.SharerProtocolState.ERROR;
import static org.briarproject.api.forum.SharerProtocolState.FINISHED;
import static org.briarproject.api.forum.SharerProtocolState.LEFT;
public class SharerEngine
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
private static final Logger LOG =
Logger.getLogger(SharerEngine.class.getName());
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
BdfDictionary localState, BdfDictionary localAction) {
try {
SharerProtocolState currentState =
getState(localState.getLong(STATE));
long type = localAction.getLong(TYPE);
SharerAction action = SharerAction.getLocal(type);
SharerProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
if (action == LOCAL_ABORT && currentState != ERROR) {
return abortSession(currentState, localState);
}
if (nextState == ERROR) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Error: Invalid action in state " +
currentState.name());
}
return noUpdate(localState, true);
}
List<BdfDictionary> messages;
List<Event> events = Collections.emptyList();
if (action == LOCAL_INVITATION) {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_INVITATION);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
msg.put(FORUM_NAME, localState.getString(FORUM_NAME));
msg.put(FORUM_SALT, localState.getRaw(FORUM_SALT));
if (localAction.containsKey(INVITATION_MSG)) {
msg.put(INVITATION_MSG,
localAction.getString(INVITATION_MSG));
}
messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg);
// remember that we offered to share this forum
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US);
}
else if (action == LOCAL_LEAVE) {
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_LEAVE);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
messages = Collections.singletonList(msg);
logLocalAction(currentState, localState, msg);
}
else {
throw new IllegalArgumentException("Unknown Local Action");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
BdfDictionary localState, BdfDictionary msg) {
try {
SharerProtocolState currentState =
getState(localState.getLong(STATE));
long type = msg.getLong(TYPE);
SharerAction action = SharerAction.getRemote(type);
SharerProtocolState nextState = currentState.next(action);
localState.put(STATE, nextState.getValue());
logMessageReceived(currentState, nextState, type, msg);
if (nextState == ERROR) {
if (currentState != ERROR) {
return abortSession(currentState, localState);
} else {
return noUpdate(localState, true);
}
}
List<BdfDictionary> messages = Collections.emptyList();
List<Event> events = Collections.emptyList();
boolean deleteMsg = false;
if (currentState == LEFT) {
// ignore and delete messages coming in while in that state
deleteMsg = true;
}
else if (action == REMOTE_LEAVE) {
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_BY_US);
}
else if (currentState == FINISHED) {
// ignore and delete messages coming in while in that state
// note that LEAVE is possible, but was handled above
deleteMsg = true;
}
// we have sent our invitation and just got a response
else if (action == REMOTE_ACCEPT || action == REMOTE_DECLINE) {
if (action == REMOTE_ACCEPT) {
localState.put(TASK, TASK_SHARE_FORUM);
} else {
// this ensures that the forum can be shared again
localState.put(TASK,
TASK_REMOVE_FORUM_FROM_LIST_TO_BE_SHARED_BY_US);
}
String name = localState.getString(FORUM_NAME);
ContactId c = new ContactId(
localState.getLong(CONTACT_ID).intValue());
Event event = new ForumInvitationResponseReceivedEvent(name, c);
events = Collections.singletonList(event);
}
else {
throw new IllegalArgumentException("Bad state");
}
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
false, localState, messages, events);
} catch (FormatException e) {
throw new IllegalArgumentException(e);
}
}
private void logLocalAction(SharerProtocolState state,
BdfDictionary localState, BdfDictionary msg) {
if (!LOG.isLoggable(INFO)) return;
String a = "invitation";
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
try {
LOG.info("Sending " + a + " in state " + state.name() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " +
getState(localState.getLong(STATE)).name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
private void logMessageReceived(SharerProtocolState currentState,
SharerProtocolState nextState, long type, BdfDictionary msg) {
if (!LOG.isLoggable(INFO)) return;
try {
String t = "unknown";
if (type == SHARE_MSG_TYPE_ACCEPT) t = "ACCEPT";
else if (type == SHARE_MSG_TYPE_DECLINE) t = "DECLINE";
else if (type == SHARE_MSG_TYPE_LEAVE) t = "LEAVE";
else if (type == SHARE_MSG_TYPE_ABORT) t = "ABORT";
LOG.info("Received " + t + " in state " + currentState.name() +
" with session ID " +
Arrays.hashCode(msg.getRaw(SESSION_ID)) + " in group " +
Arrays.hashCode(msg.getRaw(GROUP_ID)) + ". " +
"Moving on to state " + nextState.name()
);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
}
@Override
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
BdfDictionary localState, BdfDictionary delivered) {
try {
return noUpdate(localState, false);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return null;
}
}
private SharerProtocolState getState(Long state) {
return SharerProtocolState.fromValue(state.intValue());
}
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
SharerProtocolState currentState, BdfDictionary localState)
throws FormatException {
if (LOG.isLoggable(WARNING)) {
LOG.warning("Aborting protocol session " +
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
" in state " + currentState.name());
}
localState.put(STATE, ERROR.getValue());
BdfDictionary msg = new BdfDictionary();
msg.put(TYPE, SHARE_MSG_TYPE_ABORT);
msg.put(SESSION_ID, localState.getRaw(SESSION_ID));
msg.put(GROUP_ID, localState.getRaw(GROUP_ID));
List<BdfDictionary> messages = Collections.singletonList(msg);
List<Event> events = Collections.emptyList();
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
localState, messages, events);
}
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
BdfDictionary localState, boolean delete) throws FormatException {
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
localState, Collections.<BdfDictionary>emptyList(),
Collections.<Event>emptyList());
}
}

View File

@@ -1,14 +0,0 @@
package org.briarproject.forum;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ForumManagerImplTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -1,14 +0,0 @@
package org.briarproject.forum;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ForumPostValidatorTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -1,14 +0,0 @@
package org.briarproject.forum;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ForumSharingManagerImplTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}

View File

@@ -1,14 +0,0 @@
package org.briarproject.forum;
import org.briarproject.BriarTestCase;
import org.junit.Test;
import static org.junit.Assert.fail;
public class ForumSharingValidatorTest extends BriarTestCase {
@Test
public void testUnitTestsExist() {
fail(); // FIXME: Write tests
}
}