diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java index a9e21a6b0f9364938fad3bc829a0b918f877e824..cf9d4eff3bedfa283a34be88afdc26725cb778bc 100644 --- a/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/ForumManagerTest.java @@ -209,6 +209,18 @@ public class ForumManagerTest { deliveryWaiter.await(TIMEOUT, 1); assertEquals(1, forumManager1.getPostHeaders(g).size()); + // add another forum post + time = clock.currentTimeMillis(); + ForumPost post2 = createForumPost(g, null, "b", time); + forumManager1.addLocalPost(post2); + assertEquals(1, forumManager0.getPostHeaders(g).size()); + assertEquals(2, forumManager1.getPostHeaders(g).size()); + + // send post to 0 + sync1To0(); + deliveryWaiter.await(TIMEOUT, 1); + assertEquals(2, forumManager1.getPostHeaders(g).size()); + stopLifecycles(); } diff --git a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java index 522ca424ee2d26af423b0a5743f348d5e98380ec..444f2f4c8df18691ff30d72a89bde26499a4d5e2 100644 --- a/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java +++ b/briar-android-tests/src/test/java/org/briarproject/ForumSharingIntegrationTest.java @@ -9,6 +9,10 @@ import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.crypto.KeyPair; +import org.briarproject.api.crypto.KeyParser; +import org.briarproject.api.crypto.PrivateKey; import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.data.BdfList; import org.briarproject.api.db.DatabaseComponent; @@ -23,6 +27,9 @@ import org.briarproject.api.event.MessageStateChangedEvent; import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.ForumInvitationMessage; import org.briarproject.api.forum.ForumManager; +import org.briarproject.api.forum.ForumPost; +import org.briarproject.api.forum.ForumPostFactory; +import org.briarproject.api.forum.ForumPostHeader; import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.identity.AuthorFactory; import org.briarproject.api.identity.IdentityManager; @@ -62,7 +69,6 @@ import javax.inject.Inject; import static org.briarproject.TestPluginsModule.MAX_LATENCY; import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; -import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION; import static org.briarproject.api.sync.ValidationManager.State.DELIVERED; import static org.briarproject.api.sync.ValidationManager.State.INVALID; @@ -87,6 +93,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase { Clock clock; @Inject AuthorFactory authorFactory; + @Inject + ForumPostFactory forumPostFactory; + @Inject + CryptoComponent cryptoComponent; // objects accessed from background threads need to be volatile private volatile ForumSharingManager forumSharingManager0; @@ -173,7 +183,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { assertTrue(listener0.responseReceived); // forum was added successfully - assertEquals(0, forumSharingManager0.getAvailable().size()); + assertEquals(0, forumSharingManager0.getInvited().size()); assertEquals(1, forumManager1.getForums().size()); // invitee has one invitation message from sharer @@ -223,10 +233,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase { assertTrue(listener0.responseReceived); // forum was not added - assertEquals(0, forumSharingManager0.getAvailable().size()); + assertEquals(0, forumSharingManager0.getInvited().size()); assertEquals(0, forumManager1.getForums().size()); // forum is no longer available to invitee who declined - assertEquals(0, forumSharingManager1.getAvailable().size()); + assertEquals(0, forumSharingManager1.getInvited().size()); // invitee has one invitation message from sharer List<ForumInvitationMessage> list = @@ -273,7 +283,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { assertTrue(listener0.responseReceived); // forum was added successfully - assertEquals(0, forumSharingManager0.getAvailable().size()); + assertEquals(0, forumSharingManager0.getInvited().size()); assertEquals(1, forumManager1.getForums().size()); assertTrue(forumManager1.getForums().contains(forum0)); @@ -293,7 +303,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { syncToSharer(); // forum is gone - assertEquals(0, forumSharingManager0.getAvailable().size()); + assertEquals(0, forumSharingManager0.getInvited().size()); assertEquals(0, forumManager1.getForums().size()); // sharer no longer shares forum with invitee @@ -333,7 +343,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { assertTrue(listener0.responseReceived); // forum was added successfully - assertEquals(0, forumSharingManager0.getAvailable().size()); + assertEquals(0, forumSharingManager0.getInvited().size()); assertEquals(1, forumManager1.getForums().size()); assertTrue(forumManager1.getForums().contains(forum0)); @@ -384,8 +394,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase { // sharer un-subscribes from forum forumManager0.removeForum(forum0); - // from here on expect the response to fail with a DbException - thrown.expect(DbException.class); + // from here on expect the response to fail with an AssertionError, + // because there is in fact no invited forum available anymore + thrown.expect(AssertionError.class); // sync first request message and leave message syncToInvitee(); @@ -393,7 +404,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase { assertTrue(listener1.requestReceived); // invitee has no forums available - assertEquals(0, forumSharingManager1.getAvailable().size()); + assertEquals(0, forumSharingManager1.getInvited().size()); } finally { stopLifecycles(); } @@ -699,10 +710,11 @@ public class ForumSharingIntegrationTest extends BriarTestCase { deliverMessage(sync2, contactId2, sync1, contactId1, "Sharer2 to Invitee"); - // make sure we have only one forum available - Collection<Forum> forums = - forumSharingManager1.getAvailable(); + // make sure we now have two invitations to the same forum available + Collection<Forum> forums = forumSharingManager1.getInvited(); assertEquals(1, forums.size()); + assertEquals(2, + forumSharingManager1.getSharedBy(forum0.getId()).size()); // make sure both sharers actually share the forum Collection<Contact> contacts = @@ -731,6 +743,119 @@ public class ForumSharingIntegrationTest extends BriarTestCase { } } + @Test + public void testSyncAfterReSharing() throws Exception { + startLifecycles(); + try { + // initialize and let invitee accept all requests + defaultInit(true); + + // send invitation + forumSharingManager0 + .sendInvitation(forum0.getId(), contactId1, "Hi!"); + + // sync first request message + syncToInvitee(); + eventWaiter.await(TIMEOUT, 1); + + // sync response back + syncToSharer(); + eventWaiter.await(TIMEOUT, 1); + + // sharer posts into the forum + long time = clock.currentTimeMillis(); + byte[] body = TestUtils.getRandomBytes(42); + KeyParser keyParser = cryptoComponent.getSignatureKeyParser(); + PrivateKey key = keyParser.parsePrivateKey(author0.getPrivateKey()); + ForumPost p = forumPostFactory + .createPseudonymousPost(forum0.getId(), time, null, author0, + "text/plain", body, key); + forumManager0.addLocalPost(p); + + // sync forum post + syncToInvitee(); + + // make sure forum post arrived + Collection<ForumPostHeader> headers = + forumManager1.getPostHeaders(forum0.getId()); + assertEquals(1, headers.size()); + ForumPostHeader header = headers.iterator().next(); + assertEquals(p.getMessage().getId(), header.getId()); + assertEquals(author0, header.getAuthor()); + + // now invitee creates a post + time = clock.currentTimeMillis(); + body = TestUtils.getRandomBytes(42); + key = keyParser.parsePrivateKey(author1.getPrivateKey()); + p = forumPostFactory + .createPseudonymousPost(forum0.getId(), time, null, author1, + "text/plain", body, key); + forumManager1.addLocalPost(p); + + // sync forum post + syncToSharer(); + + // make sure forum post arrived + headers = forumManager1.getPostHeaders(forum0.getId()); + assertEquals(2, headers.size()); + boolean found = false; + for (ForumPostHeader h : headers) { + if (p.getMessage().getId().equals(h.getId())) { + found = true; + assertEquals(author1, h.getAuthor()); + } + } + assertTrue(found); + + // contacts remove each other + contactManager0.removeContact(contactId1); + contactManager1.removeContact(contactId0); + contactManager1.removeContact(contactId2); + contactManager2.removeContact(contactId21); + + // contacts add each other back + addDefaultContacts(); + + // send invitation again + forumSharingManager0 + .sendInvitation(forum0.getId(), contactId1, "Hi!"); + + // sync first request message + syncToInvitee(); + eventWaiter.await(TIMEOUT, 1); + + // sync response back + syncToSharer(); + eventWaiter.await(TIMEOUT, 1); + + // now invitee creates a post + time = clock.currentTimeMillis(); + body = TestUtils.getRandomBytes(42); + key = keyParser.parsePrivateKey(author1.getPrivateKey()); + p = forumPostFactory + .createPseudonymousPost(forum0.getId(), time, null, author1, + "text/plain", body, key); + forumManager1.addLocalPost(p); + + // sync forum post + syncToSharer(); + + // make sure forum post arrived + headers = forumManager1.getPostHeaders(forum0.getId()); + assertEquals(3, headers.size()); + found = false; + for (ForumPostHeader h : headers) { + if (p.getMessage().getId().equals(h.getId())) { + found = true; + assertEquals(author1, h.getAuthor()); + } + } + assertTrue(found); + } finally { + stopLifecycles(); + } + } + @After public void tearDown() throws InterruptedException { @@ -753,6 +878,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase { LOG.info("TEST: Sharer received message in group " + event.getMessage().getGroupId().hashCode()); msgWaiter.resume(); + } else if (s == DELIVERED && !event.isLocal() && + c.equals(forumManager0.getClientId())) { + LOG.info("TEST: Sharer received forum post"); + msgWaiter.resume(); } } else if (e instanceof ForumInvitationResponseReceivedEvent) { ForumInvitationResponseReceivedEvent event = @@ -807,6 +936,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase { LOG.info("TEST: Invitee received message in group " + event.getMessage().getGroupId().hashCode()); msgWaiter.resume(); + } else if (s == DELIVERED && !event.isLocal() && + c.equals(forumManager0.getClientId())) { + LOG.info("TEST: Invitee received forum post"); + msgWaiter.resume(); } } else if (e instanceof ForumInvitationReceivedEvent) { ForumInvitationReceivedEvent event = @@ -815,6 +948,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase { if (!answer) return; Forum f = event.getForum(); try { + eventWaiter.assertEquals(1, + forumSharingManager1.getInvited().size()); Contact c = contactManager1.getContact(event.getContactId()); forumSharingManager1.respondToInvitation(f, c, accept); @@ -866,17 +1001,22 @@ public class ForumSharingIntegrationTest extends BriarTestCase { } private void addDefaultIdentities() throws DbException { + KeyPair keyPair = cryptoComponent.generateSignatureKeyPair(); author0 = authorFactory.createLocalAuthor(SHARER, - TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH), - TestUtils.getRandomBytes(123)); + keyPair.getPublic().getEncoded(), + keyPair.getPrivate().getEncoded()); identityManager0.addLocalAuthor(author0); + + keyPair = cryptoComponent.generateSignatureKeyPair(); author1 = authorFactory.createLocalAuthor(INVITEE, - TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH), - TestUtils.getRandomBytes(123)); + keyPair.getPublic().getEncoded(), + keyPair.getPrivate().getEncoded()); identityManager1.addLocalAuthor(author1); + + keyPair = cryptoComponent.generateSignatureKeyPair(); author2 = authorFactory.createLocalAuthor(SHARER2, - TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH), - TestUtils.getRandomBytes(123)); + keyPair.getPublic().getEncoded(), + keyPair.getPrivate().getEncoded()); identityManager2.addLocalAuthor(author2); } diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index ea3126ed0c5ca8c1cd2581ebb1983002bb3b3ba4..03664f0efa100ae0cba4cdeaeadc960454b77007 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -100,8 +100,8 @@ </activity> <activity - android:name=".android.forum.AvailableForumsActivity" - android:label="@string/available_forums_title" + android:name=".android.forum.ForumInvitationsActivity" + android:label="@string/forum_invitations_title" android:parentActivityName=".android.NavDrawerActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" diff --git a/briar-android/res/layout/list_item_available_forum.xml b/briar-android/res/layout/list_item_available_forum.xml index 536d983e22e42b31b72143aec91c40ec359c7cbf..823fff7824fe82e26cb305cdb1b524b856b4139d 100644 --- a/briar-android/res/layout/list_item_available_forum.xml +++ b/briar-android/res/layout/list_item_available_forum.xml @@ -6,8 +6,8 @@ android:layout_height="wrap_content" android:layout_marginLeft="@dimen/listitem_horizontal_margin" android:layout_marginStart="@dimen/listitem_horizontal_margin" - android:paddingTop="@dimen/listitem_horizontal_margin" - android:background="?attr/selectableItemBackground"> + android:background="?attr/selectableItemBackground" + android:paddingTop="@dimen/listitem_horizontal_margin"> <org.briarproject.android.util.TextAvatarView android:id="@+id/avatarView" @@ -22,10 +22,12 @@ android:id="@+id/forumNameView" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/listitem_horizontal_margin" + android:layout_marginRight="@dimen/listitem_horizontal_margin" android:layout_toEndOf="@+id/avatarView" android:layout_toRightOf="@+id/avatarView" android:maxLines="2" - android:textColor="@android:color/primary_text_light" + android:textColor="@color/briar_primary" android:textSize="@dimen/text_size_medium" tools:text="This is a name of a forum that is available"/> @@ -34,38 +36,55 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/forumNameView" - android:layout_marginBottom="-8dp" + android:layout_marginEnd="@dimen/listitem_horizontal_margin" + android:layout_marginRight="@dimen/listitem_horizontal_margin" android:layout_toEndOf="@+id/avatarView" android:layout_toRightOf="@+id/avatarView" android:paddingTop="@dimen/margin_medium" - android:textColor="@android:color/secondary_text_light" + android:textColor="@color/briar_text_secondary" android:textSize="@dimen/text_size_small" tools:text="Shared by Megalox"/> + <TextView + android:id="@+id/forumSubscribedView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@+id/sharedByView" + android:layout_marginEnd="@dimen/listitem_horizontal_margin" + android:layout_marginRight="@dimen/listitem_horizontal_margin" + android:paddingTop="@dimen/margin_medium" + android:text="@string/forum_invitation_exists" + android:textColor="@color/briar_text_tertiary" + android:textSize="@dimen/text_size_small" + tools:visibility="visible"/> + <Button android:id="@+id/acceptButton" style="@style/BriarButtonFlat.Positive" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/dialog_button_accept" - android:layout_below="@+id/sharedByView" + android:layout_alignParentEnd="true" android:layout_alignParentRight="true" - android:layout_alignParentEnd="true"/> + android:layout_below="@+id/forumSubscribedView" + android:layout_marginTop="-8dp" + android:text="@string/dialog_button_accept"/> <Button android:id="@+id/declineButton" style="@style/BriarButtonFlat.Negative" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/dialog_button_decline" - android:layout_below="@+id/sharedByView" + android:layout_below="@+id/forumSubscribedView" + android:layout_marginTop="-8dp" android:layout_toLeftOf="@+id/acceptButton" - android:layout_toStartOf="@+id/acceptButton"/> + android:layout_toStartOf="@+id/acceptButton" + android:text="@string/dialog_button_decline"/> - <View style="@style/Divider.ForumList" - android:layout_below="@+id/acceptButton" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true"/> + <View + style="@style/Divider.ForumList" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_below="@+id/acceptButton"/> </RelativeLayout> diff --git a/briar-android/res/layout/list_item_forum_invitation_in.xml b/briar-android/res/layout/list_item_forum_invitation_in.xml index ad8d0bcfc622dc8e5e0110130fa9f528a2abdab2..33f95059baae5763c77bbc6963c7d0f7f279a062 100644 --- a/briar-android/res/layout/list_item_forum_invitation_in.xml +++ b/briar-android/res/layout/list_item_forum_invitation_in.xml @@ -51,7 +51,7 @@ android:layout_alignEnd="@+id/introductionText" android:layout_alignRight="@+id/introductionText" android:layout_below="@+id/introductionText" - android:text="@string/forum_show_available"/> + android:text="@string/forum_show_invitations"/> </RelativeLayout> diff --git a/briar-android/res/values-pt-rBR/strings.xml b/briar-android/res/values-pt-rBR/strings.xml index 4bb995c4715957fa1888769b75844e722a596a6c..7aaeee2eab279b6cabd04e02aea3fa2252abcfee 100644 --- a/briar-android/res/values-pt-rBR/strings.xml +++ b/briar-android/res/values-pt-rBR/strings.xml @@ -98,7 +98,7 @@ <string name="forum_share_message">Você pode escrever um convite que será enviado aos contatos selecionados.</string> <string name="forum_invitation_received">%1$s compartilhou o fórum \"%2$s\" com você.</string> <string name="forum_invitation_sent">Você compartilhou o fórum \"%1$s\" com %2$s.</string> - <string name="forum_show_available">Mostrar fóruns disponÃveis</string> + <string name="forum_show_invitations">Mostrar fóruns disponÃveis</string> <string name="forum_compose_post">Nova postagem em fórum</string> <string name="from">De:</string> <string name="anonymous">Anônimo</string> diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index b9cf51ef47dfe308c540e435586a58ba0307c62e..d44bc389a6872b6a3c538fa3e9efc5ff7d251c4b 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -103,7 +103,7 @@ <string name="forum_share_message">You may compose an optional invitation message that will be sent to the selected contacts.</string> <string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string> <string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string> - <string name="forum_show_available">Show Available Forums</string> + <string name="forum_show_invitations">Show Forum Invitations</string> <string name="forum_compose_post">New Forum Post</string> <string name="from">From:</string> <string name="anonymous">Anonymous</string> @@ -112,7 +112,8 @@ <string name="create_identity_button">Create Identity</string> <string name="identity_created_toast">Identity created</string> <string name="forum_post_hint">Type forum post</string> - <string name="available_forums_title">Available Forums</string> + <string name="forum_invitations_title">Forum Invitations</string> + <string name="forum_invitation_exists">You accepted an invitation to this forum already. Accepting more invitations will grow and strengthen the communication in the forum.</string> <string name="forum_joined_toast">Joined Forum</string> <string name="forum_declined_toast">Forum Invitation Declined</string> <string name="shared_by_format">Shared by %s</string> diff --git a/briar-android/src/org/briarproject/android/ActivityComponent.java b/briar-android/src/org/briarproject/android/ActivityComponent.java index 4d82fe74dcfe02cf5e2d3243bc419edeb5550c19..3353f62b44e7fd29024f72b03a96502d773cc077 100644 --- a/briar-android/src/org/briarproject/android/ActivityComponent.java +++ b/briar-android/src/org/briarproject/android/ActivityComponent.java @@ -5,7 +5,7 @@ import android.app.Activity; import org.briarproject.android.blogs.MyBlogsFragment; import org.briarproject.android.contact.ContactListFragment; import org.briarproject.android.contact.ConversationActivity; -import org.briarproject.android.forum.AvailableForumsActivity; +import org.briarproject.android.forum.ForumInvitationsActivity; import org.briarproject.android.forum.ContactSelectorFragment; import org.briarproject.android.forum.CreateForumActivity; import org.briarproject.android.forum.ForumActivity; @@ -54,7 +54,7 @@ public interface ActivityComponent { void inject(CreateIdentityActivity activity); - void inject(AvailableForumsActivity activity); + void inject(ForumInvitationsActivity activity); void inject(CreateForumActivity activity); diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java index cc67ec7772ff5571543607187d26594852759c45..2884350ea9994d77da44c650fa8635a3615a4a8d 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java @@ -13,7 +13,7 @@ import android.widget.ImageView; import android.widget.TextView; import org.briarproject.R; -import org.briarproject.android.forum.AvailableForumsActivity; +import org.briarproject.android.forum.ForumInvitationsActivity; import org.briarproject.android.util.AndroidUtils; import org.briarproject.api.clients.SessionId; import org.briarproject.api.forum.ForumInvitationMessage; @@ -322,7 +322,7 @@ class ConversationAdapter extends RecyclerView.Adapter { @Override public void onClick(View v) { Intent intent = new Intent(ctx, - AvailableForumsActivity.class); + ForumInvitationsActivity.class); ctx.startActivity(intent); } }); diff --git a/briar-android/src/org/briarproject/android/forum/AvailableForumsItem.java b/briar-android/src/org/briarproject/android/forum/AvailableForumsItem.java deleted file mode 100644 index 7d8a91feb8b4fea845dde718d0594fae89b1d366..0000000000000000000000000000000000000000 --- a/briar-android/src/org/briarproject/android/forum/AvailableForumsItem.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.briarproject.android.forum; - -import org.briarproject.api.contact.Contact; -import org.briarproject.api.forum.Forum; - -import java.util.Collection; - -class AvailableForumsItem { - - private final ForumContacts forumContacts; - - AvailableForumsItem(ForumContacts forumContacts) { - this.forumContacts = forumContacts; - } - - Forum getForum() { - return forumContacts.getForum(); - } - - Collection<Contact> getContacts() { - return forumContacts.getContacts(); - } -} diff --git a/briar-android/src/org/briarproject/android/forum/AvailableForumsAdapter.java b/briar-android/src/org/briarproject/android/forum/ForumInvitationAdapter.java similarity index 73% rename from briar-android/src/org/briarproject/android/forum/AvailableForumsAdapter.java rename to briar-android/src/org/briarproject/android/forum/ForumInvitationAdapter.java index 8fc9caa399613d34f212fdf362fe7293ff0456ec..23e24b7e1494de5e919aa29691b89d267dffea85 100644 --- a/briar-android/src/org/briarproject/android/forum/AvailableForumsAdapter.java +++ b/briar-android/src/org/briarproject/android/forum/ForumInvitationAdapter.java @@ -17,16 +17,19 @@ import org.briarproject.util.StringUtils; import java.util.ArrayList; import java.util.Collection; -class AvailableForumsAdapter extends - RecyclerView.Adapter<AvailableForumsAdapter.AvailableForumViewHolder> { +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +class ForumInvitationAdapter extends + RecyclerView.Adapter<ForumInvitationAdapter.AvailableForumViewHolder> { private final Context ctx; private final AvailableForumClickListener listener; - private final SortedList<AvailableForumsItem> forums = - new SortedList<>(AvailableForumsItem.class, + private final SortedList<ForumInvitationItem> forums = + new SortedList<>(ForumInvitationItem.class, new SortedListCallBacks()); - AvailableForumsAdapter(Context ctx, AvailableForumClickListener listener) { + ForumInvitationAdapter(Context ctx, AvailableForumClickListener listener) { this.ctx = ctx; this.listener = listener; } @@ -42,7 +45,7 @@ class AvailableForumsAdapter extends @Override public void onBindViewHolder(AvailableForumViewHolder ui, int position) { - final AvailableForumsItem item = getItem(position); + final ForumInvitationItem item = getItem(position); ui.avatar.setText(item.getForum().getName().substring(0, 1)); ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes()); @@ -54,6 +57,12 @@ class AvailableForumsAdapter extends ui.sharedBy.setText(ctx.getString(R.string.shared_by_format, StringUtils.join(names, ", "))); + if (item.isSubscribed()) { + ui.subscribed.setVisibility(VISIBLE); + } else { + ui.subscribed.setVisibility(GONE); + } + ui.accept.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -73,15 +82,15 @@ class AvailableForumsAdapter extends return forums.size(); } - public AvailableForumsItem getItem(int position) { + public ForumInvitationItem getItem(int position) { return forums.get(position); } - public void add(AvailableForumsItem item) { + public void add(ForumInvitationItem item) { forums.add(item); } - public void addAll(Collection<AvailableForumsItem> list) { + public void addAll(Collection<ForumInvitationItem> list) { forums.addAll(list); } @@ -89,32 +98,34 @@ class AvailableForumsAdapter extends forums.clear(); } - protected static class AvailableForumViewHolder + static class AvailableForumViewHolder extends RecyclerView.ViewHolder { private final TextAvatarView avatar; private final TextView name; private final TextView sharedBy; + private final TextView subscribed; private final Button accept; private final Button decline; - public AvailableForumViewHolder(View v) { + AvailableForumViewHolder(View v) { super(v); avatar = (TextAvatarView) v.findViewById(R.id.avatarView); name = (TextView) v.findViewById(R.id.forumNameView); sharedBy = (TextView) v.findViewById(R.id.sharedByView); + subscribed = (TextView) v.findViewById(R.id.forumSubscribedView); accept = (Button) v.findViewById(R.id.acceptButton); decline = (Button) v.findViewById(R.id.declineButton); } } private class SortedListCallBacks - extends SortedList.Callback<AvailableForumsItem> { + extends SortedList.Callback<ForumInvitationItem> { @Override - public int compare(AvailableForumsItem o1, - AvailableForumsItem o2) { + public int compare(ForumInvitationItem o1, + ForumInvitationItem o2) { return String.CASE_INSENSITIVE_ORDER .compare(o1.getForum().getName(), o2.getForum().getName()); @@ -141,21 +152,21 @@ class AvailableForumsAdapter extends } @Override - public boolean areContentsTheSame(AvailableForumsItem oldItem, - AvailableForumsItem newItem) { + public boolean areContentsTheSame(ForumInvitationItem oldItem, + ForumInvitationItem newItem) { return oldItem.getForum().equals(newItem.getForum()) && oldItem.getContacts().equals(newItem.getContacts()); } @Override - public boolean areItemsTheSame(AvailableForumsItem oldItem, - AvailableForumsItem newItem) { + public boolean areItemsTheSame(ForumInvitationItem oldItem, + ForumInvitationItem newItem) { return oldItem.getForum().equals(newItem.getForum()); } } interface AvailableForumClickListener { - void onItemClick(AvailableForumsItem item, boolean accept); + void onItemClick(ForumInvitationItem item, boolean accept); } } diff --git a/briar-android/src/org/briarproject/android/forum/ForumContacts.java b/briar-android/src/org/briarproject/android/forum/ForumInvitationItem.java similarity index 61% rename from briar-android/src/org/briarproject/android/forum/ForumContacts.java rename to briar-android/src/org/briarproject/android/forum/ForumInvitationItem.java index ce24fc86053d44987cc5fb710f37a90c65b2ad27..9db87abe73aa5123b5f743dfecb8417e9f7e46da 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumContacts.java +++ b/briar-android/src/org/briarproject/android/forum/ForumInvitationItem.java @@ -5,13 +5,17 @@ import org.briarproject.api.forum.Forum; import java.util.Collection; -class ForumContacts { +class ForumInvitationItem { private final Forum forum; + private final boolean subscribed; private final Collection<Contact> contacts; - ForumContacts(Forum forum, Collection<Contact> contacts) { + ForumInvitationItem(Forum forum, boolean subscribed, + Collection<Contact> contacts) { + this.forum = forum; + this.subscribed = subscribed; this.contacts = contacts; } @@ -19,6 +23,10 @@ class ForumContacts { return forum; } + public boolean isSubscribed() { + return subscribed; + } + Collection<Contact> getContacts() { return contacts; } diff --git a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java b/briar-android/src/org/briarproject/android/forum/ForumInvitationsActivity.java similarity index 80% rename from briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java rename to briar-android/src/org/briarproject/android/forum/ForumInvitationsActivity.java index f7034475cfe2b89f043f887a0a3f11030d8a714f..24436eb81efada0c75f0072b4876975f758d814b 100644 --- a/briar-android/src/org/briarproject/android/forum/AvailableForumsActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumInvitationsActivity.java @@ -24,7 +24,6 @@ import org.briarproject.api.forum.ForumSharingManager; import java.util.ArrayList; import java.util.Collection; -import java.util.List; import java.util.logging.Logger; import javax.inject.Inject; @@ -32,15 +31,15 @@ import javax.inject.Inject; import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -import static org.briarproject.android.forum.AvailableForumsAdapter.AvailableForumClickListener; +import static org.briarproject.android.forum.ForumInvitationAdapter.AvailableForumClickListener; -public class AvailableForumsActivity extends BriarActivity +public class ForumInvitationsActivity extends BriarActivity implements EventListener, AvailableForumClickListener { private static final Logger LOG = - Logger.getLogger(AvailableForumsActivity.class.getName()); + Logger.getLogger(ForumInvitationsActivity.class.getName()); - private AvailableForumsAdapter adapter; + private ForumInvitationAdapter adapter; // Fields that are accessed from background threads must be volatile @Inject @@ -56,7 +55,7 @@ public class AvailableForumsActivity extends BriarActivity setContentView(R.layout.activity_available_forums); - adapter = new AvailableForumsAdapter(this, this); + adapter = new ForumInvitationAdapter(this, this); BriarRecyclerView list = (BriarRecyclerView) findViewById(R.id.availableForumsView); list.setLayoutManager(new LinearLayoutManager(this)); @@ -80,21 +79,25 @@ public class AvailableForumsActivity extends BriarActivity @Override public void run() { try { - Collection<ForumContacts> available = new ArrayList<>(); + Collection<ForumInvitationItem> forums = new ArrayList<>(); long now = System.currentTimeMillis(); - for (Forum f : forumSharingManager.getAvailable()) { + for (Forum f : forumSharingManager.getInvited()) { + boolean subscribed; try { - Collection<Contact> c = - forumSharingManager.getSharedBy(f.getId()); - available.add(new ForumContacts(f, c)); + forumManager.getForum(f.getId()); + subscribed = true; } catch (NoSuchGroupException e) { - // Continue + subscribed = false; } + Collection<Contact> c = + forumSharingManager.getSharedBy(f.getId()); + forums.add( + new ForumInvitationItem(f, subscribed, c)); } long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Load took " + duration + " ms"); - displayForums(available); + displayForums(forums); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -103,20 +106,16 @@ public class AvailableForumsActivity extends BriarActivity }); } - private void displayForums(final Collection<ForumContacts> available) { + private void displayForums(final Collection<ForumInvitationItem> forums) { runOnUiThread(new Runnable() { @Override public void run() { - if (available.isEmpty()) { + if (forums.isEmpty()) { LOG.info("No forums available, finishing"); finish(); } else { adapter.clear(); - List<AvailableForumsItem> list = - new ArrayList<>(available.size()); - for (ForumContacts f : available) - list.add(new AvailableForumsItem(f)); - adapter.addAll(list); + adapter.addAll(forums); } } }); @@ -152,7 +151,7 @@ public class AvailableForumsActivity extends BriarActivity } @Override - public void onItemClick(AvailableForumsItem item, boolean accept) { + public void onItemClick(ForumInvitationItem item, boolean accept) { respondToInvitation(item, accept); // show toast @@ -161,7 +160,7 @@ public class AvailableForumsActivity extends BriarActivity Toast.makeText(this, res, LENGTH_SHORT).show(); } - private void respondToInvitation(final AvailableForumsItem item, + private void respondToInvitation(final ForumInvitationItem item, final boolean accept) { runOnDbThread(new Runnable() { @Override diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java index d8c5c81bde9058b91ddcda96eb0df4837ea51298..c3dc8ebe40e82cc5c07f81038f33032704b8c0b9 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java @@ -188,7 +188,7 @@ public class ForumListFragment extends BaseEventFragment implements try { long now = System.currentTimeMillis(); int available = - forumSharingManager.getAvailable().size(); + forumSharingManager.getInvited().size(); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading available took " + duration + " ms"); @@ -290,6 +290,6 @@ public class ForumListFragment extends BaseEventFragment implements @Override public void onClick(View view) { // snackbar click - startActivity(new Intent(getContext(), AvailableForumsActivity.class)); + startActivity(new Intent(getContext(), ForumInvitationsActivity.class)); } } diff --git a/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java index ec9f23e49df4910fc2bdc5935fd98b4e9eed4cbd..6fed69e19faf24ba63cb3c82e9aef514833d2bf6 100644 --- a/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java +++ b/briar-api/src/org/briarproject/api/blogs/BlogSharingManager.java @@ -38,9 +38,9 @@ public interface BlogSharingManager ContactId contactId) throws DbException; /** - * Returns all blogs to which the user could subscribe. + * Returns all blogs to which the user has been invited. */ - Collection<Blog> getAvailable() throws DbException; + Collection<Blog> getInvited() throws DbException; /** * Returns all contacts who are sharing the given blog with us. diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java index 55da0615f9134ee0d6a1c89f62a0d53fcef7321c..365cf18e753cb28964d11e0ae4018abb5e334d0f 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java +++ b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java @@ -34,8 +34,8 @@ public interface ForumSharingManager extends SharingManager<Forum, ForumInvitati Collection<ForumInvitationMessage> getInvitationMessages( ContactId contactId) throws DbException; - /** Returns all forums to which the user could subscribe. */ - Collection<Forum> getAvailable() throws DbException; + /** Returns all forums to which the user has been invited. */ + Collection<Forum> getInvited() throws DbException; /** Returns all contacts who are sharing the given forum with us. */ Collection<Contact> getSharedBy(GroupId g) throws DbException; diff --git a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java index 952bfd6602b2c3956c287c17bad66abe1cb30fe0..43d689729be66acab0d2733c36d35c88fcb79ad4 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingConstants.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingConstants.java @@ -25,7 +25,6 @@ public interface SharingConstants { int SHARE_MSG_TYPE_DECLINE = 3; int SHARE_MSG_TYPE_LEAVE = 4; int SHARE_MSG_TYPE_ABORT = 5; - String TASK = "task"; int TASK_ADD_SHAREABLE_TO_LIST_SHARED_WITH_US = 0; int TASK_REMOVE_SHAREABLE_FROM_LIST_SHARED_WITH_US = 1; int TASK_ADD_SHARED_SHAREABLE = 2; diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java index 8c5db480b7058bf63d4730a8f6d9c406c119fd49..100ab5dc48d13097e252b3cdb653afc3ebc5cc8f 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingManager.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java @@ -3,8 +3,6 @@ package org.briarproject.api.sharing; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DbException; -import org.briarproject.api.forum.Forum; -import org.briarproject.api.forum.ForumInvitationMessage; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; @@ -16,7 +14,7 @@ public interface SharingManager<S extends Shareable, IM extends InvitationMessag ClientId getClientId(); /** - * Sends an invitation to share the given group with the given contact + * Sends an invitation to share the given shareable with the given contact * and sends an optional message along with it. */ void sendInvitation(GroupId groupId, ContactId contactId, @@ -35,8 +33,8 @@ public interface SharingManager<S extends Shareable, IM extends InvitationMessag Collection<IM> getInvitationMessages( ContactId contactId) throws DbException; - /** Returns all groups to which the user could subscribe. */ - Collection<S> getAvailable() throws DbException; + /** Returns all shareables to which the user has been invited. */ + Collection<S> getInvited() throws DbException; /** Returns all contacts who are sharing the given group with us. */ Collection<Contact> getSharedBy(GroupId g) throws DbException; diff --git a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java index a25520fac0f49ffdcea490d227340bff041181cb..a8bd64b2aa57b19417fa58db52026ef883c3d830 100644 --- a/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/BlogSharingManagerImpl.java @@ -80,11 +80,6 @@ class BlogSharingManagerImpl extends return CLIENT_ID; } - @Override - protected ClientId getShareableClientId() { - return blogManager.getClientId(); - } - @Override protected BlogInvitationMessage createInvitationMessage(MessageId id, BlogInvitation msg, ContactId contactId, boolean available, diff --git a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java index eb06dff4ca6ade66985921d35bce2169bb64c56c..9cee6346369bf7605cf760c162e1deb28dc413ee 100644 --- a/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/ForumSharingManagerImpl.java @@ -78,11 +78,6 @@ class ForumSharingManagerImpl extends return CLIENT_ID; } - @Override - protected ClientId getShareableClientId() { - return forumManager.getClientId(); - } - @Override protected ForumInvitationMessage createInvitationMessage(MessageId id, ForumInvitation msg, ContactId contactId, boolean available, diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java index e4929fb7948d7a9d492782b15bd35223875c6896..9df89ceb6d5895323b8f0d9538ea7a749ea57d00 100644 --- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java @@ -81,6 +81,7 @@ import static org.briarproject.api.sharing.SharingConstants.TO_BE_SHARED_BY_US; import static org.briarproject.api.sharing.SharingConstants.TYPE; import static org.briarproject.api.sharing.SharingMessage.BaseMessage; import static org.briarproject.api.sharing.SharingMessage.Invitation; +import static org.briarproject.sharing.InviteeSessionState.State.AWAIT_LOCAL_RESPONSE; abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM extends InvitationMessage, IS extends InviteeSessionState, SS extends SharerSessionState, IR extends InvitationReceivedEvent, IRR extends InvitationResponseReceivedEvent> extends BdfIncomingMessageHook @@ -116,8 +117,6 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM public abstract ClientId getClientId(); - protected abstract ClientId getShareableClientId(); - protected abstract IM createInvitationMessage(MessageId id, I msg, ContactId contactId, boolean available, long time, boolean local, boolean sent, boolean seen, boolean read); @@ -331,7 +330,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM new BdfEntry(TYPE, SHARE_MSG_TYPE_INVITATION) ); - Transaction txn = db.startTransaction(false); + Transaction txn = db.startTransaction(true); try { Contact contact = db.getContact(txn, contactId); Group group = getContactGroup(contact); @@ -356,7 +355,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM if (!(s instanceof InviteeSessionState)) continue; available = ((InviteeSessionState) s).getState() == - InviteeSessionState.State.AWAIT_LOCAL_RESPONSE; + AWAIT_LOCAL_RESPONSE; } IM im = createInvitationMessage(m.getKey(), msg, contactId, available, time, local, status.isSent(), @@ -377,32 +376,52 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM } @Override - public Collection<S> getAvailable() throws DbException { + public Collection<S> getInvited() throws DbException { + Transaction txn = db.startTransaction(true); try { - Set<S> available = new HashSet<S>(); - Transaction txn = db.startTransaction(true); + Set<S> invited = new HashSet<S>(); + Collection<Contact> contacts = db.getContacts(txn); + for (Contact contact : contacts) { + invited.addAll(getInvited(txn, contact)); + } + txn.setComplete(); + return Collections.unmodifiableCollection(invited); + } catch (FormatException e) { + throw new DbException(e); + } finally { + db.endTransaction(txn); + } + } + + private Collection<S> getInvited(Transaction txn, Contact contact) + throws DbException, FormatException { + + // query for all external invitations + BdfDictionary query = BdfDictionary.of( + new BdfEntry(TYPE, SHARE_MSG_TYPE_INVITATION), + new BdfEntry(LOCAL, false) + ); + Group group = getContactGroup(contact); + + Set<S> invited = new HashSet<S>(); + Map<MessageId, BdfDictionary> map = clientHelper + .getMessageMetadataAsDictionary(txn, group.getId(), query); + for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { + BdfDictionary d = m.getValue(); try { - // Get any shareables we subscribe to - Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn, - getShareableClientId())); - // Get all shareables shared by contacts - for (Contact c : db.getContacts(txn)) { - Group g = getContactGroup(c); - List<S> shareables = - getShareableList(txn, g.getId(), SHARED_WITH_US); - for (S f : shareables) { - if (!subscribed.contains(f.getGroup())) - available.add(f); - } + I msg = getIFactory().build(group.getId(), d); + IS iss = (IS) getSessionState(txn, msg.getSessionId(), true); + // get and add the shareable if the invitation is unanswered + if (iss.getState().equals(AWAIT_LOCAL_RESPONSE)) { + S s = getSFactory().parse(iss); + invited.add(s); } - txn.setComplete(); - } finally { - db.endTransaction(txn); + } catch (FormatException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); } - return Collections.unmodifiableSet(available); - } catch (IOException e) { - throw new DbException(e); } + return invited; } @Override @@ -473,7 +492,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM } } - protected void removingShareable(Transaction txn, S f) throws DbException { + void removingShareable(Transaction txn, S f) throws DbException { try { for (Contact c : db.getContacts(txn)) { GroupId g = getContactGroup(c).getId(); @@ -642,9 +661,7 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM new BdfEntry(IS_SHARER, false), new BdfEntry(CONTACT_ID, c.getId().getInt()), new BdfEntry(SHAREABLE_ID, f.getId()), - new BdfEntry(STATE, - InviteeSessionState.State.AWAIT_LOCAL_RESPONSE - .getValue()) + new BdfEntry(STATE, AWAIT_LOCAL_RESPONSE.getValue()) ); Map<MessageId, BdfDictionary> map = clientHelper