mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Compare commits
31 Commits
beta-2016-
...
278-bqp-ui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e32139a89 | ||
|
|
0d16dd0358 | ||
|
|
3f2b85ac0d | ||
|
|
d0852d2265 | ||
|
|
9149b82cc4 | ||
|
|
b13bf66165 | ||
|
|
8c2fd905a1 | ||
|
|
38a4f73cdc | ||
|
|
1a175beac9 | ||
|
|
7e9ff40837 | ||
|
|
bd01c3732e | ||
|
|
9532a60f43 | ||
|
|
d2722eed92 | ||
|
|
886c8feb34 | ||
|
|
c7f73f9247 | ||
|
|
35156d698f | ||
|
|
1366972449 | ||
|
|
3d25c41e7a | ||
|
|
ba928875df | ||
|
|
cac0f30816 | ||
|
|
aad9f5142b | ||
|
|
9d686e16e0 | ||
|
|
2880043c07 | ||
|
|
fb85345392 | ||
|
|
0b67ec9201 | ||
|
|
fcf21b7ed7 | ||
|
|
6e545d0100 | ||
|
|
69026054cd | ||
|
|
db4b79fcae | ||
|
|
7412ca59ac | ||
|
|
ec083d617e |
@@ -2,10 +2,6 @@ apply plugin: 'com.android.library'
|
||||
apply plugin: 'witness'
|
||||
apply plugin: 'com.neenbedankt.android-apt'
|
||||
|
||||
repositories {
|
||||
maven { url 'http://repo1.maven.org/maven2' }
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.3"
|
||||
|
||||
@@ -49,6 +49,7 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
@@ -65,15 +66,15 @@ 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;
|
||||
LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
|
||||
SyncSessionFactory sync0, sync1, sync2;
|
||||
ForumManager forumManager0, forumManager1, forumManager2;
|
||||
ContactManager contactManager0, contactManager1, contactManager2;
|
||||
ContactId contactId0, contactId2, contactId1, contactId21;
|
||||
IdentityManager identityManager0, identityManager1, identityManager2;
|
||||
LocalAuthor author0, author1, author2;
|
||||
Forum forum0;
|
||||
SharerListener listener0;
|
||||
SharerListener listener0, listener2;
|
||||
InviteeListener listener1;
|
||||
|
||||
@Inject
|
||||
@@ -84,6 +85,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile ForumSharingManager forumSharingManager0;
|
||||
private volatile ForumSharingManager forumSharingManager1;
|
||||
private volatile ForumSharingManager forumSharingManager2;
|
||||
private volatile Waiter eventWaiter;
|
||||
private volatile Waiter msgWaiter;
|
||||
|
||||
@@ -92,11 +94,12 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
private final int TIMEOUT = 15000;
|
||||
private final String SHARER = "Sharer";
|
||||
private final String INVITEE = "Invitee";
|
||||
private final String SHARER2 = "Sharer2";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ForumSharingIntegrationTest.class.getName());
|
||||
|
||||
private ForumSharingIntegrationTestComponent t0, t1;
|
||||
private ForumSharingIntegrationTestComponent t0, t1, t2;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown=ExpectedException.none();
|
||||
@@ -117,17 +120,26 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
t1 = DaggerForumSharingIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(t1);
|
||||
File t2Dir = new File(testDir, SHARER2);
|
||||
t2 = DaggerForumSharingIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(t2);
|
||||
|
||||
identityManager0 = t0.getIdentityManager();
|
||||
identityManager1 = t1.getIdentityManager();
|
||||
identityManager2 = t2.getIdentityManager();
|
||||
contactManager0 = t0.getContactManager();
|
||||
contactManager1 = t1.getContactManager();
|
||||
contactManager2 = t2.getContactManager();
|
||||
forumManager0 = t0.getForumManager();
|
||||
forumManager1 = t1.getForumManager();
|
||||
forumManager2 = t2.getForumManager();
|
||||
forumSharingManager0 = t0.getForumSharingManager();
|
||||
forumSharingManager1 = t1.getForumSharingManager();
|
||||
forumSharingManager2 = t2.getForumSharingManager();
|
||||
sync0 = t0.getSyncSessionFactory();
|
||||
sync1 = t1.getSyncSessionFactory();
|
||||
sync2 = t2.getSyncSessionFactory();
|
||||
|
||||
// initialize waiters fresh for each test
|
||||
eventWaiter = new Waiter();
|
||||
@@ -261,8 +273,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
assertTrue(forumManager1.getForums().contains(forum0));
|
||||
|
||||
// sharer shares forum with invitee
|
||||
Contact c1 = contactManager0.getContact(contactId1);
|
||||
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
|
||||
.contains(contactId1));
|
||||
.contains(c1));
|
||||
// invitee gets forum shared by sharer
|
||||
Contact contact0 = contactManager1.getContact(contactId1);
|
||||
assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
|
||||
@@ -280,12 +293,11 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
|
||||
// sharer no longer shares forum with invitee
|
||||
assertFalse(forumSharingManager0.getSharedWith(forum0.getId())
|
||||
.contains(contactId1));
|
||||
.contains(c1));
|
||||
// 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));
|
||||
@@ -321,8 +333,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
assertTrue(forumManager1.getForums().contains(forum0));
|
||||
|
||||
// sharer shares forum with invitee
|
||||
Contact c1 = contactManager0.getContact(contactId1);
|
||||
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
|
||||
.contains(contactId1));
|
||||
.contains(c1));
|
||||
// invitee gets forum shared by sharer
|
||||
Contact contact0 = contactManager1.getContact(contactId1);
|
||||
assertTrue(forumSharingManager1.getSharedBy(forum0.getId())
|
||||
@@ -339,13 +352,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
|
||||
// invitee no longer shares forum with sharer
|
||||
Contact c0 = contactManager1.getContact(contactId0);
|
||||
assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
|
||||
.contains(contactId0));
|
||||
.contains(c0));
|
||||
// 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();
|
||||
@@ -474,7 +487,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
|
||||
// invitee now shares same forum back
|
||||
forumSharingManager1.sendForumInvitation(forum0.getId(), contactId0,
|
||||
forumSharingManager1.sendForumInvitation(forum0.getId(),
|
||||
contactId0,
|
||||
"I am re-sharing this forum with you.");
|
||||
|
||||
// sync re-share invitation
|
||||
@@ -526,7 +540,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
|
||||
// contacts now remove each other
|
||||
contactManager0.removeContact(contactId1);
|
||||
contactManager2.removeContact(contactId21);
|
||||
contactManager1.removeContact(contactId0);
|
||||
contactManager1.removeContact(contactId2);
|
||||
|
||||
// make sure sharer does share the forum with nobody now
|
||||
assertEquals(0,
|
||||
@@ -572,6 +588,75 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoContactsShareSameForum() throws Exception {
|
||||
startLifecycles();
|
||||
try {
|
||||
// initialize
|
||||
addDefaultIdentities();
|
||||
addDefaultContacts();
|
||||
addForumForSharer();
|
||||
|
||||
// second sharer adds the same forum
|
||||
DatabaseComponent db2 = t2.getDatabaseComponent();
|
||||
Transaction txn = db2.startTransaction(false);
|
||||
db2.addGroup(txn, forum0.getGroup());
|
||||
txn.setComplete();
|
||||
db2.endTransaction(txn);
|
||||
|
||||
// add listeners
|
||||
listener0 = new SharerListener();
|
||||
t0.getEventBus().addListener(listener0);
|
||||
listener1 = new InviteeListener(true, false);
|
||||
t1.getEventBus().addListener(listener1);
|
||||
listener2 = new SharerListener();
|
||||
t2.getEventBus().addListener(listener2);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
|
||||
// sync first request message
|
||||
syncToInvitee();
|
||||
|
||||
// second sharer sends invitation for same forum
|
||||
forumSharingManager2
|
||||
.sendForumInvitation(forum0.getId(), contactId1, null);
|
||||
// sync second request message
|
||||
deliverMessage(sync2, contactId2, sync1, contactId1,
|
||||
"Sharer2 to Invitee");
|
||||
|
||||
// make sure we have only one forum available
|
||||
Collection<Forum> forums =
|
||||
forumSharingManager1.getAvailableForums();
|
||||
assertEquals(1, forums.size());
|
||||
|
||||
// make sure both sharers actually share the forum
|
||||
Collection<Contact> contacts =
|
||||
forumSharingManager1.getSharedBy(forum0.getId());
|
||||
assertEquals(2, contacts.size());
|
||||
|
||||
// answer second request
|
||||
Contact c2 = contactManager1.getContact(contactId2);
|
||||
forumSharingManager1.respondToInvitation(forum0, c2, true);
|
||||
// sync response
|
||||
deliverMessage(sync1, contactId21, sync2, contactId2,
|
||||
"Invitee to Sharer2");
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener2.responseReceived);
|
||||
|
||||
// answer first request
|
||||
Contact c0 =
|
||||
contactManager1.getContact(contactId0);
|
||||
forumSharingManager1.respondToInvitation(forum0, c0, true);
|
||||
// sync response
|
||||
syncToSharer();
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
} finally {
|
||||
stopLifecycles();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void tearDown() throws InterruptedException {
|
||||
@@ -609,7 +694,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
requestReceived = true;
|
||||
Forum f = event.getForum();
|
||||
try {
|
||||
forumSharingManager0.respondToInvitation(f, true);
|
||||
Contact c = contactManager0.getContact(contactId1);
|
||||
forumSharingManager0.respondToInvitation(f, c, true);
|
||||
} catch (DbException ex) {
|
||||
eventWaiter.rethrow(ex);
|
||||
} finally {
|
||||
@@ -624,10 +710,15 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
public volatile boolean requestReceived = false;
|
||||
public volatile boolean responseReceived = false;
|
||||
|
||||
private final boolean accept;
|
||||
private final boolean accept, answer;
|
||||
|
||||
InviteeListener(boolean accept, boolean answer) {
|
||||
this.accept = accept;
|
||||
this.answer = answer;
|
||||
}
|
||||
|
||||
InviteeListener(boolean accept) {
|
||||
this.accept = accept;
|
||||
this(accept, true);
|
||||
}
|
||||
|
||||
public void eventOccurred(Event e) {
|
||||
@@ -644,13 +735,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
} else if (e instanceof ForumInvitationReceivedEvent) {
|
||||
ForumInvitationReceivedEvent event =
|
||||
(ForumInvitationReceivedEvent) e;
|
||||
eventWaiter.assertEquals(contactId0, event.getContactId());
|
||||
requestReceived = true;
|
||||
if (!answer) return;
|
||||
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);
|
||||
Contact c =
|
||||
contactManager1.getContact(event.getContactId());
|
||||
forumSharingManager1.respondToInvitation(f, c, accept);
|
||||
} catch (DbException ex) {
|
||||
eventWaiter.rethrow(ex);
|
||||
} finally {
|
||||
@@ -672,18 +763,23 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
// Start the lifecycle manager and wait for it to finish
|
||||
lifecycleManager0 = t0.getLifecycleManager();
|
||||
lifecycleManager1 = t1.getLifecycleManager();
|
||||
lifecycleManager2 = t2.getLifecycleManager();
|
||||
lifecycleManager0.startServices();
|
||||
lifecycleManager1.startServices();
|
||||
lifecycleManager2.startServices();
|
||||
lifecycleManager0.waitForStartup();
|
||||
lifecycleManager1.waitForStartup();
|
||||
lifecycleManager2.waitForStartup();
|
||||
}
|
||||
|
||||
private void stopLifecycles() throws InterruptedException {
|
||||
// Clean up
|
||||
lifecycleManager0.stopServices();
|
||||
lifecycleManager1.stopServices();
|
||||
lifecycleManager2.stopServices();
|
||||
lifecycleManager0.waitForShutdown();
|
||||
lifecycleManager1.waitForShutdown();
|
||||
lifecycleManager2.waitForShutdown();
|
||||
}
|
||||
|
||||
private void defaultInit(boolean accept) throws DbException {
|
||||
@@ -702,6 +798,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||
TestUtils.getRandomBytes(123));
|
||||
identityManager1.addLocalAuthor(author1);
|
||||
author2 = authorFactory.createLocalAuthor(SHARER2,
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
|
||||
TestUtils.getRandomBytes(123));
|
||||
identityManager2.addLocalAuthor(author2);
|
||||
}
|
||||
|
||||
private void addDefaultContacts() throws DbException {
|
||||
@@ -710,17 +810,25 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
author0.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
// invitee adds sharer back
|
||||
// second sharer does the same
|
||||
contactId21 = contactManager2.addContact(author1,
|
||||
author2.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
// invitee adds sharers back
|
||||
contactId0 = contactManager1.addContact(author0,
|
||||
author1.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
contactId2 = contactManager1.addContact(author2,
|
||||
author1.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
private void addForumForSharer() throws DbException {
|
||||
// sharer creates forum
|
||||
forum0 = forumManager0.createForum("Test Forum");
|
||||
forumManager0.addForum(forum0);
|
||||
forum0 = forumManager0.addForum("Test Forum");
|
||||
}
|
||||
|
||||
private void listenToEvents(boolean accept) {
|
||||
@@ -728,6 +836,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
|
||||
t0.getEventBus().addListener(listener0);
|
||||
listener1 = new InviteeListener(accept);
|
||||
t1.getEventBus().addListener(listener1);
|
||||
listener2 = new SharerListener();
|
||||
t2.getEventBus().addListener(listener2);
|
||||
}
|
||||
|
||||
private void syncToInvitee() throws IOException, TimeoutException {
|
||||
|
||||
@@ -832,6 +832,106 @@ public class IntroductionIntegrationTest extends BriarTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroduceesRemovedCleanup() throws Exception {
|
||||
startLifecycles();
|
||||
try {
|
||||
// Add Identities
|
||||
addDefaultIdentities();
|
||||
|
||||
// Add Transport Properties
|
||||
addTransportProperties();
|
||||
|
||||
// Add introducees as contacts
|
||||
contactId1 = contactManager0.addContact(author1,
|
||||
author0.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
contactId2 = contactManager0.addContact(author2,
|
||||
author0.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
// Add introducer back
|
||||
contactId0 = contactManager1.addContact(author0,
|
||||
author1.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
ContactId contactId02 = contactManager2.addContact(author0,
|
||||
author2.getId(), master, clock.currentTimeMillis(), true,
|
||||
true
|
||||
);
|
||||
assertTrue(contactId0.equals(contactId02));
|
||||
|
||||
// listen to events
|
||||
IntroducerListener listener0 = new IntroducerListener();
|
||||
t0.getEventBus().addListener(listener0);
|
||||
IntroduceeListener listener1 = new IntroduceeListener(1, true);
|
||||
t1.getEventBus().addListener(listener1);
|
||||
IntroduceeListener listener2 = new IntroduceeListener(2, true);
|
||||
t2.getEventBus().addListener(listener2);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
Contact introducee1 = contactManager0.getContact(contactId1);
|
||||
Contact introducee2 = contactManager0.getContact(contactId2);
|
||||
introductionManager0
|
||||
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
||||
|
||||
// sync first request message
|
||||
deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// get database and local group for introducee
|
||||
DatabaseComponent db0 = t0.getDatabaseComponent();
|
||||
IntroductionGroupFactory groupFactory0 =
|
||||
t0.getIntroductionGroupFactory();
|
||||
Group group1 = groupFactory0.createLocalGroup();
|
||||
|
||||
// get local session state messages
|
||||
Map<MessageId, Metadata> map;
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// check that we have one session state
|
||||
assertEquals(1, map.size());
|
||||
|
||||
// introducer removes introducee1
|
||||
contactManager0.removeContact(contactId1);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// make sure local state is still there
|
||||
assertEquals(1, map.size());
|
||||
|
||||
// introducer removes other introducee
|
||||
contactManager0.removeContact(contactId2);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// make sure local state is gone now
|
||||
assertEquals(0, map.size());
|
||||
} finally {
|
||||
stopLifecycles();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add a test for faking responses when #256 is implemented
|
||||
|
||||
@After
|
||||
|
||||
@@ -149,6 +149,16 @@
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.forum.ForumSharingStatusActivity"
|
||||
android:label="@string/forum_sharing_status"
|
||||
android:parentActivityName=".android.forum.ForumActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.forum.ForumActivity"
|
||||
/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.forum.WriteForumPostActivity"
|
||||
android:label="@string/app_name"
|
||||
|
||||
@@ -6,11 +6,6 @@ apply plugin: 'witness'
|
||||
apply plugin: 'com.neenbedankt.android-apt'
|
||||
apply plugin: 'de.undercouch.download'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def supportVersion = '23.2.1'
|
||||
compile project(':briar-api')
|
||||
|
||||
21
briar-android/res/drawable/bubble.xml
Normal file
21
briar-android/res/drawable/bubble.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<corners
|
||||
android:radius="@dimen/unread_bubble_size"/>
|
||||
|
||||
<padding
|
||||
android:left="@dimen/unread_bubble_padding_horizontal"
|
||||
android:right="@dimen/unread_bubble_padding_horizontal"/>
|
||||
|
||||
<solid
|
||||
android:color="@color/briar_primary"/>
|
||||
|
||||
<stroke
|
||||
android:color="@color/briar_text_primary_inverse"
|
||||
android:width="@dimen/avatar_border_width"/>
|
||||
|
||||
</shape>
|
||||
|
||||
52
briar-android/res/layout/activity_create_identity.xml
Normal file
52
briar-android/res/layout/activity_create_identity.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_activity_horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="@string/choose_nickname"
|
||||
android:textSize="@dimen/text_size_large"/>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/nicknameInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/nicknameEntry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:ems="10"
|
||||
android:inputType="textPersonName"
|
||||
android:maxLines="1"/>
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/createIdentityButton"
|
||||
style="@style/BriarButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:enabled="false"
|
||||
android:text="@string/create_identity_button"/>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
9
briar-android/res/layout/activity_expired.xml
Normal file
9
briar-android/res/layout/activity_expired.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:text="@string/expiry_warning"
|
||||
android:textSize="@dimen/text_size_large"/>
|
||||
48
briar-android/res/layout/activity_forum_sharing_status.xml
Normal file
48
briar-android/res/layout/activity_forum_sharing_status.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/default_separator_inverted"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:text="@string/forum_shared_by"
|
||||
android:textSize="@dimen/text_size_large"/>
|
||||
|
||||
<View style="@style/Divider.ForumList"/>
|
||||
|
||||
<org.briarproject.android.util.BriarRecyclerView
|
||||
android:id="@+id/sharedByView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/margin_medium"
|
||||
android:paddingTop="@dimen/margin_medium"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/default_separator_inverted"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:text="@string/forum_shared_with"
|
||||
android:textSize="@dimen/text_size_large"/>
|
||||
|
||||
<View style="@style/Divider.ForumList"/>
|
||||
|
||||
<org.briarproject.android.util.BriarRecyclerView
|
||||
android:id="@+id/sharedWithView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/margin_medium"
|
||||
android:paddingTop="@dimen/margin_medium"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -1,19 +1,19 @@
|
||||
<?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="match_parent">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarView"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
app:civ_border_color="@color/briar_primary"/>
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/statusView"
|
||||
|
||||
@@ -9,11 +9,10 @@
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/contactAvatar"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:transitionName="avatar"
|
||||
app:civ_border_color="@color/action_bar_text"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<ImageView
|
||||
@@ -22,7 +21,7 @@
|
||||
android:layout_height="15dp"
|
||||
android:layout_gravity="bottom|right"
|
||||
android:scaleType="fitCenter"
|
||||
tools:src="@drawable/contact_online"
|
||||
tools:ignore="ContentDescription"/>
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/contact_online"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -9,11 +8,11 @@
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarView"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/dropdown_picture_size"
|
||||
android:layout_height="@dimen/dropdown_picture_size"
|
||||
android:layout_margin="@dimen/margin_small"
|
||||
app:civ_border_color="@color/briar_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"/>
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nameView"
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<org.briarproject.android.util.ViewfinderView
|
||||
android:id="@+id/viewfinder_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarContact1"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
@@ -31,8 +32,6 @@
|
||||
android:layout_marginRight="@dimen/listitem_horizontal_margin"
|
||||
android:layout_toLeftOf="@+id/introductionIcon"
|
||||
android:layout_toStartOf="@+id/introductionIcon"
|
||||
app:civ_border_color="@color/briar_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<ImageView
|
||||
@@ -45,6 +44,7 @@
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarContact2"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_centerHorizontal="true"
|
||||
@@ -53,8 +53,6 @@
|
||||
android:layout_toEndOf="@+id/introductionIcon"
|
||||
android:layout_toRightOf="@+id/introductionIcon"
|
||||
android:transitionName="avatar"
|
||||
app:civ_border_color="@color/briar_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -10,36 +10,55 @@
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/listitem_horizontal_margin"
|
||||
android:paddingBottom="@dimen/listitem_horizontal_margin"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingBottom="@dimen/listitem_horizontal_margin"
|
||||
android:paddingTop="@dimen/listitem_horizontal_margin"
|
||||
>
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarView"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
<FrameLayout
|
||||
android:id="@+id/avatarFrameView"
|
||||
android:layout_width="@dimen/listitem_picture_frame_size"
|
||||
android:layout_height="@dimen/listitem_picture_frame_size"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:transitionName="avatar"
|
||||
app:civ_border_color="@color/briar_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarView"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size"
|
||||
android:layout_height="@dimen/listitem_picture_size"
|
||||
android:layout_gravity="bottom|left"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/unreadCountView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/unread_bubble_size"
|
||||
android:layout_gravity="right|top"
|
||||
android:background="@drawable/bubble"
|
||||
android:gravity="center"
|
||||
android:minWidth="@dimen/unread_bubble_size"
|
||||
android:textColor="@color/briar_text_primary_inverse"
|
||||
android:textSize="@dimen/unread_bubble_text_size"
|
||||
android:textStyle="bold"
|
||||
tools:text="123"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/textViews"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:layout_toEndOf="@+id/avatarFrameView"
|
||||
android:layout_toLeftOf="@+id/bulbView"
|
||||
android:layout_toRightOf="@+id/avatarView"
|
||||
android:layout_toEndOf="@+id/avatarView">
|
||||
android:layout_toRightOf="@+id/avatarFrameView"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nameView"
|
||||
|
||||
32
briar-android/res/layout/list_item_contact_small.xml
Normal file
32
briar-android/res/layout/list_item_contact_small.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="@dimen/margin_small"
|
||||
android:paddingTop="@dimen/margin_small">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarView"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_picture_size_small"
|
||||
android:layout_height="@dimen/listitem_picture_size_small"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nameView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
tools:text="This is a name of a contact"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarView"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/listitem_selectable_picture_size"
|
||||
android:layout_height="@dimen/listitem_selectable_picture_size"
|
||||
android:layout_alignParentLeft="true"
|
||||
@@ -22,8 +23,6 @@
|
||||
android:layout_marginLeft="@dimen/listitem_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/listitem_horizontal_margin"
|
||||
android:transitionName="avatar"
|
||||
app:civ_border_color="@color/briar_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"
|
||||
tools:src="@drawable/ic_launcher"/>
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<?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">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/avatarBackground"
|
||||
style="@style/BriarAvatar"
|
||||
android:layout_width="@dimen/avatar_forum_size"
|
||||
android:layout_height="@dimen/avatar_forum_size"
|
||||
android:layout_gravity="center"
|
||||
android:src="@android:color/transparent"
|
||||
app:civ_fill_color="@color/briar_button_positive"
|
||||
app:civ_border_color="@color/briar_primary"
|
||||
app:civ_border_width="@dimen/avatar_border_width"/>
|
||||
app:civ_fill_color="@color/briar_button_positive"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatTextView
|
||||
android:id="@+id/textAvatarView"
|
||||
@@ -29,4 +28,3 @@
|
||||
tools:text="T"/>
|
||||
|
||||
</merge>
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
android:title="@string/forum_share_button"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_forum_sharing_status"
|
||||
android:title="@string/forum_sharing_status"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_forum_delete"
|
||||
android:icon="@drawable/action_delete_white"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<color name="horizontal_border">#CCCCCC</color>
|
||||
<color name="forums_available_background">@color/briar_gold</color>
|
||||
<color name="no_private_messages">#AAAAAA</color>
|
||||
<color name="forum_avatar_shadow">#b3b3b3</color>
|
||||
<color name="forum_avatar_shadow">#99000000</color>
|
||||
|
||||
<color name="briar_primary">@color/briar_blue</color>
|
||||
<color name="briar_primary_dark">@color/briar_blue_dark</color>
|
||||
@@ -36,10 +36,17 @@
|
||||
|
||||
<!-- this is needed as preference_category_material layout uses this color as the text color -->
|
||||
<color name="preference_fallback_accent_color">@color/briar_accent</color>
|
||||
<color name="divider">#c1c1c1</color>
|
||||
<color name="default_separator">#000000</color>
|
||||
<color name="default_separator_inverted">#ffffff</color>
|
||||
<color name="menu_background">#FFFFFF</color>
|
||||
|
||||
<color name="spinner_border">#61000000</color> <!-- 38% Black -->
|
||||
<color name="spinner_arrow">@color/briar_blue_dark</color>
|
||||
|
||||
<!-- ViewfinderView -->
|
||||
<color name="possible_result_points">#c0ffbd21</color> <!-- Material Yellow 700 with alpha -->
|
||||
<color name="result_view">#b0000000</color>
|
||||
<color name="viewfinder_laser">#d50000</color> <!-- Red accent 700 -->
|
||||
<color name="viewfinder_mask">#60000000</color>
|
||||
</resources>
|
||||
@@ -26,10 +26,17 @@
|
||||
<dimen name="listitem_height_one_line_avatar">56dp</dimen>
|
||||
<dimen name="listitem_height_contact_selector">68dp</dimen>
|
||||
<dimen name="listitem_picture_size">48dp</dimen>
|
||||
<dimen name="listitem_picture_size_small">23dp</dimen>
|
||||
<dimen name="listitem_picture_frame_size">50dp</dimen>
|
||||
<dimen name="listitem_selectable_picture_size">40dp</dimen>
|
||||
<dimen name="dropdown_picture_size">32dp</dimen>
|
||||
<dimen name="avatar_forum_size">48dp</dimen>
|
||||
<dimen name="avatar_border_width">1dp</dimen>
|
||||
<dimen name="avatar_border_width">2dp</dimen>
|
||||
|
||||
<dimen name="unread_bubble_text_size">12sp</dimen>
|
||||
<dimen name="unread_bubble_border_width">2dp</dimen>
|
||||
<dimen name="unread_bubble_padding_horizontal">6dp</dimen>
|
||||
<dimen name="unread_bubble_size">19dp</dimen>
|
||||
|
||||
<dimen name="message_bubble_margin_tail">14dp</dimen>
|
||||
<dimen name="message_bubble_margin_non_tail">51dp</dimen>
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
<string name="show_forums">Show</string>
|
||||
<string name="forum_leave">Leave Forum</string>
|
||||
<string name="forum_left_toast">Left Forum</string>
|
||||
<string name="forum_sharing_status">Sharing Status</string>
|
||||
<string name="no_forum_posts">No posts</string>
|
||||
<string name="no_unread_posts">no unread posts</string>
|
||||
<plurals name="unread_posts">
|
||||
@@ -109,6 +110,9 @@
|
||||
<string name="forum_joined_toast">Joined Forum</string>
|
||||
<string name="forum_declined_toast">Forum Invitation Declined</string>
|
||||
<string name="shared_by_format">Shared by %s</string>
|
||||
<string name="forum_shared_by">Shared by</string>
|
||||
<string name="forum_shared_with">Shared with</string>
|
||||
<string name="nobody">Nobody</string>
|
||||
<string name="no_contacts_prompt">You don\'t have any contacts. Add a contact now?</string>
|
||||
<string name="add_button">Add</string>
|
||||
<string name="cancel_button">Cancel</string>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</style>
|
||||
|
||||
<style name="Divider">
|
||||
<item name="android:background">?android:attr/listDivider</item>
|
||||
<item name="android:background">@color/divider</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider.Horizontal" parent="Divider">
|
||||
@@ -112,6 +112,14 @@
|
||||
<item name="android:layout_height">1dp</item>
|
||||
</style>
|
||||
|
||||
<style name="BriarAvatar">
|
||||
<item name="civ_border_width">@dimen/avatar_border_width</item>
|
||||
<item name="civ_border_color">@color/briar_primary</item>
|
||||
|
||||
<!-- Remove when we are using 'de.hdodenhof:circleimageview:2.1.0' -->
|
||||
<item name="civ_border_overlay">true</item>
|
||||
</style>
|
||||
|
||||
<style name="NavMenuButton" parent="Widget.AppCompat.Button.Borderless.Colored">
|
||||
<item name="android:textSize">@dimen/text_size_medium</item>
|
||||
<item name="android:textColor">@android:color/tertiary_text_light</item>
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.briarproject.android.forum.AvailableForumsActivity;
|
||||
import org.briarproject.android.forum.ContactSelectorFragment;
|
||||
import org.briarproject.android.forum.CreateForumActivity;
|
||||
import org.briarproject.android.forum.ForumActivity;
|
||||
import org.briarproject.android.forum.ForumSharingStatusActivity;
|
||||
import org.briarproject.android.forum.ReadForumPostActivity;
|
||||
import org.briarproject.android.forum.ShareForumActivity;
|
||||
import org.briarproject.android.forum.ShareForumMessageFragment;
|
||||
@@ -59,6 +60,8 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(ShareForumActivity activity);
|
||||
|
||||
void inject(ForumSharingStatusActivity activity);
|
||||
|
||||
void inject(ReadForumPostActivity activity);
|
||||
|
||||
void inject(ForumActivity activity);
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package org.briarproject.android;
|
||||
|
||||
import static android.view.Gravity.CENTER;
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||
import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.util.LayoutUtils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||
import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
|
||||
|
||||
public class ExpiredActivity extends Activity {
|
||||
|
||||
@@ -21,19 +16,6 @@ public class ExpiredActivity extends Activity {
|
||||
|
||||
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
|
||||
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_MATCH);
|
||||
layout.setGravity(CENTER);
|
||||
|
||||
int pad = LayoutUtils.getPadding(this);
|
||||
|
||||
TextView warning = new TextView(this);
|
||||
warning.setGravity(CENTER);
|
||||
warning.setTextSize(18);
|
||||
warning.setPadding(pad, pad, pad, pad);
|
||||
warning.setText(R.string.expiry_warning);
|
||||
layout.addView(warning);
|
||||
|
||||
setContentView(layout);
|
||||
setContentView(R.layout.activity_expired);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
@@ -11,6 +12,7 @@ import android.widget.TextView;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.identity.Author;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -48,6 +50,9 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
|
||||
if (listener != null) listener.onItemClick(ui.avatar, item);
|
||||
}
|
||||
});
|
||||
|
||||
ViewCompat.setTransitionName(ui.avatar, "avatar" +
|
||||
StringUtils.toHexString(item.getGroupId().getBytes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -10,6 +11,7 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
public class ContactListAdapter
|
||||
extends BaseContactListAdapter<ContactListAdapter.ContactHolder> {
|
||||
@@ -32,18 +34,13 @@ public class ContactListAdapter
|
||||
|
||||
ContactListItem item = getItem(position);
|
||||
|
||||
// name and unread count
|
||||
String contactName = item.getContact().getAuthor().getName();
|
||||
// unread count
|
||||
int unread = item.getUnreadCount();
|
||||
if (unread > 0) {
|
||||
// TODO show these in a bubble on top of the avatar
|
||||
ui.name.setText(contactName + " (" + unread + ")");
|
||||
|
||||
// different background for contacts with unread messages
|
||||
ui.layout.setBackgroundColor(
|
||||
ContextCompat.getColor(ctx, R.color.unread_background));
|
||||
ui.unread.setText(String.valueOf(unread));
|
||||
ui.unread.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
ui.name.setText(contactName);
|
||||
ui.unread.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
// date of last message
|
||||
@@ -62,12 +59,16 @@ public class ContactListAdapter
|
||||
} else {
|
||||
ui.bulb.setImageResource(R.drawable.contact_disconnected);
|
||||
}
|
||||
|
||||
ViewCompat.setTransitionName(ui.bulb,
|
||||
"bulb" + StringUtils.toHexString(item.getGroupId().getBytes()));
|
||||
}
|
||||
|
||||
protected static class ContactHolder
|
||||
extends BaseContactListAdapter.BaseContactHolder {
|
||||
|
||||
public final ImageView bulb;
|
||||
public final TextView unread;
|
||||
public final TextView date;
|
||||
public final TextView identity;
|
||||
|
||||
@@ -75,6 +76,7 @@ public class ContactListAdapter
|
||||
super(v);
|
||||
|
||||
bulb = (ImageView) v.findViewById(R.id.bulbView);
|
||||
unread = (TextView) v.findViewById(R.id.unreadCountView);
|
||||
date = (TextView) v.findViewById(R.id.dateView);
|
||||
identity = (TextView) v.findViewById(R.id.identityView);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.briarproject.android.contact;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.util.Pair;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -48,6 +50,7 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.BriarActivity.GROUP_ID;
|
||||
@@ -107,16 +110,22 @@ public class ContactListFragment extends BaseFragment implements EventListener {
|
||||
ConversationActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
ActivityOptionsCompat options =
|
||||
ActivityOptionsCompat.
|
||||
makeSceneTransitionAnimation(
|
||||
getActivity(),
|
||||
view, "avatar");
|
||||
getActivity().startActivity(i, options.toBundle());
|
||||
} else {
|
||||
startActivity(i);
|
||||
}
|
||||
ContactListAdapter.ContactHolder holder =
|
||||
(ContactListAdapter.ContactHolder) list
|
||||
.getRecyclerView()
|
||||
.findViewHolderForAdapterPosition(
|
||||
adapter.findItemPosition(item));
|
||||
Pair<View, String> avatar =
|
||||
Pair.create((View) holder.avatar, ViewCompat
|
||||
.getTransitionName(holder.avatar));
|
||||
Pair<View, String> bulb =
|
||||
Pair.create((View) holder.bulb, ViewCompat
|
||||
.getTransitionName(holder.bulb));
|
||||
ActivityOptionsCompat options =
|
||||
makeSceneTransitionAnimation(getActivity(),
|
||||
avatar, bulb);
|
||||
ActivityCompat.startActivity(getActivity(), i,
|
||||
options.toBundle());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ public class ContactListItem {
|
||||
}
|
||||
|
||||
void setMessages(Collection<ConversationItem> messages) {
|
||||
empty = messages.isEmpty();
|
||||
empty = messages == null || messages.isEmpty();
|
||||
timestamp = 0;
|
||||
unread = 0;
|
||||
if (!empty) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.ActivityOptionsCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
@@ -156,6 +157,10 @@ public class ConversationActivity extends BriarActivity
|
||||
ab.setDisplayShowTitleEnabled(false);
|
||||
}
|
||||
|
||||
String hexGroupId = StringUtils.toHexString(b);
|
||||
ViewCompat.setTransitionName(toolbarAvatar, "avatar" + hexGroupId);
|
||||
ViewCompat.setTransitionName(toolbarStatus, "bulb" + hexGroupId);
|
||||
|
||||
adapter = new ConversationAdapter(this, this);
|
||||
list = (BriarRecyclerView) findViewById(R.id.conversationView);
|
||||
list.setLayoutManager(new LinearLayoutManager(this));
|
||||
@@ -207,7 +212,7 @@ public class ConversationActivity extends BriarActivity
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
supportFinishAfterTransition();
|
||||
onBackPressed();
|
||||
return true;
|
||||
case R.id.action_introduction:
|
||||
if (contactId == null) return false;
|
||||
@@ -227,6 +232,13 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// FIXME disabled exit transition, because it doesn't work for some reason
|
||||
//supportFinishAfterTransition();
|
||||
finish();
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -153,7 +153,7 @@ public class AvailableForumsActivity extends BriarActivity
|
||||
|
||||
@Override
|
||||
public void onItemClick(AvailableForumsItem item, boolean accept) {
|
||||
respondToInvitation(item.getForum(), accept);
|
||||
respondToInvitation(item, accept);
|
||||
|
||||
// show toast
|
||||
int res = R.string.forum_declined_toast;
|
||||
@@ -161,12 +161,16 @@ public class AvailableForumsActivity extends BriarActivity
|
||||
Toast.makeText(this, res, LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void respondToInvitation(final Forum f, final boolean accept) {
|
||||
private void respondToInvitation(final AvailableForumsItem item,
|
||||
final boolean accept) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
forumSharingManager.respondToInvitation(f, accept);
|
||||
Forum f = item.getForum();
|
||||
for (Contact c : item.getContacts()) {
|
||||
forumSharingManager.respondToInvitation(f, c, accept);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
|
||||
@@ -129,8 +129,7 @@ public class CreateForumActivity extends BriarActivity
|
||||
public void run() {
|
||||
try {
|
||||
long now = System.currentTimeMillis();
|
||||
Forum f = forumManager.createForum(name);
|
||||
forumManager.addForum(f);
|
||||
Forum f = forumManager.addForum(name);
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Storing forum took " + duration + " ms");
|
||||
|
||||
@@ -153,6 +153,9 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
ActivityOptionsCompat options = ActivityOptionsCompat
|
||||
.makeCustomAnimation(this, android.R.anim.slide_in_left,
|
||||
android.R.anim.slide_out_right);
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_forum_compose_post:
|
||||
@@ -166,13 +169,16 @@ public class ForumActivity extends BriarActivity implements EventListener,
|
||||
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_sharing_status:
|
||||
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
|
||||
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||
ActivityCompat.startActivity(this, i3, options.toBundle());
|
||||
return true;
|
||||
case R.id.action_forum_delete:
|
||||
showUnsubscribeDialog();
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.contact.ContactListItem;
|
||||
import org.briarproject.android.util.BriarRecyclerView;
|
||||
import org.briarproject.api.contact.Contact;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
import org.briarproject.api.identity.IdentityManager;
|
||||
import org.briarproject.api.identity.LocalAuthor;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
public class ForumSharingStatusActivity extends BriarActivity {
|
||||
|
||||
private GroupId groupId;
|
||||
private BriarRecyclerView sharedByList, sharedWithList;
|
||||
private ForumSharingStatusAdapter sharedByAdapter, sharedWithAdapter;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
protected volatile ForumSharingManager forumSharingManager;
|
||||
@Inject
|
||||
protected volatile IdentityManager identityManager;
|
||||
|
||||
public final static String TAG = "ForumSharingStatusActivity";
|
||||
private static final Logger LOG = Logger.getLogger(TAG);
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_forum_sharing_status);
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No GroupId");
|
||||
groupId = new GroupId(b);
|
||||
|
||||
sharedByList = (BriarRecyclerView) findViewById(R.id.sharedByView);
|
||||
sharedByAdapter = new ForumSharingStatusAdapter(this);
|
||||
sharedByList.setLayoutManager(new LinearLayoutManager(this));
|
||||
sharedByList.setAdapter(sharedByAdapter);
|
||||
sharedByList.setEmptyText(getString(R.string.nobody));
|
||||
|
||||
sharedWithList = (BriarRecyclerView) findViewById(R.id.sharedWithView);
|
||||
sharedWithAdapter = new ForumSharingStatusAdapter(this);
|
||||
sharedWithList.setLayoutManager(new LinearLayoutManager(this));
|
||||
sharedWithList.setAdapter(sharedWithAdapter);
|
||||
sharedWithList.setEmptyText(getString(R.string.nobody));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
loadSharedBy();
|
||||
loadSharedWith();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
// Handle presses on the action bar items
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
}
|
||||
|
||||
private void loadSharedBy() {
|
||||
dbController.runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<ContactListItem> contactItems = new ArrayList<>();
|
||||
try {
|
||||
Collection<Contact> contacts =
|
||||
forumSharingManager.getSharedBy(groupId);
|
||||
for (Contact c : contacts) {
|
||||
LocalAuthor localAuthor = identityManager
|
||||
.getLocalAuthor(c.getLocalAuthorId());
|
||||
ContactListItem item =
|
||||
new ContactListItem(c, localAuthor, false, null,
|
||||
null);
|
||||
contactItems.add(item);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
displaySharedBy(contactItems);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displaySharedBy(final List<ContactListItem> contacts) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (contacts.isEmpty()) {
|
||||
sharedByList.showData();
|
||||
} else {
|
||||
sharedByAdapter.addAll(contacts);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSharedWith() {
|
||||
dbController.runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<ContactListItem> contactItems = new ArrayList<>();
|
||||
try {
|
||||
Collection<Contact> contacts =
|
||||
forumSharingManager.getSharedWith(groupId);
|
||||
for (Contact c : contacts) {
|
||||
LocalAuthor localAuthor = identityManager
|
||||
.getLocalAuthor(c.getLocalAuthorId());
|
||||
ContactListItem item =
|
||||
new ContactListItem(c, localAuthor, false, null,
|
||||
null);
|
||||
contactItems.add(item);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
displaySharedWith(contactItems);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displaySharedWith(final List<ContactListItem> contacts) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (contacts.isEmpty()) {
|
||||
sharedWithList.showData();
|
||||
} else {
|
||||
sharedWithAdapter.addAll(contacts);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.briarproject.android.forum;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.format.DateUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.contact.BaseContactListAdapter;
|
||||
import org.briarproject.android.contact.ContactListItem;
|
||||
|
||||
public class ForumSharingStatusAdapter
|
||||
extends BaseContactListAdapter<BaseContactListAdapter.BaseContactHolder> {
|
||||
|
||||
public ForumSharingStatusAdapter(Context context) {
|
||||
super(context, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseContactHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
|
||||
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
|
||||
R.layout.list_item_contact_small, viewGroup, false);
|
||||
|
||||
return new BaseContactHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareContactListItems(ContactListItem c1, ContactListItem c2) {
|
||||
return compareByName(c1, c2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,12 +2,14 @@ package org.briarproject.android.identity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
@@ -16,7 +18,7 @@ import android.widget.Toast;
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.ActivityComponent;
|
||||
import org.briarproject.android.BriarActivity;
|
||||
import org.briarproject.android.util.LayoutUtils;
|
||||
import org.briarproject.android.util.AndroidUtils;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.CryptoExecutor;
|
||||
import org.briarproject.api.crypto.KeyPair;
|
||||
@@ -31,18 +33,11 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.text.InputType.TYPE_CLASS_TEXT;
|
||||
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_WORDS;
|
||||
import static android.view.Gravity.CENTER;
|
||||
import static android.view.Gravity.CENTER_HORIZONTAL;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.widget.LinearLayout.VERTICAL;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.MATCH_MATCH;
|
||||
import static org.briarproject.android.util.CommonLayoutParams.WRAP_WRAP;
|
||||
import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
|
||||
public class CreateIdentityActivity extends BriarActivity
|
||||
@@ -55,10 +50,10 @@ public class CreateIdentityActivity extends BriarActivity
|
||||
@CryptoExecutor
|
||||
protected Executor cryptoExecutor;
|
||||
|
||||
private TextInputLayout nicknameInput;
|
||||
private EditText nicknameEntry;
|
||||
private Button createIdentityButton;
|
||||
private ProgressBar progress;
|
||||
private TextView feedback;
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
@@ -71,52 +66,32 @@ public class CreateIdentityActivity extends BriarActivity
|
||||
@Override
|
||||
public void onCreate(Bundle state) {
|
||||
super.onCreate(state);
|
||||
LinearLayout layout = new LinearLayout(this);
|
||||
layout.setLayoutParams(MATCH_MATCH);
|
||||
layout.setOrientation(VERTICAL);
|
||||
layout.setGravity(CENTER_HORIZONTAL);
|
||||
int pad = LayoutUtils.getPadding(this);
|
||||
layout.setPadding(pad, pad, pad, pad);
|
||||
|
||||
TextView chooseNickname = new TextView(this);
|
||||
chooseNickname.setGravity(CENTER);
|
||||
chooseNickname.setTextSize(18);
|
||||
chooseNickname.setText(R.string.choose_nickname);
|
||||
layout.addView(chooseNickname);
|
||||
setContentView(R.layout.activity_create_identity);
|
||||
|
||||
nicknameEntry = new EditText(this) {
|
||||
nicknameInput = (TextInputLayout) findViewById(R.id.nicknameInputLayout);
|
||||
nicknameEntry = (EditText) findViewById(R.id.nicknameEntry);
|
||||
nicknameEntry.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
protected void onTextChanged(CharSequence text, int start,
|
||||
int lengthBefore, int lengthAfter) {
|
||||
public void beforeTextChanged(CharSequence s, int start, int count,
|
||||
int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before,
|
||||
int count) {
|
||||
enableOrDisableCreateButton();
|
||||
}
|
||||
};
|
||||
nicknameEntry.setId(1);
|
||||
nicknameEntry.setMaxLines(1);
|
||||
int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS;
|
||||
nicknameEntry.setInputType(inputType);
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
nicknameEntry.setOnEditorActionListener(this);
|
||||
layout.addView(nicknameEntry);
|
||||
|
||||
feedback = new TextView(this);
|
||||
feedback.setGravity(CENTER);
|
||||
feedback.setPadding(0, pad, 0, pad);
|
||||
layout.addView(feedback);
|
||||
createIdentityButton = (Button) findViewById(R.id.createIdentityButton);
|
||||
if (createIdentityButton != null)
|
||||
createIdentityButton.setOnClickListener(this);
|
||||
|
||||
createIdentityButton = new Button(this);
|
||||
createIdentityButton.setLayoutParams(WRAP_WRAP);
|
||||
createIdentityButton.setText(R.string.create_identity_button);
|
||||
createIdentityButton.setEnabled(false);
|
||||
createIdentityButton.setOnClickListener(this);
|
||||
layout.addView(createIdentityButton);
|
||||
|
||||
progress = new ProgressBar(this);
|
||||
progress.setLayoutParams(WRAP_WRAP);
|
||||
progress.setIndeterminate(true);
|
||||
progress.setVisibility(GONE);
|
||||
layout.addView(progress);
|
||||
|
||||
setContentView(layout);
|
||||
progress = (ProgressBar) findViewById(R.id.progressBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,10 +114,11 @@ public class CreateIdentityActivity extends BriarActivity
|
||||
String nickname = nicknameEntry.getText().toString();
|
||||
int length = StringUtils.toUtf8(nickname).length;
|
||||
if (length > MAX_AUTHOR_NAME_LENGTH) {
|
||||
feedback.setText(R.string.name_too_long);
|
||||
String str = getString(R.string.name_too_long);
|
||||
AndroidUtils.setError(nicknameInput, str, true);
|
||||
return false;
|
||||
}
|
||||
feedback.setText("");
|
||||
AndroidUtils.setError(nicknameInput, null, false);
|
||||
return length > 0;
|
||||
}
|
||||
|
||||
@@ -169,7 +145,7 @@ public class CreateIdentityActivity extends BriarActivity
|
||||
}
|
||||
|
||||
private void storeLocalAuthor(final LocalAuthor a) {
|
||||
runOnDbThread(new Runnable() {
|
||||
dbController.runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
||||
@@ -19,6 +19,8 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.ResultPointCallback;
|
||||
|
||||
import org.briarproject.R;
|
||||
import org.briarproject.android.AndroidComponent;
|
||||
@@ -27,6 +29,7 @@ import org.briarproject.android.fragment.BaseEventFragment;
|
||||
import org.briarproject.android.util.CameraView;
|
||||
import org.briarproject.android.util.QrCodeDecoder;
|
||||
import org.briarproject.android.util.QrCodeUtils;
|
||||
import org.briarproject.android.util.ViewfinderView;
|
||||
import org.briarproject.api.event.Event;
|
||||
import org.briarproject.api.event.KeyAgreementAbortedEvent;
|
||||
import org.briarproject.api.event.KeyAgreementFailedEvent;
|
||||
@@ -55,7 +58,7 @@ import static java.util.logging.Level.WARNING;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class ShowQrCodeFragment extends BaseEventFragment
|
||||
implements QrCodeDecoder.ResultCallback {
|
||||
implements QrCodeDecoder.ResultCallback, ResultPointCallback {
|
||||
|
||||
public static final String TAG = "ShowQrCodeFragment";
|
||||
|
||||
@@ -75,6 +78,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
protected Executor ioExecutor;
|
||||
|
||||
private CameraView cameraView;
|
||||
private ViewfinderView viewfinderView;
|
||||
private View statusView;
|
||||
private TextView status;
|
||||
private ImageView qrCode;
|
||||
@@ -109,9 +113,13 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
cameraView = (CameraView) view.findViewById(R.id.camera_view);
|
||||
viewfinderView =
|
||||
(ViewfinderView) view.findViewById(R.id.viewfinder_view);
|
||||
statusView = view.findViewById(R.id.status_container);
|
||||
status = (TextView) view.findViewById(R.id.connect_status);
|
||||
qrCode = (ImageView) view.findViewById(R.id.qr_code);
|
||||
|
||||
viewfinderView.setFrameProvider(cameraView);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,7 +128,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
|
||||
getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||
|
||||
decoder = new QrCodeDecoder(this);
|
||||
decoder = new QrCodeDecoder(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -219,6 +227,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
getActivity().finish();
|
||||
} else {
|
||||
cameraView.start(camera, decoder, 0);
|
||||
viewfinderView.drawViewfinder();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -355,6 +364,11 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void foundPossibleResultPoint(ResultPoint point) {
|
||||
viewfinderView.addPossibleResultPoint(point);
|
||||
}
|
||||
|
||||
private class BluetoothStateReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -120,10 +120,11 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
emptyView.setVisibility(VISIBLE);
|
||||
recyclerView.setVisibility(INVISIBLE);
|
||||
} else {
|
||||
emptyView.setVisibility(INVISIBLE);
|
||||
// use GONE here so empty view doesn't use space on small lists
|
||||
emptyView.setVisibility(GONE);
|
||||
recyclerView.setVisibility(VISIBLE);
|
||||
}
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +132,9 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
if (recyclerView == null) initViews();
|
||||
recyclerView.scrollToPosition(position);
|
||||
}
|
||||
|
||||
public RecyclerView getRecyclerView() {
|
||||
return this.recyclerView;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.Camera.AutoFocusCallback;
|
||||
import android.hardware.Camera.CameraInfo;
|
||||
@@ -13,6 +15,7 @@ import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
@@ -31,17 +34,25 @@ import static java.util.logging.Level.WARNING;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
AutoFocusCallback {
|
||||
AutoFocusCallback, ViewfinderView.FrameProvider {
|
||||
|
||||
private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds
|
||||
private static final int MIN_FRAME_SIZE = 240;
|
||||
private static final int MAX_FRAME_SIZE = 675; // = 5/8 * 1080
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(CameraView.class.getName());
|
||||
|
||||
private Camera camera = null;
|
||||
private Rect framingRect;
|
||||
private Rect framingRectInPreview;
|
||||
private Rect framingRectInSensor;
|
||||
private PreviewConsumer previewConsumer = null;
|
||||
private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0;
|
||||
private boolean autoFocus = false, surfaceExists = false;
|
||||
|
||||
private Point cameraResolution;
|
||||
private final Object cameraResolutionLock = new Object();
|
||||
|
||||
public CameraView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -184,6 +195,24 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
LOG.info("No suitable focus mode");
|
||||
}
|
||||
params.setZoom(0);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
List<Camera.Area> areas = new ArrayList<>();
|
||||
areas.add(new Camera.Area(getFramingRectInSensor(), 1000));
|
||||
if (params.getMaxNumFocusAreas() > 0) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Focus areas supported: " +
|
||||
params.getMaxNumFocusAreas());
|
||||
}
|
||||
params.setFocusAreas(areas);
|
||||
}
|
||||
if (params.getMaxNumMeteringAreas() > 0) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Metering areas supported: " +
|
||||
params.getMaxNumMeteringAreas());
|
||||
}
|
||||
params.setMeteringAreas(areas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setPreviewSize(Parameters params) {
|
||||
@@ -222,6 +251,13 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Best size " + bestSize.width + "x" + bestSize.height);
|
||||
params.setPreviewSize(bestSize.width, bestSize.height);
|
||||
synchronized (cameraResolutionLock) {
|
||||
cameraResolution = new Point(bestSize.width, bestSize.height);
|
||||
}
|
||||
} else {
|
||||
synchronized (cameraResolutionLock) {
|
||||
cameraResolution = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,4 +312,152 @@ public class CameraView extends SurfaceView implements SurfaceHolder.Callback,
|
||||
LOG.log(WARNING, "Error retrying auto focus", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the framing rect which the UI should draw to show the user where to place the
|
||||
* barcode. This target helps with alignment as well as forces the user to hold the device
|
||||
* far enough away to ensure the image will be in focus.
|
||||
*
|
||||
* @return The rectangle to draw on screen in window coordinates.
|
||||
*/
|
||||
@Override
|
||||
public Rect getFramingRect() {
|
||||
if (framingRect == null) {
|
||||
framingRect = calculateFramingRect(true);
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Calculated framing rect: " + framingRect);
|
||||
}
|
||||
return framingRect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the framing rect which the UI should draw to show the user where to place the
|
||||
* barcode. This target helps with alignment as well as forces the user to hold the device
|
||||
* far enough away to ensure the image will be in focus.
|
||||
* <p/>
|
||||
* Adapted from the Zxing Barcode Scanner.
|
||||
*
|
||||
* @return The rectangle to draw on screen in window coordinates.
|
||||
*/
|
||||
private Rect calculateFramingRect(boolean withOrientation) {
|
||||
if (camera == null) {
|
||||
return null;
|
||||
}
|
||||
if (surfaceWidth == 0 || surfaceHeight == 0) {
|
||||
// Called early, before the surface is ready
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean portrait =
|
||||
withOrientation && displayOrientation % 180 == 90;
|
||||
int size = findDesiredDimensionInRange(
|
||||
portrait ? surfaceWidth : surfaceHeight,
|
||||
portrait ? surfaceHeight / 2 : surfaceWidth / 2,
|
||||
MIN_FRAME_SIZE, MAX_FRAME_SIZE);
|
||||
|
||||
int leftOffset = portrait ?
|
||||
(surfaceWidth - size) / 2 :
|
||||
((surfaceWidth / 2) - size) / 2;
|
||||
int topOffset = portrait ?
|
||||
((surfaceHeight / 2) - size) / 2 :
|
||||
(surfaceHeight - size) / 2;
|
||||
return new Rect(leftOffset, topOffset, leftOffset + size,
|
||||
topOffset + size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the square that fits best inside the given region.
|
||||
*/
|
||||
private static int findDesiredDimensionInRange(int side1, int side2,
|
||||
int hardMin, int hardMax) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Finding framing dimension, side1 = " + side1 +
|
||||
", side2 = " + side2);
|
||||
int minSide = Math.min(side1, side2);
|
||||
int dim = 5 * minSide / 8; // Target 5/8 of smallest side
|
||||
if (dim < hardMin) {
|
||||
if (hardMin > minSide) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Returning minimum side length: " + minSide);
|
||||
return minSide;
|
||||
} else {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Returning hard minimum: " + hardMin);
|
||||
return hardMin;
|
||||
}
|
||||
}
|
||||
if (dim > hardMax) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Returning hard maximum: " + hardMax);
|
||||
return hardMax;
|
||||
}
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Returning desired dimension: " + dim);
|
||||
return dim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #getFramingRect} but coordinates are in terms of the preview
|
||||
* frame, not UI / screen.
|
||||
* <p/>
|
||||
* Adapted from the Zxing Barcode Scanner.
|
||||
*
|
||||
* @return {@link Rect} expressing QR code scan area in terms of the preview size
|
||||
*/
|
||||
@Override
|
||||
public Rect getFramingRectInPreview() {
|
||||
if (framingRectInPreview == null) {
|
||||
Rect framingRect = getFramingRect();
|
||||
if (framingRect == null) {
|
||||
return null;
|
||||
}
|
||||
Rect rect = new Rect(framingRect);
|
||||
Point cameraResolution = getCameraResolution();
|
||||
if (cameraResolution == null || surfaceWidth == 0 ||
|
||||
surfaceHeight == 0) {
|
||||
// Called early, before the surface is ready
|
||||
return null;
|
||||
}
|
||||
rect.left = rect.left * cameraResolution.x / surfaceWidth;
|
||||
rect.right = rect.right * cameraResolution.x / surfaceWidth;
|
||||
rect.top = rect.top * cameraResolution.y / surfaceHeight;
|
||||
rect.bottom = rect.bottom * cameraResolution.y / surfaceHeight;
|
||||
framingRectInPreview = rect;
|
||||
}
|
||||
return framingRectInPreview;
|
||||
}
|
||||
|
||||
private Point getCameraResolution() {
|
||||
Point ret;
|
||||
synchronized (cameraResolutionLock) {
|
||||
ret = new Point(cameraResolution);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #getFramingRect} but coordinates are in terms of the sensor,
|
||||
* not UI / screen (ie. it is independent of orientation)
|
||||
*
|
||||
* @return {@link Rect} expressing QR code scan area in terms of the sensor
|
||||
*/
|
||||
private Rect getFramingRectInSensor() {
|
||||
if (framingRectInSensor == null) {
|
||||
Rect framingRect = calculateFramingRect(false);
|
||||
if (framingRect == null) {
|
||||
return null;
|
||||
}
|
||||
Rect rect = new Rect(framingRect);
|
||||
if (surfaceWidth == 0 || surfaceHeight == 0) {
|
||||
// Called early, before the surface is ready
|
||||
return null;
|
||||
}
|
||||
rect.left = (rect.left * 2000 / surfaceWidth) - 1000;
|
||||
rect.right = (rect.right * 2000 / surfaceWidth) - 1000;
|
||||
rect.top = (rect.top * 2000 / surfaceHeight) - 1000;
|
||||
rect.bottom = (rect.bottom * 2000 / surfaceHeight) - 1000;
|
||||
framingRectInSensor = rect;
|
||||
}
|
||||
return framingRectInSensor;
|
||||
}
|
||||
}
|
||||
@@ -6,14 +6,18 @@ import android.hardware.Camera.Size;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.LuminanceSource;
|
||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||
import com.google.zxing.Reader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPointCallback;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.google.zxing.qrcode.QRCodeReader;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
@@ -26,11 +30,14 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
|
||||
private final Reader reader = new QRCodeReader();
|
||||
private final ResultCallback callback;
|
||||
private final ResultPointCallback pointCallback;
|
||||
|
||||
private boolean stopped = false;
|
||||
|
||||
public QrCodeDecoder(ResultCallback callback) {
|
||||
public QrCodeDecoder(ResultCallback callback,
|
||||
ResultPointCallback pointCallback) {
|
||||
this.callback = callback;
|
||||
this.pointCallback = pointCallback;
|
||||
}
|
||||
|
||||
public void start(Camera camera) {
|
||||
@@ -72,9 +79,11 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
|
||||
LuminanceSource src = new PlanarYUVLuminanceSource(data, width,
|
||||
height, 0, 0, width, height, false);
|
||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
|
||||
Map<DecodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, pointCallback);
|
||||
Result result = null;
|
||||
try {
|
||||
result = reader.decode(bitmap);
|
||||
result = reader.decode(bitmap, hints);
|
||||
} catch (ReaderException e) {
|
||||
return null; // No barcode found
|
||||
} catch (RuntimeException e) {
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (C) 2008 ZXing authors
|
||||
* Copyright (C) 2016 Sublime Software Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.briarproject.android.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
|
||||
import org.briarproject.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This view is overlaid on top of the camera preview. It adds the viewfinder
|
||||
* rectangle and partial transparency outside it, as well as the laser scanner
|
||||
* animation and result points.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public final class ViewfinderView extends View {
|
||||
|
||||
private static final int[] SCANNER_ALPHA =
|
||||
{0, 64, 128, 192, 255, 192, 128, 64};
|
||||
private static final long ANIMATION_DELAY = 80L;
|
||||
private static final int CURRENT_POINT_OPACITY = 0xA0;
|
||||
private static final int MAX_RESULT_POINTS = 20;
|
||||
private static final int POINT_SIZE = 6;
|
||||
|
||||
private FrameProvider frameProvider;
|
||||
private final Paint paint;
|
||||
private Bitmap resultBitmap;
|
||||
private final int maskColor;
|
||||
private final int resultColor;
|
||||
private final int laserColor;
|
||||
private final int resultPointColor;
|
||||
private int scannerAlpha;
|
||||
private List<ResultPoint> possibleResultPoints;
|
||||
private List<ResultPoint> lastPossibleResultPoints;
|
||||
|
||||
// This constructor is used when the class is built from an XML resource.
|
||||
public ViewfinderView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
if (isInEditMode()) {
|
||||
paint = null;
|
||||
maskColor = 0;
|
||||
resultColor = 0;
|
||||
laserColor = 0;
|
||||
resultPointColor = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize these once for performance rather than calling them every
|
||||
// time in onDraw().
|
||||
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
Resources resources = getResources();
|
||||
maskColor = resources.getColor(R.color.viewfinder_mask);
|
||||
resultColor = resources.getColor(R.color.result_view);
|
||||
laserColor = resources.getColor(R.color.viewfinder_laser);
|
||||
resultPointColor = resources.getColor(R.color.possible_result_points);
|
||||
scannerAlpha = 0;
|
||||
possibleResultPoints = new ArrayList<>(5);
|
||||
lastPossibleResultPoints = null;
|
||||
}
|
||||
|
||||
public void setFrameProvider(FrameProvider frameProvider) {
|
||||
this.frameProvider = frameProvider;
|
||||
}
|
||||
|
||||
@SuppressLint("DrawAllocation")
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
if (frameProvider == null) {
|
||||
return; // not ready yet, early draw before done configuring
|
||||
}
|
||||
Rect frame = this.frameProvider.getFramingRect();
|
||||
Rect previewFrame = this.frameProvider.getFramingRectInPreview();
|
||||
if (frame == null || previewFrame == null) {
|
||||
return;
|
||||
}
|
||||
int width = canvas.getWidth();
|
||||
int height = canvas.getHeight();
|
||||
|
||||
// Draw the exterior (i.e. outside the framing rect) darkened
|
||||
paint.setColor(resultBitmap != null ? resultColor : maskColor);
|
||||
canvas.drawRect(0, 0, width, frame.top, paint);
|
||||
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
|
||||
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1,
|
||||
paint);
|
||||
canvas.drawRect(0, frame.bottom + 1, width, height, paint);
|
||||
|
||||
if (resultBitmap != null) {
|
||||
// Draw the opaque result bitmap over the scanning rectangle
|
||||
paint.setAlpha(CURRENT_POINT_OPACITY);
|
||||
canvas.drawBitmap(resultBitmap, null, frame, paint);
|
||||
} else {
|
||||
|
||||
// Draw a red "laser scanner" line through the middle to show
|
||||
// decoding is active
|
||||
paint.setColor(laserColor);
|
||||
paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
|
||||
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
|
||||
int middle = frame.height() / 2 + frame.top;
|
||||
canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1,
|
||||
middle + 2, paint);
|
||||
|
||||
float scaleX = frame.width() / (float) previewFrame.width();
|
||||
float scaleY = frame.height() / (float) previewFrame.height();
|
||||
|
||||
List<ResultPoint> currentPossible = possibleResultPoints;
|
||||
List<ResultPoint> currentLast = lastPossibleResultPoints;
|
||||
int frameLeft = frame.left;
|
||||
int frameTop = frame.top;
|
||||
if (currentPossible.isEmpty()) {
|
||||
lastPossibleResultPoints = null;
|
||||
} else {
|
||||
possibleResultPoints = new ArrayList<>(5);
|
||||
lastPossibleResultPoints = currentPossible;
|
||||
paint.setAlpha(CURRENT_POINT_OPACITY);
|
||||
paint.setColor(resultPointColor);
|
||||
synchronized (currentPossible) {
|
||||
for (ResultPoint point : currentPossible) {
|
||||
canvas.drawCircle(
|
||||
frameLeft + (int) (point.getX() * scaleX),
|
||||
frameTop + (int) (point.getY() * scaleY),
|
||||
POINT_SIZE, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentLast != null) {
|
||||
paint.setAlpha(CURRENT_POINT_OPACITY / 2);
|
||||
paint.setColor(resultPointColor);
|
||||
synchronized (currentLast) {
|
||||
float radius = POINT_SIZE / 2.0f;
|
||||
for (ResultPoint point : currentLast) {
|
||||
canvas.drawCircle(
|
||||
frameLeft + (int) (point.getX() * scaleX),
|
||||
frameTop + (int) (point.getY() * scaleY),
|
||||
radius, paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request another update at the animation interval, but only
|
||||
// repaint the laser line, not the entire viewfinder mask.
|
||||
postInvalidateDelayed(ANIMATION_DELAY,
|
||||
frame.left - POINT_SIZE,
|
||||
frame.top - POINT_SIZE,
|
||||
frame.right + POINT_SIZE,
|
||||
frame.bottom + POINT_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawViewfinder() {
|
||||
Bitmap resultBitmap = this.resultBitmap;
|
||||
this.resultBitmap = null;
|
||||
if (resultBitmap != null) {
|
||||
resultBitmap.recycle();
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a bitmap with the result points highlighted instead of the live
|
||||
* scanning display.
|
||||
*
|
||||
* @param barcode An image of the decoded barcode.
|
||||
*/
|
||||
public void drawResultBitmap(Bitmap barcode) {
|
||||
resultBitmap = barcode;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void addPossibleResultPoint(ResultPoint point) {
|
||||
List<ResultPoint> points = possibleResultPoints;
|
||||
synchronized (points) {
|
||||
points.add(point);
|
||||
int size = points.size();
|
||||
if (size > MAX_RESULT_POINTS) {
|
||||
// trim it
|
||||
points.subList(0, size - MAX_RESULT_POINTS / 2).clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface FrameProvider {
|
||||
|
||||
Rect getFramingRect();
|
||||
Rect getFramingRectInPreview();
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,6 @@ targetCompatibility = 1.6
|
||||
|
||||
apply plugin: 'witness'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "com.google.dagger:dagger:2.0.2"
|
||||
compile 'com.google.dagger:dagger-compiler:2.0.2'
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.api.clients;
|
||||
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.BaseMessageContext;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class BdfMessageContext extends BaseMessageContext {
|
||||
|
||||
private final BdfDictionary dictionary;
|
||||
|
||||
public BdfMessageContext(BdfDictionary dictionary,
|
||||
Collection<MessageId> dependencies) {
|
||||
|
||||
super(dependencies);
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
|
||||
public BdfMessageContext(BdfDictionary dictionary) {
|
||||
this(dictionary, null);
|
||||
}
|
||||
|
||||
public BdfDictionary getDictionary() {
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.InvalidMessageException;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
|
||||
public interface MessageQueueManager {
|
||||
|
||||
@@ -34,10 +36,11 @@ public interface MessageQueueManager {
|
||||
interface QueueMessageValidator {
|
||||
|
||||
/**
|
||||
* Validates the given message and returns its metadata if the message
|
||||
* is valid, or null if the message is invalid.
|
||||
* Validates the given message and returns its metadata and
|
||||
* dependencies.
|
||||
*/
|
||||
Metadata validateMessage(QueueMessage q, Group g);
|
||||
MessageContext validateMessage(QueueMessage q, Group g)
|
||||
throws InvalidMessageException;
|
||||
}
|
||||
|
||||
interface IncomingQueueMessageHook {
|
||||
|
||||
11
briar-api/src/org/briarproject/api/forum/ForumFactory.java
Normal file
11
briar-api/src/org/briarproject/api/forum/ForumFactory.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.api.forum;
|
||||
|
||||
public interface ForumFactory {
|
||||
|
||||
/** Creates a forum with the given name. */
|
||||
Forum createForum(String name);
|
||||
|
||||
/** Creates a forum with the given name and salt. */
|
||||
Forum createForum(String name, byte[] salt);
|
||||
|
||||
}
|
||||
@@ -13,14 +13,8 @@ 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;
|
||||
Forum addForum(String name) throws DbException;
|
||||
|
||||
/** Unsubscribes from a forum. */
|
||||
void removeForum(Forum f) throws DbException;
|
||||
|
||||
@@ -24,7 +24,8 @@ public interface ForumSharingManager {
|
||||
/**
|
||||
* Responds to a pending forum invitation
|
||||
*/
|
||||
void respondToInvitation(Forum f, boolean accept) throws DbException;
|
||||
void respondToInvitation(Forum f, Contact c, boolean accept)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Returns all forum sharing messages sent by the Contact
|
||||
@@ -40,7 +41,7 @@ public interface ForumSharingManager {
|
||||
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;
|
||||
Collection<Contact> 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;
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package org.briarproject.api.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfEntry;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
|
||||
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.TYPE;
|
||||
|
||||
public interface ForumSharingMessage {
|
||||
|
||||
abstract class BaseMessage {
|
||||
private final GroupId groupId;
|
||||
private final SessionId sessionId;
|
||||
|
||||
public BaseMessage(GroupId groupId, SessionId sessionId) {
|
||||
|
||||
this.groupId = groupId;
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public BdfList toBdfList() {
|
||||
return BdfList.of(getType(), getSessionId());
|
||||
}
|
||||
|
||||
public abstract BdfDictionary toBdfDictionary();
|
||||
|
||||
protected BdfDictionary toBdfDictionaryHelper() {
|
||||
return BdfDictionary.of(
|
||||
new BdfEntry(TYPE, getType()),
|
||||
new BdfEntry(GROUP_ID, groupId),
|
||||
new BdfEntry(SESSION_ID, sessionId)
|
||||
);
|
||||
}
|
||||
|
||||
public static BaseMessage from(GroupId groupId, BdfDictionary d)
|
||||
throws FormatException {
|
||||
|
||||
long type = d.getLong(TYPE);
|
||||
|
||||
if (type == SHARE_MSG_TYPE_INVITATION)
|
||||
return Invitation.from(groupId, d);
|
||||
else
|
||||
return SimpleMessage.from(type, groupId, d);
|
||||
}
|
||||
|
||||
public abstract long getType();
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public SessionId getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
class Invitation extends BaseMessage {
|
||||
|
||||
private final String forumName;
|
||||
private final byte[] forumSalt;
|
||||
private final String message;
|
||||
|
||||
public Invitation(GroupId groupId, SessionId sessionId,
|
||||
String forumName, byte[] forumSalt, String message) {
|
||||
|
||||
super(groupId, sessionId);
|
||||
|
||||
this.forumName = forumName;
|
||||
this.forumSalt = forumSalt;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getType() {
|
||||
return SHARE_MSG_TYPE_INVITATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfList toBdfList() {
|
||||
BdfList list = super.toBdfList();
|
||||
list.add(forumName);
|
||||
list.add(forumSalt);
|
||||
if (message != null) list.add(message);
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = toBdfDictionaryHelper();
|
||||
d.put(FORUM_NAME, forumName);
|
||||
d.put(FORUM_SALT, forumSalt);
|
||||
if (message != null) d.put(INVITATION_MSG, message);
|
||||
return d;
|
||||
}
|
||||
|
||||
public static Invitation from(GroupId groupId, BdfDictionary d)
|
||||
throws FormatException {
|
||||
|
||||
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
|
||||
String forumName = d.getString(FORUM_NAME);
|
||||
byte[] forumSalt = d.getRaw(FORUM_SALT);
|
||||
String message = d.getOptionalString(INVITATION_MSG);
|
||||
|
||||
return new Invitation(groupId, sessionId, forumName, forumSalt,
|
||||
message);
|
||||
}
|
||||
|
||||
public String getForumName() {
|
||||
return forumName;
|
||||
}
|
||||
|
||||
public byte[] getForumSalt() {
|
||||
return forumSalt;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleMessage extends BaseMessage {
|
||||
|
||||
private final long type;
|
||||
|
||||
public SimpleMessage(long type, GroupId groupId, SessionId sessionId) {
|
||||
super(groupId, sessionId);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
return toBdfDictionaryHelper();
|
||||
}
|
||||
|
||||
public static SimpleMessage from(long type, GroupId groupId,
|
||||
BdfDictionary d) throws FormatException {
|
||||
|
||||
if (type != SHARE_MSG_TYPE_ACCEPT &&
|
||||
type != SHARE_MSG_TYPE_DECLINE &&
|
||||
type != SHARE_MSG_TYPE_LEAVE &&
|
||||
type != SHARE_MSG_TYPE_ABORT) throw new FormatException();
|
||||
|
||||
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
|
||||
return new SimpleMessage(type, groupId, sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.briarproject.api.sync;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public abstract class BaseMessageContext {
|
||||
|
||||
private final Collection<MessageId> dependencies;
|
||||
|
||||
public BaseMessageContext(Collection<MessageId> dependencies) {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
public Collection<MessageId> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.briarproject.api.sync;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** An exception that indicates an invalid message. */
|
||||
public class InvalidMessageException extends IOException {
|
||||
|
||||
public InvalidMessageException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public InvalidMessageException(String str) {
|
||||
super(str);
|
||||
}
|
||||
|
||||
public InvalidMessageException(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
}
|
||||
26
briar-api/src/org/briarproject/api/sync/MessageContext.java
Normal file
26
briar-api/src/org/briarproject/api/sync/MessageContext.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package org.briarproject.api.sync;
|
||||
|
||||
import org.briarproject.api.db.Metadata;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class MessageContext extends BaseMessageContext {
|
||||
|
||||
private final Metadata metadata;
|
||||
|
||||
public MessageContext(Metadata metadata,
|
||||
Collection<MessageId> dependencies) {
|
||||
|
||||
super(dependencies);
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public MessageContext(Metadata metadata) {
|
||||
this(metadata, null);
|
||||
}
|
||||
|
||||
public Metadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,10 +44,11 @@ public interface ValidationManager {
|
||||
interface MessageValidator {
|
||||
|
||||
/**
|
||||
* Validates the given message and returns its metadata if the message
|
||||
* is valid, or null if the message is invalid.
|
||||
* Validates the given message and returns its metadata and
|
||||
* dependencies.
|
||||
*/
|
||||
Metadata validateMessage(Message m, Group g);
|
||||
MessageContext validateMessage(Message m, Group g)
|
||||
throws InvalidMessageException;
|
||||
}
|
||||
|
||||
interface IncomingMessageHook {
|
||||
|
||||
@@ -4,10 +4,6 @@ targetCompatibility = 1.6
|
||||
|
||||
apply plugin: 'witness'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':briar-api')
|
||||
compile fileTree(dir: 'libs', include: '*.jar')
|
||||
|
||||
@@ -4,13 +4,16 @@ import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.MessageQueueManager.QueueMessageValidator;
|
||||
import org.briarproject.api.clients.QueueMessage;
|
||||
import org.briarproject.api.clients.BdfMessageContext;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.db.Metadata;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.InvalidMessageException;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.ValidationManager.MessageValidator;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
@@ -37,43 +40,41 @@ public abstract class BdfMessageValidator implements MessageValidator,
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
protected abstract BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException;
|
||||
protected abstract BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws InvalidMessageException, FormatException;
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
public MessageContext validateMessage(Message m, Group g)
|
||||
throws InvalidMessageException {
|
||||
return validateMessage(m, g, MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(QueueMessage q, Group g) {
|
||||
public MessageContext validateMessage(QueueMessage q, Group g)
|
||||
throws InvalidMessageException {
|
||||
return validateMessage(q, g, QUEUE_MESSAGE_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
private Metadata validateMessage(Message m, Group g, int headerLength) {
|
||||
private MessageContext validateMessage(Message m, Group g, int headerLength)
|
||||
throws InvalidMessageException {
|
||||
// Reject the message if it's too far in the future
|
||||
long now = clock.currentTimeMillis();
|
||||
if (m.getTimestamp() - now > MAX_CLOCK_DIFFERENCE) {
|
||||
LOG.info("Timestamp is too far in the future");
|
||||
return null;
|
||||
throw new InvalidMessageException(
|
||||
"Timestamp is too far in the future");
|
||||
}
|
||||
byte[] raw = m.getRaw();
|
||||
if (raw.length <= headerLength) {
|
||||
LOG.info("Message is too short");
|
||||
return null;
|
||||
throw new InvalidMessageException("Message is too short");
|
||||
}
|
||||
try {
|
||||
BdfList body = clientHelper.toList(raw, headerLength,
|
||||
raw.length - headerLength);
|
||||
BdfDictionary meta = validateMessage(m, g, body);
|
||||
if (meta == null) {
|
||||
LOG.info("Invalid message");
|
||||
return null;
|
||||
}
|
||||
return metadataEncoder.encode(meta);
|
||||
BdfMessageContext result = validateMessage(m, g, body);
|
||||
Metadata meta = metadataEncoder.encode(result.getDictionary());
|
||||
return new MessageContext(meta, result.getDependencies());
|
||||
} catch (FormatException e) {
|
||||
LOG.info("Invalid message");
|
||||
return null;
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,12 @@ import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.InvalidMessageException;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -169,7 +171,8 @@ class MessageQueueManagerImpl implements MessageQueueManager {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metadata validateMessage(Message m, Group g) {
|
||||
public MessageContext validateMessage(Message m, Group g)
|
||||
throws InvalidMessageException {
|
||||
byte[] raw = m.getRaw();
|
||||
if (raw.length < QUEUE_MESSAGE_HEADER_LENGTH) return null;
|
||||
long queuePosition = ByteUtils.readUint64(raw,
|
||||
|
||||
58
briar-core/src/org/briarproject/forum/ForumFactoryImpl.java
Normal file
58
briar-core/src/org/briarproject/forum/ForumFactoryImpl.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.forum.Forum;
|
||||
import org.briarproject.api.forum.ForumFactory;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupFactory;
|
||||
import org.briarproject.util.StringUtils;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
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;
|
||||
|
||||
class ForumFactoryImpl implements ForumFactory {
|
||||
|
||||
private final GroupFactory groupFactory;
|
||||
private final ClientHelper clientHelper;
|
||||
private final SecureRandom random;
|
||||
|
||||
@Inject
|
||||
ForumFactoryImpl(GroupFactory groupFactory, ClientHelper clientHelper,
|
||||
SecureRandom random) {
|
||||
|
||||
this.groupFactory = groupFactory;
|
||||
this.clientHelper = clientHelper;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@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(ForumManagerImpl.CLIENT_ID, descriptor);
|
||||
return new Forum(g, name, salt);
|
||||
} catch (FormatException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,6 +9,7 @@ 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.ForumFactory;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPost;
|
||||
import org.briarproject.api.forum.ForumPostHeader;
|
||||
@@ -17,12 +18,10 @@ 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;
|
||||
@@ -35,8 +34,6 @@ 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;
|
||||
@@ -49,18 +46,16 @@ class ForumManagerImpl implements ForumManager {
|
||||
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final GroupFactory groupFactory;
|
||||
private final SecureRandom random;
|
||||
private final ForumFactory forumFactory;
|
||||
private final List<RemoveForumHook> removeHooks;
|
||||
|
||||
@Inject
|
||||
ForumManagerImpl(DatabaseComponent db, ClientHelper clientHelper,
|
||||
GroupFactory groupFactory, SecureRandom random) {
|
||||
ForumFactory forumFactory) {
|
||||
|
||||
this.db = db;
|
||||
this.clientHelper = clientHelper;
|
||||
this.groupFactory = groupFactory;
|
||||
this.random = random;
|
||||
this.forumFactory = forumFactory;
|
||||
removeHooks = new CopyOnWriteArrayList<RemoveForumHook>();
|
||||
}
|
||||
|
||||
@@ -70,30 +65,9 @@ class ForumManagerImpl implements ForumManager {
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
public Forum addForum(String name) throws DbException {
|
||||
Forum f = forumFactory.createForum(name);
|
||||
|
||||
@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());
|
||||
@@ -101,6 +75,7 @@ class ForumManagerImpl implements ForumManager {
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ 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.ForumFactory;
|
||||
import org.briarproject.api.forum.ForumManager;
|
||||
import org.briarproject.api.forum.ForumPostFactory;
|
||||
import org.briarproject.api.forum.ForumSharingManager;
|
||||
@@ -38,9 +39,8 @@ public class ForumModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
ForumManager provideForumManager(DatabaseComponent db,
|
||||
ClientHelper clientHelper,
|
||||
GroupFactory groupFactory, SecureRandom random) {
|
||||
return new ForumManagerImpl(db, clientHelper, groupFactory, random);
|
||||
ClientHelper clientHelper, ForumFactory forumFactory) {
|
||||
return new ForumManagerImpl(db, clientHelper, forumFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -49,6 +49,12 @@ public class ForumModule {
|
||||
return new ForumPostFactoryImpl(crypto, clientHelper);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ForumFactory provideForumFactory(GroupFactory groupFactory,
|
||||
ClientHelper clientHelper, SecureRandom random) {
|
||||
return new ForumFactoryImpl(groupFactory, clientHelper, random);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ForumPostValidator provideForumPostValidator(
|
||||
@@ -82,7 +88,7 @@ public class ForumModule {
|
||||
LifecycleManager lifecycleManager,
|
||||
ContactManager contactManager,
|
||||
MessageQueueManager messageQueueManager,
|
||||
ForumManager forumManager,
|
||||
ForumManager forumManager, ForumFactory forumFactory,
|
||||
ForumSharingManagerImpl forumSharingManager) {
|
||||
|
||||
lifecycleManager.registerClient(forumSharingManager);
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.forum;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.BdfMessageContext;
|
||||
import org.briarproject.api.crypto.CryptoComponent;
|
||||
import org.briarproject.api.crypto.KeyParser;
|
||||
import org.briarproject.api.crypto.PublicKey;
|
||||
@@ -13,6 +14,7 @@ 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.InvalidMessageException;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.system.Clock;
|
||||
import org.briarproject.clients.BdfMessageValidator;
|
||||
@@ -39,8 +41,8 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws InvalidMessageException, FormatException {
|
||||
// Parent ID, author, content type, forum post body, signature
|
||||
checkSize(body, 5);
|
||||
// Parent ID is optional
|
||||
@@ -69,12 +71,10 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
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;
|
||||
throw new InvalidMessageException("Author without signature");
|
||||
}
|
||||
if (author == null && sig != null) {
|
||||
LOG.info("Signature without author");
|
||||
return null;
|
||||
throw new InvalidMessageException("Signature without author");
|
||||
}
|
||||
// Verify the signature, if any
|
||||
if (author != null) {
|
||||
@@ -90,12 +90,10 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
signature.initVerify(key);
|
||||
signature.update(clientHelper.toByteArray(signed));
|
||||
if (!signature.verify(sig)) {
|
||||
LOG.info("Invalid signature");
|
||||
return null;
|
||||
throw new InvalidMessageException("Invalid signature");
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
LOG.info("Invalid public key");
|
||||
return null;
|
||||
throw new InvalidMessageException("Invalid public key");
|
||||
}
|
||||
}
|
||||
// Return the metadata
|
||||
@@ -111,6 +109,6 @@ class ForumPostValidator extends BdfMessageValidator {
|
||||
}
|
||||
meta.put("contentType", contentType);
|
||||
meta.put("read", false);
|
||||
return meta;
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,10 @@ 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.ForumFactory;
|
||||
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;
|
||||
@@ -59,11 +56,7 @@ 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;
|
||||
@@ -76,10 +69,8 @@ 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_FORUM_TO_LIST_TO_BE_SHARED_BY_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;
|
||||
@@ -90,9 +81,10 @@ 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;
|
||||
import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
|
||||
import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
|
||||
import static org.briarproject.forum.ForumSharingSessionState.fromBdfDictionary;
|
||||
import static org.briarproject.forum.SharerSessionState.Action;
|
||||
|
||||
class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
implements ForumSharingManager, Client, RemoveForumHook,
|
||||
@@ -111,6 +103,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final SecureRandom random;
|
||||
private final PrivateGroupFactory privateGroupFactory;
|
||||
private final ForumFactory forumFactory;
|
||||
private final Clock clock;
|
||||
private final Group localGroup;
|
||||
|
||||
@@ -119,7 +112,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
MessageQueueManager messageQueueManager, ClientHelper clientHelper,
|
||||
MetadataParser metadataParser, MetadataEncoder metadataEncoder,
|
||||
SecureRandom random, PrivateGroupFactory privateGroupFactory,
|
||||
Clock clock) {
|
||||
ForumFactory forumFactory, Clock clock) {
|
||||
|
||||
super(clientHelper, metadataParser);
|
||||
this.db = db;
|
||||
@@ -128,6 +121,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
this.metadataEncoder = metadataEncoder;
|
||||
this.random = random;
|
||||
this.privateGroupFactory = privateGroupFactory;
|
||||
this.forumFactory = forumFactory;
|
||||
this.clock = clock;
|
||||
localGroup = privateGroupFactory.createLocalGroup(getClientId());
|
||||
}
|
||||
@@ -163,16 +157,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
// query for this contact c
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(CONTACT_ID, c.getId().getInt())
|
||||
);
|
||||
|
||||
// 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());
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(),
|
||||
query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
BdfDictionary d = entry.getValue();
|
||||
if (id.equals(d.getLong(CONTACT_ID))) {
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
@@ -184,11 +180,12 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
|
||||
@Override
|
||||
protected void incomingMessage(Transaction txn, Message m, BdfList body,
|
||||
BdfDictionary msg) throws DbException, FormatException {
|
||||
BdfDictionary d) throws DbException, FormatException {
|
||||
|
||||
SessionId sessionId = new SessionId(msg.getRaw(SESSION_ID));
|
||||
long type = msg.getLong(TYPE);
|
||||
if (type == SHARE_MSG_TYPE_INVITATION) {
|
||||
BaseMessage msg = BaseMessage.from(m.getGroupId(), d);
|
||||
SessionId sessionId = msg.getSessionId();
|
||||
|
||||
if (msg.getType() == SHARE_MSG_TYPE_INVITATION) {
|
||||
// we are an invitee who just received a new invitation
|
||||
boolean stateExists = true;
|
||||
try {
|
||||
@@ -203,43 +200,46 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
if (stateExists) throw new FormatException();
|
||||
|
||||
// check if forum can be shared
|
||||
Forum f = forumManager.createForum(msg.getString(FORUM_NAME),
|
||||
msg.getRaw(FORUM_SALT));
|
||||
Invitation invitation = (Invitation) msg;
|
||||
Forum f = forumFactory.createForum(invitation.getForumName(),
|
||||
invitation.getForumSalt());
|
||||
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(),
|
||||
InviteeSessionState state =
|
||||
initializeInviteeState(txn, contactId, invitation);
|
||||
InviteeEngine engine = new InviteeEngine(forumFactory);
|
||||
processInviteeStateUpdate(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) {
|
||||
} else if (msg.getType() == SHARE_MSG_TYPE_ACCEPT ||
|
||||
msg.getType() == SHARE_MSG_TYPE_DECLINE) {
|
||||
// we are a sharer who just received a response
|
||||
BdfDictionary state = getSessionState(txn, sessionId, true);
|
||||
SharerSessionState state = getSessionStateForSharer(txn, sessionId);
|
||||
SharerEngine engine = new SharerEngine();
|
||||
processStateUpdate(txn, m.getId(),
|
||||
processSharerStateUpdate(txn, m.getId(),
|
||||
engine.onMessageReceived(state, msg));
|
||||
} else if (type == SHARE_MSG_TYPE_LEAVE ||
|
||||
type == SHARE_MSG_TYPE_ABORT) {
|
||||
} else if (msg.getType() == SHARE_MSG_TYPE_LEAVE ||
|
||||
msg.getType() == 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)) {
|
||||
ForumSharingSessionState s = getSessionState(txn, sessionId, true);
|
||||
if (s instanceof SharerSessionState) {
|
||||
// we are a sharer and the invitee wants to leave or abort
|
||||
SharerSessionState state = (SharerSessionState) s;
|
||||
SharerEngine engine = new SharerEngine();
|
||||
processStateUpdate(txn, m.getId(),
|
||||
processSharerStateUpdate(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(),
|
||||
InviteeSessionState state = (InviteeSessionState) s;
|
||||
InviteeEngine engine = new InviteeEngine(forumFactory);
|
||||
processInviteeStateUpdate(txn, m.getId(),
|
||||
engine.onMessageReceived(state, msg));
|
||||
}
|
||||
} else {
|
||||
@@ -261,19 +261,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
try {
|
||||
// initialize local state for sharer
|
||||
Forum f = forumManager.getForum(txn, groupId);
|
||||
BdfDictionary localState = initializeSharerState(txn, f, contactId);
|
||||
SharerSessionState localState =
|
||||
initializeSharerState(txn, f, contactId);
|
||||
|
||||
// define action
|
||||
BdfDictionary localAction = new BdfDictionary();
|
||||
localAction.put(TYPE, SHARE_MSG_TYPE_INVITATION);
|
||||
// add invitation message to local state to be available for engine
|
||||
if (!StringUtils.isNullOrEmpty(msg)) {
|
||||
localAction.put(INVITATION_MSG, msg);
|
||||
localState.setMessage(msg);
|
||||
}
|
||||
|
||||
// start engine and process its state update
|
||||
SharerEngine engine = new SharerEngine();
|
||||
processStateUpdate(txn, null,
|
||||
engine.onLocalAction(localState, localAction));
|
||||
processSharerStateUpdate(txn, null,
|
||||
engine.onLocalAction(localState, Action.LOCAL_INVITATION));
|
||||
|
||||
txn.setComplete();
|
||||
} catch (FormatException e) {
|
||||
@@ -284,25 +283,26 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
|
||||
@Override
|
||||
public void respondToInvitation(Forum f, boolean accept)
|
||||
public void respondToInvitation(Forum f, Contact c, boolean accept)
|
||||
throws DbException {
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
// find session state based on forum
|
||||
BdfDictionary localState = getSessionStateForResponse(txn, f);
|
||||
InviteeSessionState localState =
|
||||
getSessionStateForResponse(txn, f, c);
|
||||
|
||||
// define action
|
||||
BdfDictionary localAction = new BdfDictionary();
|
||||
InviteeSessionState.Action localAction;
|
||||
if (accept) {
|
||||
localAction.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
|
||||
localAction = InviteeSessionState.Action.LOCAL_ACCEPT;
|
||||
} else {
|
||||
localAction.put(TYPE, SHARE_MSG_TYPE_DECLINE);
|
||||
localAction = InviteeSessionState.Action.LOCAL_DECLINE;
|
||||
}
|
||||
|
||||
// start engine and process its state update
|
||||
InviteeEngine engine = new InviteeEngine();
|
||||
processStateUpdate(txn, null,
|
||||
InviteeEngine engine = new InviteeEngine(forumFactory);
|
||||
processInviteeStateUpdate(txn, null,
|
||||
engine.onLocalAction(localState, localAction));
|
||||
|
||||
txn.setComplete();
|
||||
@@ -317,6 +317,11 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
public Collection<ForumInvitationMessage> getForumInvitationMessages(
|
||||
ContactId contactId) throws DbException {
|
||||
|
||||
// query for all invitations
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, SHARE_MSG_TYPE_INVITATION)
|
||||
);
|
||||
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
Contact contact = db.getContact(txn, contactId);
|
||||
@@ -325,36 +330,32 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
Collection<ForumInvitationMessage> list =
|
||||
new ArrayList<ForumInvitationMessage>();
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, group.getId());
|
||||
.getMessageMetadataAsDictionary(txn, group.getId(), query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) {
|
||||
BdfDictionary msg = m.getValue();
|
||||
BdfDictionary d = m.getValue();
|
||||
try {
|
||||
if (msg.getLong(TYPE) != SHARE_MSG_TYPE_INVITATION)
|
||||
continue;
|
||||
|
||||
Invitation msg = Invitation.from(group.getId(), d);
|
||||
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);
|
||||
long time = d.getLong(TIME);
|
||||
boolean local = d.getBoolean(LOCAL);
|
||||
boolean read = d.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;
|
||||
ForumSharingSessionState s =
|
||||
getSessionState(txn, msg.getSessionId(), true);
|
||||
if (!(s instanceof InviteeSessionState))
|
||||
continue;
|
||||
available = ((InviteeSessionState) s).getState() ==
|
||||
InviteeSessionState.State.AWAIT_LOCAL_RESPONSE;
|
||||
}
|
||||
ForumInvitationMessage im =
|
||||
new ForumInvitationMessage(m.getKey(), sessionId,
|
||||
contactId, name, message, available, time,
|
||||
local, status.isSent(), status.isSeen(),
|
||||
read);
|
||||
new ForumInvitationMessage(m.getKey(),
|
||||
msg.getSessionId(), contactId,
|
||||
msg.getForumName(), msg.getMessage(),
|
||||
available, time, local, status.isSent(),
|
||||
status.isSeen(), read);
|
||||
list.add(im);
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
@@ -441,15 +442,15 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ContactId> getSharedWith(GroupId g) throws DbException {
|
||||
public Collection<Contact> getSharedWith(GroupId g) throws DbException {
|
||||
try {
|
||||
List<ContactId> shared = new ArrayList<ContactId>();
|
||||
List<Contact> shared = 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_BY_US))
|
||||
shared.add(c.getId());
|
||||
shared.add(c);
|
||||
}
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
@@ -487,7 +488,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
}
|
||||
|
||||
private BdfDictionary initializeSharerState(Transaction txn, Forum f,
|
||||
private SharerSessionState initializeSharerState(Transaction txn, Forum f,
|
||||
ContactId contactId) throws FormatException, DbException {
|
||||
|
||||
Contact c = db.getContact(txn, contactId);
|
||||
@@ -499,34 +500,28 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
random.nextBytes(salt.getBytes());
|
||||
Message m = clientHelper.createMessage(localGroup.getId(), now,
|
||||
BdfList.of(salt));
|
||||
MessageId sessionId = m.getId();
|
||||
SessionId sessionId = new SessionId(m.getId().getBytes());
|
||||
|
||||
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());
|
||||
SharerSessionState s = new SharerSessionState(sessionId, sessionId,
|
||||
group.getId(), SharerSessionState.State.PREPARE_INVITATION,
|
||||
contactId, f.getId(), f.getName(), f.getSalt());
|
||||
|
||||
// save local state to database
|
||||
BdfDictionary d = s.toBdfDictionary();
|
||||
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
|
||||
|
||||
return d;
|
||||
return s;
|
||||
}
|
||||
|
||||
private BdfDictionary initializeInviteeState(Transaction txn,
|
||||
ContactId contactId, BdfDictionary msg)
|
||||
private InviteeSessionState initializeInviteeState(Transaction txn,
|
||||
ContactId contactId, Invitation 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);
|
||||
String name = msg.getForumName();
|
||||
byte[] salt = msg.getForumSalt();
|
||||
Forum f = forumFactory.createForum(name, salt);
|
||||
|
||||
// create local message to keep engine state
|
||||
long now = clock.currentTimeMillis();
|
||||
@@ -535,105 +530,129 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
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);
|
||||
InviteeSessionState s = new InviteeSessionState(msg.getSessionId(),
|
||||
m.getId(), group.getId(),
|
||||
InviteeSessionState.State.AWAIT_INVITATION, contactId,
|
||||
f.getId(), f.getName(), f.getSalt());
|
||||
|
||||
// save local state to database
|
||||
BdfDictionary d = s.toBdfDictionary();
|
||||
clientHelper.addLocalMessage(txn, m, getClientId(), d, false);
|
||||
|
||||
return d;
|
||||
return s;
|
||||
}
|
||||
|
||||
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)
|
||||
private ForumSharingSessionState getSessionState(Transaction txn,
|
||||
SessionId sessionId, boolean warn)
|
||||
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);
|
||||
try {
|
||||
return getSessionStateForSharer(txn, sessionId);
|
||||
} catch (NoSuchMessageException e) {
|
||||
// State not found directly, so query for state for invitee
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(SESSION_ID, sessionId)
|
||||
);
|
||||
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(),
|
||||
query);
|
||||
|
||||
if (map.size() > 1 && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"More than one session state found for message with session ID " +
|
||||
Arrays.hashCode(sessionId.getBytes()));
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
if (warn && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"No session state found for message with session ID " +
|
||||
Arrays.hashCode(sessionId.getBytes()));
|
||||
}
|
||||
throw new FormatException();
|
||||
}
|
||||
return fromBdfDictionary(map.values().iterator().next());
|
||||
}
|
||||
throw new DbException();
|
||||
}
|
||||
|
||||
private BdfDictionary getSessionStateForLeaving(Transaction txn, Forum f,
|
||||
ContactId c) throws DbException, FormatException {
|
||||
private SharerSessionState getSessionStateForSharer(Transaction txn,
|
||||
SessionId sessionId)
|
||||
throws DbException, FormatException {
|
||||
|
||||
// we should be able to get the sharer state directly from sessionId
|
||||
BdfDictionary d =
|
||||
clientHelper.getMessageMetadataAsDictionary(txn, sessionId);
|
||||
|
||||
if (!d.getBoolean(IS_SHARER)) throw new FormatException();
|
||||
|
||||
return (SharerSessionState) fromBdfDictionary(d);
|
||||
}
|
||||
|
||||
private InviteeSessionState getSessionStateForResponse(Transaction txn,
|
||||
Forum f, Contact c) throws DbException, FormatException {
|
||||
|
||||
// query for invitee states for that forum in state await response
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(IS_SHARER, false),
|
||||
new BdfEntry(CONTACT_ID, c.getId().getInt()),
|
||||
new BdfEntry(FORUM_ID, f.getId()),
|
||||
new BdfEntry(STATE,
|
||||
InviteeSessionState.State.AWAIT_LOCAL_RESPONSE
|
||||
.getValue())
|
||||
);
|
||||
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId());
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
|
||||
|
||||
if (map.size() > 1 && LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"More than one session state found for forum with ID " +
|
||||
Arrays.hashCode(f.getId().getBytes()) +
|
||||
" in state AWAIT_LOCAL_RESPONSE for contact " +
|
||||
c.getAuthor().getName());
|
||||
}
|
||||
if (map.isEmpty()) {
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning(
|
||||
"No session state found for forum with ID " +
|
||||
Arrays.hashCode(f.getId().getBytes()) +
|
||||
" in state AWAIT_LOCAL_RESPONSE");
|
||||
}
|
||||
throw new DbException();
|
||||
}
|
||||
return (InviteeSessionState) fromBdfDictionary(
|
||||
map.values().iterator().next());
|
||||
}
|
||||
|
||||
private ForumSharingSessionState getSessionStateForLeaving(Transaction txn,
|
||||
Forum f, ContactId c) throws DbException, FormatException {
|
||||
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(CONTACT_ID, c.getInt()),
|
||||
new BdfEntry(FORUM_ID, f.getId())
|
||||
);
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
|
||||
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;
|
||||
ForumSharingSessionState s = fromBdfDictionary(d);
|
||||
|
||||
// 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;
|
||||
if (s instanceof SharerSessionState) {
|
||||
SharerSessionState state = (SharerSessionState) s;
|
||||
SharerSessionState.State nextState =
|
||||
state.getState().next(Action.LOCAL_LEAVE);
|
||||
if (nextState != SharerSessionState.State.ERROR) {
|
||||
return state;
|
||||
}
|
||||
} 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;
|
||||
InviteeSessionState state = (InviteeSessionState) s;
|
||||
InviteeSessionState.State nextState = state.getState()
|
||||
.next(InviteeSessionState.Action.LOCAL_LEAVE);
|
||||
if (nextState != InviteeSessionState.State.ERROR) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
@@ -643,20 +662,20 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
|
||||
private void processStateUpdate(Transaction txn, MessageId messageId,
|
||||
StateUpdate<BdfDictionary, BdfDictionary> result)
|
||||
StateUpdate<ForumSharingSessionState, BaseMessage> 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);
|
||||
MessageId storageId = result.localState.getStorageId();
|
||||
clientHelper.mergeMessageMetadata(txn, storageId,
|
||||
result.localState.toBdfDictionary());
|
||||
|
||||
// send messages
|
||||
for (BdfDictionary d : result.toSend) {
|
||||
sendMessage(txn, d);
|
||||
for (BaseMessage msg : result.toSend) {
|
||||
sendMessage(txn, msg);
|
||||
}
|
||||
|
||||
// broadcast events
|
||||
@@ -674,25 +693,48 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
}
|
||||
|
||||
private void performTasks(Transaction txn, BdfDictionary localState)
|
||||
private void processSharerStateUpdate(Transaction txn, MessageId messageId,
|
||||
StateUpdate<SharerSessionState, BaseMessage> result)
|
||||
throws DbException, FormatException {
|
||||
|
||||
StateUpdate<ForumSharingSessionState, BaseMessage> r =
|
||||
new StateUpdate<ForumSharingSessionState, BaseMessage>(
|
||||
result.deleteMessage, result.deleteState,
|
||||
result.localState, result.toSend, result.toBroadcast);
|
||||
|
||||
processStateUpdate(txn, messageId, r);
|
||||
}
|
||||
|
||||
private void processInviteeStateUpdate(Transaction txn, MessageId messageId,
|
||||
StateUpdate<InviteeSessionState, BaseMessage> result)
|
||||
throws DbException, FormatException {
|
||||
|
||||
StateUpdate<ForumSharingSessionState, BaseMessage> r =
|
||||
new StateUpdate<ForumSharingSessionState, BaseMessage>(
|
||||
result.deleteMessage, result.deleteState,
|
||||
result.localState, result.toSend, result.toBroadcast);
|
||||
|
||||
processStateUpdate(txn, messageId, r);
|
||||
}
|
||||
|
||||
private void performTasks(Transaction txn, ForumSharingSessionState localState)
|
||||
throws FormatException, DbException {
|
||||
|
||||
if (!localState.containsKey(TASK)) return;
|
||||
if (localState.getTask() == -1) return;
|
||||
|
||||
// remember task and remove it from localState
|
||||
long task = localState.getLong(TASK);
|
||||
localState.put(TASK, BdfDictionary.NULL_VALUE);
|
||||
long task = localState.getTask();
|
||||
localState.setTask(-1);
|
||||
|
||||
// get group ID for later
|
||||
GroupId groupId = new GroupId(localState.getRaw(GROUP_ID));
|
||||
GroupId groupId = localState.getGroupId();
|
||||
// get contact ID for later
|
||||
ContactId contactId =
|
||||
new ContactId(localState.getLong(CONTACT_ID).intValue());
|
||||
ContactId contactId = localState.getContactId();
|
||||
|
||||
// get forum for later
|
||||
String name = localState.getString(FORUM_NAME);
|
||||
byte[] salt = localState.getRaw(FORUM_SALT);
|
||||
Forum f = forumManager.createForum(name, salt);
|
||||
String name = localState.getForumName();
|
||||
byte[] salt = localState.getForumSalt();
|
||||
Forum f = forumFactory.createForum(name, salt);
|
||||
|
||||
// perform tasks
|
||||
if (task == TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US) {
|
||||
@@ -725,50 +767,23 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
}
|
||||
}
|
||||
|
||||
private void sendMessage(Transaction txn, BdfDictionary m)
|
||||
private void sendMessage(Transaction txn, BaseMessage 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);
|
||||
byte[] body = clientHelper.toByteArray(m.toBdfList());
|
||||
Group group = db.getGroup(txn, m.getGroupId());
|
||||
long timestamp = clock.currentTimeMillis();
|
||||
|
||||
// add message itself as metadata
|
||||
m.put(LOCAL, true);
|
||||
m.put(TIME, timestamp);
|
||||
Metadata meta = metadataEncoder.encode(m);
|
||||
BdfDictionary d = m.toBdfDictionary();
|
||||
d.put(LOCAL, true);
|
||||
d.put(TIME, timestamp);
|
||||
Metadata meta = metadataEncoder.encode(d);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -783,17 +798,18 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
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)) {
|
||||
ForumSharingSessionState state = getSessionStateForLeaving(txn, f, c);
|
||||
if (state instanceof SharerSessionState) {
|
||||
Action action = Action.LOCAL_LEAVE;
|
||||
SharerEngine engine = new SharerEngine();
|
||||
processStateUpdate(txn, null,
|
||||
engine.onLocalAction(state, action));
|
||||
processSharerStateUpdate(txn, null,
|
||||
engine.onLocalAction((SharerSessionState) state, action));
|
||||
} else {
|
||||
InviteeEngine engine = new InviteeEngine();
|
||||
processStateUpdate(txn, null,
|
||||
engine.onLocalAction(state, action));
|
||||
InviteeSessionState.Action action =
|
||||
InviteeSessionState.Action.LOCAL_LEAVE;
|
||||
InviteeEngine engine = new InviteeEngine(forumFactory);
|
||||
processInviteeStateUpdate(txn, null,
|
||||
engine.onLocalAction((InviteeSessionState) state, action));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,7 +875,7 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
|
||||
List<Forum> forums = new ArrayList<Forum>(list.size());
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
BdfList forum = list.getList(i);
|
||||
forums.add(forumManager
|
||||
forums.add(forumFactory
|
||||
.createForum(forum.getString(0), forum.getRaw(1)));
|
||||
}
|
||||
return forums;
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
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.GROUP_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
|
||||
import static org.briarproject.api.forum.ForumConstants.SESSION_ID;
|
||||
import static org.briarproject.api.forum.ForumConstants.STATE;
|
||||
import static org.briarproject.api.forum.ForumConstants.STORAGE_ID;
|
||||
|
||||
// This class is not thread-safe
|
||||
public abstract class ForumSharingSessionState {
|
||||
|
||||
private final SessionId sessionId;
|
||||
private final MessageId storageId;
|
||||
private final GroupId groupId;
|
||||
private final ContactId contactId;
|
||||
private final GroupId forumId;
|
||||
private final String forumName;
|
||||
private final byte[] forumSalt;
|
||||
private int task = -1; // TODO get rid of task, see #376
|
||||
|
||||
public ForumSharingSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, ContactId contactId, GroupId forumId,
|
||||
String forumName, byte[] forumSalt) {
|
||||
|
||||
this.sessionId = sessionId;
|
||||
this.storageId = storageId;
|
||||
this.groupId = groupId;
|
||||
this.contactId = contactId;
|
||||
this.forumId = forumId;
|
||||
this.forumName = forumName;
|
||||
this.forumSalt = forumSalt;
|
||||
}
|
||||
|
||||
public static ForumSharingSessionState fromBdfDictionary(BdfDictionary d)
|
||||
throws FormatException{
|
||||
|
||||
SessionId sessionId = new SessionId(d.getRaw(SESSION_ID));
|
||||
MessageId messageId = new MessageId(d.getRaw(STORAGE_ID));
|
||||
GroupId groupId = new GroupId(d.getRaw(GROUP_ID));
|
||||
ContactId contactId = new ContactId(d.getLong(CONTACT_ID).intValue());
|
||||
GroupId forumId = new GroupId(d.getRaw(FORUM_ID));
|
||||
String forumName = d.getString(FORUM_NAME);
|
||||
byte[] forumSalt = d.getRaw(FORUM_SALT);
|
||||
|
||||
int intState = d.getLong(STATE).intValue();
|
||||
if (d.getBoolean(IS_SHARER)) {
|
||||
SharerSessionState.State state =
|
||||
SharerSessionState.State.fromValue(intState);
|
||||
return new SharerSessionState(sessionId, messageId, groupId, state,
|
||||
contactId, forumId, forumName, forumSalt);
|
||||
} else {
|
||||
InviteeSessionState.State state =
|
||||
InviteeSessionState.State.fromValue(intState);
|
||||
return new InviteeSessionState(sessionId, messageId, groupId, state,
|
||||
contactId, forumId, forumName, forumSalt);
|
||||
}
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put(SESSION_ID, getSessionId());
|
||||
d.put(STORAGE_ID, getStorageId());
|
||||
d.put(GROUP_ID, getGroupId());
|
||||
d.put(CONTACT_ID, getContactId().getInt());
|
||||
d.put(FORUM_ID, getForumId());
|
||||
d.put(FORUM_NAME, getForumName());
|
||||
d.put(FORUM_SALT, getForumSalt());
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public SessionId getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public MessageId getStorageId() {
|
||||
return storageId;
|
||||
}
|
||||
|
||||
public GroupId getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public ContactId getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
public GroupId getForumId() {
|
||||
return forumId;
|
||||
}
|
||||
|
||||
public String getForumName() {
|
||||
return forumName;
|
||||
}
|
||||
|
||||
public byte[] getForumSalt() {
|
||||
return forumSalt;
|
||||
}
|
||||
|
||||
public void setTask(int task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public int getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ 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.clients.BdfMessageContext;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
@@ -36,7 +37,7 @@ class ForumSharingValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
@@ -73,9 +74,8 @@ class ForumSharingValidator extends BdfMessageValidator {
|
||||
// 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;
|
||||
return new BdfMessageContext(d);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,65 +3,59 @@ 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 org.briarproject.api.forum.ForumFactory;
|
||||
|
||||
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;
|
||||
import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
|
||||
import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ABORT;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
|
||||
import static org.briarproject.forum.InviteeSessionState.State;
|
||||
import static org.briarproject.forum.InviteeSessionState.State.ERROR;
|
||||
import static org.briarproject.forum.InviteeSessionState.State.FINISHED;
|
||||
import static org.briarproject.forum.InviteeSessionState.State.LEFT;
|
||||
|
||||
public class InviteeEngine
|
||||
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
|
||||
implements ProtocolEngine<Action, InviteeSessionState, BaseMessage> {
|
||||
|
||||
private final ForumFactory forumFactory;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SharerEngine.class.getName());
|
||||
|
||||
InviteeEngine(ForumFactory forumFactory) {
|
||||
this.forumFactory = forumFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
|
||||
BdfDictionary localState, BdfDictionary localAction) {
|
||||
public StateUpdate<InviteeSessionState, BaseMessage> onLocalAction(
|
||||
InviteeSessionState localState, Action action) {
|
||||
|
||||
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());
|
||||
State currentState = localState.getState();
|
||||
State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
if (action == LOCAL_ABORT && currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
@@ -74,37 +68,34 @@ public class InviteeEngine
|
||||
}
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
List<BdfDictionary> messages;
|
||||
List<BaseMessage> 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))
|
||||
);
|
||||
BaseMessage msg;
|
||||
if (action == LOCAL_ACCEPT) {
|
||||
localState.put(TASK, TASK_ADD_SHARED_FORUM);
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_ACCEPT);
|
||||
localState.setTask(TASK_ADD_SHARED_FORUM);
|
||||
msg = new SimpleMessage(SHARE_MSG_TYPE_ACCEPT,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
} else {
|
||||
localState.put(TASK,
|
||||
localState.setTask(
|
||||
TASK_REMOVE_FORUM_FROM_LIST_SHARED_WITH_US);
|
||||
msg.put(TYPE, SHARE_MSG_TYPE_DECLINE);
|
||||
msg = new SimpleMessage(SHARE_MSG_TYPE_DECLINE,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
}
|
||||
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));
|
||||
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown Local Action");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
|
||||
return new StateUpdate<InviteeSessionState, BaseMessage>(false,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
@@ -112,18 +103,16 @@ public class InviteeEngine
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
public StateUpdate<InviteeSessionState, BaseMessage> onMessageReceived(
|
||||
InviteeSessionState localState, BaseMessage 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());
|
||||
State currentState = localState.getState();
|
||||
Action action = Action.getRemote(msg.getType());
|
||||
State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
logMessageReceived(currentState, nextState, type, msg);
|
||||
logMessageReceived(currentState, nextState, msg.getType(), msg);
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (currentState != ERROR) {
|
||||
@@ -133,7 +122,7 @@ public class InviteeEngine
|
||||
}
|
||||
}
|
||||
|
||||
List<BdfDictionary> messages = Collections.emptyList();
|
||||
List<BaseMessage> messages = Collections.emptyList();
|
||||
List<Event> events = Collections.emptyList();
|
||||
boolean deleteMsg = false;
|
||||
|
||||
@@ -143,7 +132,7 @@ public class InviteeEngine
|
||||
}
|
||||
// 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);
|
||||
localState.setTask(TASK_UNSHARE_FORUM_SHARED_WITH_US);
|
||||
}
|
||||
else if (currentState == FINISHED) {
|
||||
// ignore and delete messages coming in while in that state
|
||||
@@ -152,74 +141,65 @@ public class InviteeEngine
|
||||
}
|
||||
// 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);
|
||||
localState.setTask(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());
|
||||
Forum forum = forumFactory
|
||||
.createForum(localState.getForumName(),
|
||||
localState.getForumSalt());
|
||||
localState.setTask(TASK_ADD_FORUM_TO_LIST_SHARED_WITH_US);
|
||||
ContactId contactId = localState.getContactId();
|
||||
Event event = new ForumInvitationReceivedEvent(forum, contactId);
|
||||
events = Collections.singletonList(event);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Bad state");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
|
||||
return new StateUpdate<InviteeSessionState, BaseMessage>(deleteMsg,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLocalAction(InviteeProtocolState state,
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
private void logLocalAction(State state,
|
||||
InviteeSessionState localState, BaseMessage msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String a = "response";
|
||||
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
|
||||
if (msg.getType() == 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);
|
||||
}
|
||||
LOG.info("Sending " + a + " in state " + state.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + localState.getState().name()
|
||||
);
|
||||
}
|
||||
|
||||
private void logMessageReceived(InviteeProtocolState currentState,
|
||||
InviteeProtocolState nextState, long type, BdfDictionary msg) {
|
||||
private void logMessageReceived(State currentState, State nextState,
|
||||
long type, BaseMessage 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";
|
||||
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);
|
||||
}
|
||||
LOG.info("Received " + t + " in state " + currentState.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
|
||||
BdfDictionary localState, BdfDictionary delivered) {
|
||||
public StateUpdate<InviteeSessionState, BaseMessage> onMessageDelivered(
|
||||
InviteeSessionState localState, BaseMessage delivered) {
|
||||
try {
|
||||
return noUpdate(localState, false);
|
||||
} catch (FormatException e) {
|
||||
@@ -228,38 +208,32 @@ public class InviteeEngine
|
||||
}
|
||||
}
|
||||
|
||||
private InviteeProtocolState getState(Long state) {
|
||||
return InviteeProtocolState.fromValue(state.intValue());
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
|
||||
InviteeProtocolState currentState, BdfDictionary localState)
|
||||
private StateUpdate<InviteeSessionState, BaseMessage> abortSession(
|
||||
State currentState, InviteeSessionState localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Aborting protocol session " +
|
||||
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
|
||||
localState.getSessionId().hashCode() +
|
||||
" 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);
|
||||
localState.setState(ERROR);
|
||||
BaseMessage msg =
|
||||
new SimpleMessage(SHARE_MSG_TYPE_ABORT, localState.getGroupId(),
|
||||
localState.getSessionId());
|
||||
List<BaseMessage> messages = Collections.singletonList(msg);
|
||||
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
|
||||
return new StateUpdate<InviteeSessionState, BaseMessage>(false, false,
|
||||
localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
|
||||
BdfDictionary localState, boolean delete) throws FormatException {
|
||||
private StateUpdate<InviteeSessionState, BaseMessage> noUpdate(
|
||||
InviteeSessionState localState, boolean delete) throws FormatException {
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
|
||||
localState, Collections.<BdfDictionary>emptyList(),
|
||||
return new StateUpdate<InviteeSessionState, BaseMessage>(delete, false,
|
||||
localState, Collections.<BaseMessage>emptyList(),
|
||||
Collections.<Event>emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
120
briar-core/src/org/briarproject/forum/InviteeSessionState.java
Normal file
120
briar-core/src/org/briarproject/forum/InviteeSessionState.java
Normal file
@@ -0,0 +1,120 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
|
||||
import static org.briarproject.api.forum.ForumConstants.SHARE_MSG_TYPE_ABORT;
|
||||
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.forum.InviteeSessionState.Action.LOCAL_ACCEPT;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_DECLINE;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.LOCAL_LEAVE;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_INVITATION;
|
||||
import static org.briarproject.forum.InviteeSessionState.Action.REMOTE_LEAVE;
|
||||
|
||||
// This class is not thread-safe
|
||||
public class InviteeSessionState extends ForumSharingSessionState {
|
||||
|
||||
private State state;
|
||||
|
||||
public InviteeSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, State state, ContactId contactId, GroupId forumId,
|
||||
String forumName, byte[] forumSalt) {
|
||||
|
||||
super(sessionId, storageId, groupId, contactId, forumId, forumName,
|
||||
forumSalt);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = super.toBdfDictionary();
|
||||
d.put(STATE, getState().getValue());
|
||||
d.put(IS_SHARER, false);
|
||||
return d;
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
ERROR(0),
|
||||
AWAIT_INVITATION(1) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == REMOTE_INVITATION) return AWAIT_LOCAL_RESPONSE;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_LOCAL_RESPONSE(2) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_ACCEPT || a == LOCAL_DECLINE) return FINISHED;
|
||||
if (a == REMOTE_LEAVE) return LEFT;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
FINISHED(3) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
|
||||
return FINISHED;
|
||||
}
|
||||
},
|
||||
LEFT(4) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE) return ERROR;
|
||||
return LEFT;
|
||||
}
|
||||
};
|
||||
|
||||
private final int value;
|
||||
|
||||
State(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static State fromValue(int value) {
|
||||
for (State s : values()) {
|
||||
if (s.value == value) return s;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public State next(Action a) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
LOCAL_ACCEPT,
|
||||
LOCAL_DECLINE,
|
||||
LOCAL_LEAVE,
|
||||
LOCAL_ABORT,
|
||||
REMOTE_INVITATION,
|
||||
REMOTE_LEAVE,
|
||||
REMOTE_ABORT;
|
||||
|
||||
public static Action 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,64 +3,52 @@ 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;
|
||||
import static org.briarproject.api.forum.ForumSharingMessage.BaseMessage;
|
||||
import static org.briarproject.api.forum.ForumSharingMessage.Invitation;
|
||||
import static org.briarproject.api.forum.ForumSharingMessage.SimpleMessage;
|
||||
import static org.briarproject.forum.SharerSessionState.Action;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_ABORT;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
|
||||
import static org.briarproject.forum.SharerSessionState.State;
|
||||
import static org.briarproject.forum.SharerSessionState.State.ERROR;
|
||||
import static org.briarproject.forum.SharerSessionState.State.FINISHED;
|
||||
import static org.briarproject.forum.SharerSessionState.State.LEFT;
|
||||
|
||||
public class SharerEngine
|
||||
implements ProtocolEngine<BdfDictionary, BdfDictionary, BdfDictionary> {
|
||||
implements ProtocolEngine<Action, SharerSessionState, BaseMessage> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SharerEngine.class.getName());
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onLocalAction(
|
||||
BdfDictionary localState, BdfDictionary localAction) {
|
||||
public StateUpdate<SharerSessionState, BaseMessage> onLocalAction(
|
||||
SharerSessionState localState, Action action) {
|
||||
|
||||
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());
|
||||
State currentState = localState.getState();
|
||||
State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
if (action == LOCAL_ABORT && currentState != ERROR) {
|
||||
return abortSession(currentState, localState);
|
||||
@@ -73,38 +61,29 @@ public class SharerEngine
|
||||
}
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
List<BdfDictionary> messages;
|
||||
List<BaseMessage> 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));
|
||||
}
|
||||
BaseMessage msg = new Invitation(localState.getGroupId(),
|
||||
localState.getSessionId(), localState.getForumName(),
|
||||
localState.getForumSalt(), localState.getMessage());
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
logLocalAction(currentState, nextState, msg);
|
||||
|
||||
// remember that we offered to share this forum
|
||||
localState.put(TASK, TASK_ADD_FORUM_TO_LIST_TO_BE_SHARED_BY_US);
|
||||
localState.setTask(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));
|
||||
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_LEAVE,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
messages = Collections.singletonList(msg);
|
||||
logLocalAction(currentState, localState, msg);
|
||||
logLocalAction(currentState, nextState, msg);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown Local Action");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false,
|
||||
return new StateUpdate<SharerSessionState, BaseMessage>(false,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
@@ -112,18 +91,16 @@ public class SharerEngine
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageReceived(
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
public StateUpdate<SharerSessionState, BaseMessage> onMessageReceived(
|
||||
SharerSessionState localState, BaseMessage 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());
|
||||
State currentState = localState.getState();
|
||||
Action action = Action.getRemote(msg.getType());
|
||||
State nextState = currentState.next(action);
|
||||
localState.setState(nextState);
|
||||
|
||||
logMessageReceived(currentState, nextState, type, msg);
|
||||
logMessageReceived(currentState, nextState, msg.getType(), msg);
|
||||
|
||||
if (nextState == ERROR) {
|
||||
if (currentState != ERROR) {
|
||||
@@ -132,7 +109,7 @@ public class SharerEngine
|
||||
return noUpdate(localState, true);
|
||||
}
|
||||
}
|
||||
List<BdfDictionary> messages = Collections.emptyList();
|
||||
List<BaseMessage> messages = Collections.emptyList();
|
||||
List<Event> events = Collections.emptyList();
|
||||
boolean deleteMsg = false;
|
||||
|
||||
@@ -141,7 +118,7 @@ public class SharerEngine
|
||||
deleteMsg = true;
|
||||
}
|
||||
else if (action == REMOTE_LEAVE) {
|
||||
localState.put(TASK, TASK_UNSHARE_FORUM_SHARED_BY_US);
|
||||
localState.setTask(TASK_UNSHARE_FORUM_SHARED_BY_US);
|
||||
}
|
||||
else if (currentState == FINISHED) {
|
||||
// ignore and delete messages coming in while in that state
|
||||
@@ -151,74 +128,65 @@ public class SharerEngine
|
||||
// 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);
|
||||
localState.setTask(TASK_SHARE_FORUM);
|
||||
} else {
|
||||
// this ensures that the forum can be shared again
|
||||
localState.put(TASK,
|
||||
localState.setTask(
|
||||
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());
|
||||
String name = localState.getForumName();
|
||||
ContactId c = localState.getContactId();
|
||||
Event event = new ForumInvitationResponseReceivedEvent(name, c);
|
||||
events = Collections.singletonList(event);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Bad state");
|
||||
}
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(deleteMsg,
|
||||
return new StateUpdate<SharerSessionState, BaseMessage>(deleteMsg,
|
||||
false, localState, messages, events);
|
||||
} catch (FormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLocalAction(SharerProtocolState state,
|
||||
BdfDictionary localState, BdfDictionary msg) {
|
||||
private void logLocalAction(State currentState, State nextState,
|
||||
BaseMessage msg) {
|
||||
|
||||
if (!LOG.isLoggable(INFO)) return;
|
||||
|
||||
String a = "invitation";
|
||||
if (msg.getLong(TYPE, -1L) == SHARE_MSG_TYPE_LEAVE) a = "leave";
|
||||
if (msg.getType() == 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);
|
||||
}
|
||||
LOG.info("Sending " + a + " in state " + currentState.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
}
|
||||
|
||||
private void logMessageReceived(SharerProtocolState currentState,
|
||||
SharerProtocolState nextState, long type, BdfDictionary msg) {
|
||||
private void logMessageReceived(State currentState, State nextState,
|
||||
long type, BaseMessage 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";
|
||||
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);
|
||||
}
|
||||
LOG.info("Received " + t + " in state " + currentState.name() +
|
||||
" with session ID " +
|
||||
msg.getSessionId().hashCode() + " in group " +
|
||||
msg.getGroupId().hashCode() + ". " +
|
||||
"Moving on to state " + nextState.name()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateUpdate<BdfDictionary, BdfDictionary> onMessageDelivered(
|
||||
BdfDictionary localState, BdfDictionary delivered) {
|
||||
public StateUpdate<SharerSessionState, BaseMessage> onMessageDelivered(
|
||||
SharerSessionState localState, BaseMessage delivered) {
|
||||
try {
|
||||
return noUpdate(localState, false);
|
||||
} catch (FormatException e) {
|
||||
@@ -227,38 +195,34 @@ public class SharerEngine
|
||||
}
|
||||
}
|
||||
|
||||
private SharerProtocolState getState(Long state) {
|
||||
return SharerProtocolState.fromValue(state.intValue());
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> abortSession(
|
||||
SharerProtocolState currentState, BdfDictionary localState)
|
||||
private StateUpdate<SharerSessionState, BaseMessage> abortSession(
|
||||
State currentState, SharerSessionState localState)
|
||||
throws FormatException {
|
||||
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Aborting protocol session " +
|
||||
Arrays.hashCode(localState.getRaw(SESSION_ID)) +
|
||||
localState.getSessionId().hashCode() +
|
||||
" 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);
|
||||
localState.setState(ERROR);
|
||||
BaseMessage msg = new SimpleMessage(SHARE_MSG_TYPE_ABORT,
|
||||
localState.getGroupId(), localState.getSessionId());
|
||||
List<BaseMessage> messages = Collections.singletonList(msg);
|
||||
|
||||
List<Event> events = Collections.emptyList();
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(false, false,
|
||||
return new StateUpdate<SharerSessionState, BaseMessage>(false, false,
|
||||
localState, messages, events);
|
||||
}
|
||||
|
||||
private StateUpdate<BdfDictionary, BdfDictionary> noUpdate(
|
||||
BdfDictionary localState, boolean delete) throws FormatException {
|
||||
private StateUpdate<SharerSessionState, BaseMessage> noUpdate(
|
||||
SharerSessionState localState, boolean delete)
|
||||
throws FormatException {
|
||||
|
||||
return new StateUpdate<BdfDictionary, BdfDictionary>(delete, false,
|
||||
localState, Collections.<BdfDictionary>emptyList(),
|
||||
return new StateUpdate<SharerSessionState, BaseMessage>(delete, false,
|
||||
localState, Collections.<BaseMessage>emptyList(),
|
||||
Collections.<Event>emptyList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
131
briar-core/src/org/briarproject/forum/SharerSessionState.java
Normal file
131
briar-core/src/org/briarproject/forum/SharerSessionState.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package org.briarproject.forum;
|
||||
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.contact.ContactId;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
|
||||
import static org.briarproject.api.forum.ForumConstants.IS_SHARER;
|
||||
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_LEAVE;
|
||||
import static org.briarproject.api.forum.ForumConstants.STATE;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_INVITATION;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.LOCAL_LEAVE;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_ACCEPT;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_DECLINE;
|
||||
import static org.briarproject.forum.SharerSessionState.Action.REMOTE_LEAVE;
|
||||
|
||||
// This class is not thread-safe
|
||||
public class SharerSessionState extends ForumSharingSessionState {
|
||||
|
||||
private State state;
|
||||
private String msg = null;
|
||||
|
||||
public SharerSessionState(SessionId sessionId, MessageId storageId,
|
||||
GroupId groupId, State state, ContactId contactId, GroupId forumId,
|
||||
String forumName, byte[] forumSalt) {
|
||||
|
||||
super(sessionId, storageId, groupId, contactId, forumId, forumName,
|
||||
forumSalt);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public BdfDictionary toBdfDictionary() {
|
||||
BdfDictionary d = super.toBdfDictionary();
|
||||
d.put(STATE, getState().getValue());
|
||||
d.put(IS_SHARER, true);
|
||||
return d;
|
||||
}
|
||||
|
||||
public void setState(State state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setMessage(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
ERROR(0),
|
||||
PREPARE_INVITATION(1) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_INVITATION) return AWAIT_RESPONSE;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
AWAIT_RESPONSE(2) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == REMOTE_ACCEPT || a == REMOTE_DECLINE) return FINISHED;
|
||||
if (a == LOCAL_LEAVE) return LEFT;
|
||||
return ERROR;
|
||||
}
|
||||
},
|
||||
FINISHED(3) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE || a == REMOTE_LEAVE) return LEFT;
|
||||
return FINISHED;
|
||||
}
|
||||
},
|
||||
LEFT(4) {
|
||||
@Override
|
||||
public State next(Action a) {
|
||||
if (a == LOCAL_LEAVE) return ERROR;
|
||||
return LEFT;
|
||||
}
|
||||
};
|
||||
|
||||
private final int value;
|
||||
|
||||
State(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static State fromValue(int value) {
|
||||
for (State s : values()) {
|
||||
if (s.value == value) return s;
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
public State next(Action a) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
LOCAL_INVITATION,
|
||||
LOCAL_LEAVE,
|
||||
LOCAL_ABORT,
|
||||
REMOTE_ACCEPT,
|
||||
REMOTE_DECLINE,
|
||||
REMOTE_LEAVE,
|
||||
REMOTE_ABORT;
|
||||
|
||||
public static Action 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,10 +9,12 @@ 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.MetadataParser;
|
||||
import org.briarproject.api.db.DatabaseComponent;
|
||||
import org.briarproject.api.db.DbException;
|
||||
import org.briarproject.api.db.NoSuchContactException;
|
||||
import org.briarproject.api.db.NoSuchMessageException;
|
||||
import org.briarproject.api.db.Transaction;
|
||||
import org.briarproject.api.identity.AuthorId;
|
||||
@@ -136,29 +138,58 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
|
||||
|
||||
@Override
|
||||
public void removingContact(Transaction txn, Contact c) throws DbException {
|
||||
// check for open sessions with that contact and abort those
|
||||
Long id = (long) c.getId().getInt();
|
||||
GroupId gId = introductionGroupFactory.createLocalGroup().getId();
|
||||
|
||||
// search for session states where c introduced us
|
||||
BdfDictionary query = BdfDictionary.of(
|
||||
new BdfEntry(ROLE, ROLE_INTRODUCEE),
|
||||
new BdfEntry(CONTACT_ID_1, c.getId().getInt())
|
||||
);
|
||||
try {
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn,
|
||||
introductionGroupFactory.createLocalGroup().getId());
|
||||
.getMessageMetadataAsDictionary(txn, gId, query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
// delete states if introducee removes introducer
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
|
||||
// check for open sessions with c and abort those,
|
||||
// so the other introducee knows
|
||||
query = BdfDictionary.of(
|
||||
new BdfEntry(ROLE, ROLE_INTRODUCER)
|
||||
);
|
||||
try {
|
||||
Map<MessageId, BdfDictionary> map = clientHelper
|
||||
.getMessageMetadataAsDictionary(txn, gId, query);
|
||||
for (Map.Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
BdfDictionary d = entry.getValue();
|
||||
long role = d.getLong(ROLE, -1L);
|
||||
if (role != ROLE_INTRODUCER) {
|
||||
if (d.getLong(CONTACT_ID_1).equals(id)) {
|
||||
// delete states if introducee removes introducer
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
}
|
||||
else if (d.getLong(CONTACT_ID_1).equals(id) ||
|
||||
d.getLong(CONTACT_ID_2).equals(id)) {
|
||||
ContactId c1 = new ContactId(d.getLong(CONTACT_ID_1).intValue());
|
||||
ContactId c2 = new ContactId(d.getLong(CONTACT_ID_2).intValue());
|
||||
|
||||
if (c1.equals(c.getId()) || c2.equals(c.getId())) {
|
||||
IntroducerProtocolState state = IntroducerProtocolState
|
||||
.fromValue(d.getLong(STATE).intValue());
|
||||
// abort protocol if still ongoing
|
||||
if (IntroducerProtocolState.isOngoing(state)) {
|
||||
introducerManager.abort(txn, d);
|
||||
}
|
||||
// also delete state if both contacts have been deleted
|
||||
if (c1.equals(c.getId())) {
|
||||
try {
|
||||
db.getContact(txn, c2);
|
||||
} catch (NoSuchContactException e) {
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
} else if (c2.equals(c.getId())) {
|
||||
try {
|
||||
db.getContact(txn, c1);
|
||||
} catch (NoSuchContactException e) {
|
||||
deleteMessage(txn, entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (FormatException e) {
|
||||
|
||||
@@ -2,10 +2,11 @@ package org.briarproject.introduction;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.clients.BdfMessageContext;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
import org.briarproject.api.clients.SessionId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.system.Clock;
|
||||
@@ -42,7 +43,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(Message m, Group g, BdfList body)
|
||||
protected BdfMessageContext validateMessage(Message m, Group g, BdfList body)
|
||||
throws FormatException {
|
||||
|
||||
BdfDictionary d;
|
||||
@@ -67,7 +68,7 @@ class IntroductionValidator extends BdfMessageValidator {
|
||||
d.put(GROUP_ID, m.getGroupId());
|
||||
d.put(MESSAGE_ID, m.getId());
|
||||
d.put(MESSAGE_TIME, m.getTimestamp());
|
||||
return d;
|
||||
return new BdfMessageContext(d);
|
||||
}
|
||||
|
||||
private BdfDictionary validateRequest(BdfList message)
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.messaging;
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.UniqueId;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.BdfMessageContext;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
@@ -22,7 +23,7 @@ class PrivateMessageValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Parent ID, content type, private message body
|
||||
checkSize(body, 3);
|
||||
@@ -42,6 +43,6 @@ class PrivateMessageValidator extends BdfMessageValidator {
|
||||
meta.put("contentType", contentType);
|
||||
meta.put("local", false);
|
||||
meta.put("read", false);
|
||||
return meta;
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.briarproject.properties;
|
||||
|
||||
import org.briarproject.api.FormatException;
|
||||
import org.briarproject.api.clients.ClientHelper;
|
||||
import org.briarproject.api.clients.BdfMessageContext;
|
||||
import org.briarproject.api.data.BdfDictionary;
|
||||
import org.briarproject.api.data.BdfList;
|
||||
import org.briarproject.api.data.MetadataEncoder;
|
||||
@@ -22,7 +23,7 @@ public class TransportPropertyValidator extends BdfMessageValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BdfDictionary validateMessage(Message m, Group g,
|
||||
protected BdfMessageContext validateMessage(Message m, Group g,
|
||||
BdfList body) throws FormatException {
|
||||
// Transport ID, version, properties
|
||||
checkSize(body, 3);
|
||||
@@ -45,6 +46,6 @@ public class TransportPropertyValidator extends BdfMessageValidator {
|
||||
meta.put("transportId", transportId);
|
||||
meta.put("version", version);
|
||||
meta.put("local", false);
|
||||
return meta;
|
||||
return new BdfMessageContext(meta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@ import org.briarproject.api.lifecycle.Service;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.InvalidMessageException;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
@@ -31,6 +33,7 @@ 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.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
|
||||
@@ -152,31 +155,54 @@ class ValidationManagerImpl implements ValidationManager, Service,
|
||||
if (v == null) {
|
||||
LOG.warning("No validator");
|
||||
} else {
|
||||
Metadata meta = v.validateMessage(m, g);
|
||||
storeValidationResult(m, g.getClientId(), meta);
|
||||
try {
|
||||
MessageContext context = v.validateMessage(m, g);
|
||||
storeMessageContext(m, g.getClientId(), context);
|
||||
} catch (InvalidMessageException e) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.log(INFO, e.toString(), e);
|
||||
markMessageInvalid(m, g.getClientId());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void storeValidationResult(final Message m, final ClientId c,
|
||||
final Metadata meta) {
|
||||
private void storeMessageContext(final Message m, final ClientId c,
|
||||
final MessageContext result) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
if (meta == null) {
|
||||
db.setMessageValid(txn, m, c, false);
|
||||
} else {
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
db.setMessageValid(txn, m, c, true);
|
||||
db.setMessageShared(txn, m, true);
|
||||
IncomingMessageHook hook = hooks.get(c);
|
||||
if (hook != null)
|
||||
hook.incomingMessage(txn, m, meta);
|
||||
}
|
||||
Metadata meta = result.getMetadata();
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
db.setMessageValid(txn, m, c, true);
|
||||
db.setMessageShared(txn, m, true);
|
||||
IncomingMessageHook hook = hooks.get(c);
|
||||
if (hook != null)
|
||||
hook.incomingMessage(txn, m, meta);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markMessageInvalid(final Message m, final ClientId c) {
|
||||
dbExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
db.setMessageValid(txn, m, c, false);
|
||||
txn.setComplete();
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
|
||||
@@ -4,10 +4,6 @@ targetCompatibility = 1.7
|
||||
|
||||
apply plugin: 'witness'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':briar-api')
|
||||
compile fileTree(dir: '../briar-core/libs', include: '*.jar')
|
||||
|
||||
@@ -4,10 +4,6 @@ targetCompatibility = 1.7
|
||||
|
||||
apply plugin: 'witness'
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':briar-api')
|
||||
compile project(':briar-core')
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.ValidationManager;
|
||||
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.api.sync.ValidationManager.MessageValidator;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
import org.hamcrest.Description;
|
||||
import org.jmock.Expectations;
|
||||
@@ -210,7 +211,9 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
|
||||
new AtomicReference<MessageValidator>();
|
||||
final QueueMessageValidator queueMessageValidator =
|
||||
context.mock(QueueMessageValidator.class);
|
||||
final Metadata messageMetadata = new Metadata();
|
||||
final Metadata metadata = new Metadata();
|
||||
final MessageContext messageContext =
|
||||
new MessageContext(metadata);
|
||||
// The message is valid, with a queue position of zero
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
@@ -224,7 +227,7 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
|
||||
// The message should be delegated
|
||||
oneOf(queueMessageValidator).validateMessage(
|
||||
with(any(QueueMessage.class)), with(group));
|
||||
will(returnValue(messageMetadata));
|
||||
will(returnValue(messageContext));
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
@@ -235,7 +238,8 @@ public class MessageQueueManagerImplTest extends BriarTestCase {
|
||||
MessageValidator delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// The message should be valid and the metadata should be returned
|
||||
assertSame(messageMetadata, delegate.validateMessage(message, group));
|
||||
assertSame(messageContext, delegate.validateMessage(message, group));
|
||||
assertSame(metadata, messageContext.getMetadata());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@@ -85,7 +85,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
|
||||
name, publicKey, text);
|
||||
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, body);
|
||||
validator.validateMessage(message, group, body)
|
||||
.getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
@@ -182,7 +183,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
|
||||
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
|
||||
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, body);
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
@@ -199,7 +200,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT));
|
||||
|
||||
BdfDictionary result = validator.validateMessage(message, group, body);
|
||||
BdfDictionary result = validator.validateMessage(message, group, body)
|
||||
.getDictionary();
|
||||
|
||||
assertFalse(result.getBoolean(ACCEPT));
|
||||
context.assertIsSatisfied();
|
||||
@@ -288,7 +290,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body);
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
@@ -334,7 +336,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body);
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
|
||||
@@ -59,7 +59,8 @@ public class TransportPropertyValidatorTest extends BriarTestCase {
|
||||
|
||||
BdfList body = BdfList.of(transportId.getString(), 4, bdfDictionary);
|
||||
|
||||
BdfDictionary result = tpv.validateMessage(message, group, body);
|
||||
BdfDictionary result = tpv.validateMessage(message, group, body)
|
||||
.getDictionary();
|
||||
|
||||
assertEquals("test", result.getString("transportId"));
|
||||
assertEquals(4, result.getLong("version").longValue());
|
||||
|
||||
@@ -14,10 +14,12 @@ import org.briarproject.api.event.MessageAddedEvent;
|
||||
import org.briarproject.api.sync.ClientId;
|
||||
import org.briarproject.api.sync.Group;
|
||||
import org.briarproject.api.sync.GroupId;
|
||||
import org.briarproject.api.sync.InvalidMessageException;
|
||||
import org.briarproject.api.sync.Message;
|
||||
import org.briarproject.api.sync.MessageId;
|
||||
import org.briarproject.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.api.sync.ValidationManager.MessageValidator;
|
||||
import org.briarproject.api.sync.MessageContext;
|
||||
import org.briarproject.util.ByteUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
@@ -41,6 +43,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
|
||||
private final Message message1 = new Message(messageId1, groupId, timestamp,
|
||||
raw);
|
||||
private final Metadata metadata = new Metadata();
|
||||
final MessageContext validResult = new MessageContext(metadata);
|
||||
private final ContactId contactId = new ContactId(234);
|
||||
|
||||
public ValidationManagerImplTest() {
|
||||
@@ -80,7 +83,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
|
||||
oneOf(db).endTransaction(txn1);
|
||||
// Validate the first message: valid
|
||||
oneOf(validator).validateMessage(message, group);
|
||||
will(returnValue(metadata));
|
||||
will(returnValue(validResult));
|
||||
// Store the validation result for the first message
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn2));
|
||||
@@ -100,7 +103,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
|
||||
oneOf(db).endTransaction(txn3);
|
||||
// Validate the second message: invalid
|
||||
oneOf(validator).validateMessage(message1, group);
|
||||
will(returnValue(null));
|
||||
will(throwException(new InvalidMessageException()));
|
||||
// Store the validation result for the second message
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn4));
|
||||
@@ -154,7 +157,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
|
||||
oneOf(db).endTransaction(txn2);
|
||||
// Validate the second message: invalid
|
||||
oneOf(validator).validateMessage(message1, group);
|
||||
will(returnValue(null));
|
||||
will(throwException(new InvalidMessageException()));
|
||||
// Store the validation result for the second message
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn3));
|
||||
@@ -211,7 +214,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
|
||||
oneOf(db).endTransaction(txn2);
|
||||
// Validate the second message: invalid
|
||||
oneOf(validator).validateMessage(message1, group);
|
||||
will(returnValue(null));
|
||||
will(throwException(new InvalidMessageException()));
|
||||
// Store the validation result for the second message
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn3));
|
||||
@@ -248,7 +251,7 @@ public class ValidationManagerImplTest extends BriarTestCase {
|
||||
oneOf(db).endTransaction(txn);
|
||||
// Validate the message: valid
|
||||
oneOf(validator).validateMessage(message, group);
|
||||
will(returnValue(metadata));
|
||||
will(returnValue(validResult));
|
||||
// Store the validation result
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn1));
|
||||
|
||||
15
build.gradle
15
build.gradle
@@ -1,11 +1,16 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
jcenter()
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -14,4 +19,4 @@ buildscript {
|
||||
classpath 'de.undercouch:gradle-download-task:2.1.0'
|
||||
classpath files('briar-core/libs/gradle-witness.jar')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Wed Apr 27 17:31:51 BST 2016
|
||||
#Thu May 12 13:08:34 BST 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip
|
||||
|
||||
Reference in New Issue
Block a user