diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 9923043643e8983be4adc12d72877df2e5337b7b..9394f9d02ee249fc0bf9495e4f00df3fc6cb343b 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -32,6 +32,7 @@ import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.blog.BlogManager; import org.briarproject.briar.api.blog.BlogPostFactory; import org.briarproject.briar.api.blog.BlogSharingManager; +import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.feed.FeedManager; import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumSharingManager; @@ -78,6 +79,8 @@ public interface AndroidComponent @DatabaseExecutor Executor databaseExecutor(); + MessageTracker messageTracker(); + LifecycleManager lifecycleManager(); IdentityManager identityManager(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/controller/DbControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/controller/DbControllerImpl.java index eaeb7a06ec1c15fbc58e42a2d27c03bb8124eda8..6ae01b8a53ad11cac4857179ccf52f41c9b577b2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/controller/DbControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/controller/DbControllerImpl.java @@ -17,7 +17,7 @@ public class DbControllerImpl implements DbController { private static final Logger LOG = Logger.getLogger(DbControllerImpl.class.getName()); - private final Executor dbExecutor; + protected final Executor dbExecutor; private final LifecycleManager lifecycleManager; @Inject diff --git a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java index 4c52f6154c90103da580c4206ffc2dd3b4a882fb..8d97e7810063d4a4d20209a469220b5a2b7e8f47 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/forum/ForumControllerImpl.java @@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.forum.ForumController.ForumListener; import org.briarproject.briar.android.threaded.ThreadListControllerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.ForumInvitationResponse; @@ -55,10 +56,10 @@ class ForumControllerImpl extends LifecycleManager lifecycleManager, IdentityManager identityManager, @CryptoExecutor Executor cryptoExecutor, ForumManager forumManager, ForumSharingManager forumSharingManager, - EventBus eventBus, Clock clock, + EventBus eventBus, Clock clock, MessageTracker messageTracker, AndroidNotificationManager notificationManager) { super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, clock, notificationManager); + eventBus, clock, notificationManager, messageTracker); this.forumManager = forumManager; this.forumSharingManager = forumSharingManager; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java index 0c75305591d4220540a76c85683760b273b31d76..db6029e57c002ad2f8365ee7cc915670b4e7c386 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/privategroup/conversation/GroupControllerImpl.java @@ -17,6 +17,7 @@ import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.privategroup.conversation.GroupController.GroupListener; import org.briarproject.briar.android.threaded.ThreadListControllerImpl; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.privategroup.GroupMember; import org.briarproject.briar.api.privategroup.GroupMessage; @@ -60,9 +61,10 @@ class GroupControllerImpl extends @CryptoExecutor Executor cryptoExecutor, PrivateGroupManager privateGroupManager, GroupMessageFactory groupMessageFactory, EventBus eventBus, - Clock clock, AndroidNotificationManager notificationManager) { + MessageTracker messageTracker, Clock clock, + AndroidNotificationManager notificationManager) { super(dbExecutor, lifecycleManager, identityManager, cryptoExecutor, - eventBus, clock, notificationManager); + eventBus, clock, notificationManager, messageTracker); this.privateGroupManager = privateGroupManager; this.groupMessageFactory = groupMessageFactory; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java index 72b03a03fc133340218e0f45ddb801446c9349db..1c10a8b3608939bccbed4fb33687b5e2290ce93e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemAdapter.java @@ -1,5 +1,6 @@ package org.briarproject.briar.android.threaded; +import android.os.Handler; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v7.widget.LinearLayoutManager; @@ -26,6 +27,7 @@ public class ThreadItemAdapter<I extends ThreadItem> protected final NestedTreeList<I> items = new NestedTreeList<>(); private final ThreadItemListener<I> listener; private final LinearLayoutManager layoutManager; + private final Handler handler = new Handler(); private volatile int revision = 0; @@ -64,6 +66,17 @@ public class ThreadItemAdapter<I extends ThreadItem> revision++; } + void setItemWithIdVisible(MessageId messageId) { + int pos = 0; + for (I item : items) { + if (item.getId().equals(messageId)) { + layoutManager.scrollToPosition(pos); + break; + } + pos++; + } + } + public void setItems(Collection<I> items) { this.items.clear(); this.items.addAll(items); @@ -144,7 +157,7 @@ public class ThreadItemAdapter<I extends ThreadItem> /** * Returns the position of the first unread item below the current viewport */ - public int getVisibleUnreadPosBottom() { + int getVisibleUnreadPosBottom() { final int positionBottom = layoutManager.findLastVisibleItemPosition(); if (positionBottom == NO_POSITION) return NO_POSITION; for (int i = positionBottom + 1; i < items.size(); i++) { @@ -156,7 +169,7 @@ public class ThreadItemAdapter<I extends ThreadItem> /** * Returns the position of the first unread item above the current viewport */ - public int getVisibleUnreadPosTop() { + int getVisibleUnreadPosTop() { final int positionTop = layoutManager.findFirstVisibleItemPosition(); int position = NO_POSITION; for (int i = 0; i < items.size(); i++) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemList.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemList.java new file mode 100644 index 0000000000000000000000000000000000000000..f517e8e9ccb5c359c3864afe3f05a26dba75cc7f --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemList.java @@ -0,0 +1,15 @@ +package org.briarproject.briar.android.threaded; + +import org.briarproject.bramble.api.sync.MessageId; + +import java.util.List; + +import javax.annotation.Nullable; + +public interface ThreadItemList<I extends ThreadItem> extends List<I> { + + @Nullable + MessageId getFirstVisibleItemId(); + + void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId); +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemListImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemListImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..2886607edce8eecc24a62075dfe07b385e21b94b --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadItemListImpl.java @@ -0,0 +1,22 @@ +package org.briarproject.briar.android.threaded; + +import org.briarproject.bramble.api.sync.MessageId; + +import java.util.ArrayList; + +import javax.annotation.Nullable; + +public class ThreadItemListImpl<I extends ThreadItem> extends ArrayList<I> + implements ThreadItemList<I> { + + private MessageId bottomVisibleItemId; + + @Override + public MessageId getFirstVisibleItemId() { + return bottomVisibleItemId; + } + + public void setFirstVisibleId(@Nullable MessageId bottomVisibleItemId) { + this.bottomVisibleItemId = bottomVisibleItemId; + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java index 8106be7edb5fc6b1cd50a9bbd5a7257f4984f4e0..fba82cc144d6a71eded33b37966306235f976818 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListActivity.java @@ -26,6 +26,7 @@ import org.briarproject.briar.android.controller.SharingController; import org.briarproject.briar.android.controller.SharingController.SharingListener; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener; +import org.briarproject.briar.android.threaded.ThreadListController.ThreadListDataSource; import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener; import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.TextInputView; @@ -51,7 +52,7 @@ import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCo public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadItemAdapter<I>, I extends ThreadItem, H extends PostHeader> extends BriarActivity implements ThreadListListener<H>, TextInputListener, SharingListener, - ThreadItemListener<I> { + ThreadItemListener<I>, ThreadListDataSource { protected static final String KEY_REPLY_ID = "replyId"; @@ -68,6 +69,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI private MessageId replyId; protected abstract ThreadListController<G, I, H> getController(); + @Inject protected SharingController sharingController; @@ -104,6 +106,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI updateUnreadCount(); } } + @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { @@ -144,6 +147,18 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI loadSharingContacts(); } + @Override + @Nullable + public MessageId getFirstVisibleMessageId() { + if (layoutManager != null && adapter != null) { + int position = + layoutManager.findFirstVisibleItemPosition(); + I i = adapter.getItemAt(position); + return i == null ? null : i.getId(); + } + return null; + } + protected abstract A createAdapter(LinearLayoutManager layoutManager); protected void loadNamedGroup() { @@ -167,16 +182,16 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI protected void loadItems() { final int revision = adapter.getRevision(); getController().loadItems( - new UiResultExceptionHandler<Collection<I>, DbException>(this) { + new UiResultExceptionHandler<ThreadItemList<I>, DbException>( + this) { @Override - public void onResultUi(Collection<I> items) { + public void onResultUi(ThreadItemList<I> items) { if (revision == adapter.getRevision()) { adapter.incrementRevision(); if (items.isEmpty()) { list.showData(); } else { - adapter.setItems(items); - list.showData(); + initList(items); updateTextInput(replyId); } } else { @@ -192,6 +207,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI }); } + private void initList(final ThreadItemList<I> items) { + adapter.setItems(items); + MessageId messageId = items.getFirstVisibleItemId(); + if (messageId != null) + adapter.setItemWithIdVisible(messageId); + updateUnreadCount(); + list.showData(); + } + protected void loadSharingContacts() { getController().loadSharingContacts( new UiResultExceptionHandler<Collection<ContactId>, DbException>( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java index caebd1ec65d53dad3c590e0d1cf287703073e0d2..d39c8b453a6c1c5852d9c56663edd1d7ca0fe4fa 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListController.java @@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.android.DestroyableContext; import org.briarproject.briar.android.controller.ActivityLifecycleController; import org.briarproject.briar.android.controller.handler.ExceptionHandler; @@ -30,7 +31,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem void loadItem(H header, ResultExceptionHandler<I, DbException> handler); - void loadItems(ResultExceptionHandler<Collection<I>, DbException> handler); + void loadItems(ResultExceptionHandler<ThreadItemList<I>, DbException> handler); void markItemRead(I item); @@ -41,7 +42,7 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem void deleteNamedGroup(ExceptionHandler<DbException> handler); - interface ThreadListListener<H> extends DestroyableContext { + interface ThreadListListener<H> extends ThreadListDataSource { @UiThread void onHeaderReceived(H header); @@ -52,4 +53,10 @@ public interface ThreadListController<G extends NamedGroup, I extends ThreadItem void onInvitationAccepted(ContactId c); } + interface ThreadListDataSource extends DestroyableContext { + + @UiThread @Nullable + MessageId getFirstVisibleMessageId(); + } + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java index a2db5242d9cd8bba435fa1cd50bf28d0e79e8a5e..0793d785adf6ec8744748cf8df28175da1df766a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/threaded/ThreadListControllerImpl.java @@ -22,14 +22,13 @@ import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadListController.ThreadListListener; import org.briarproject.briar.api.android.AndroidNotificationManager; +import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.NamedGroup; import org.briarproject.briar.api.client.PostHeader; import org.briarproject.briar.api.client.ThreadedMessage; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -55,18 +54,21 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T protected final AndroidNotificationManager notificationManager; protected final Executor cryptoExecutor; protected final Clock clock; + private final MessageTracker messageTracker; protected volatile L listener; protected ThreadListControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, IdentityManager identityManager, @CryptoExecutor Executor cryptoExecutor, EventBus eventBus, - Clock clock, AndroidNotificationManager notificationManager) { + Clock clock, AndroidNotificationManager notificationManager, + MessageTracker messageTracker) { super(dbExecutor, lifecycleManager); this.identityManager = identityManager; this.cryptoExecutor = cryptoExecutor; this.notificationManager = notificationManager; this.clock = clock; this.eventBus = eventBus; + this.messageTracker = messageTracker; } @Override @@ -97,6 +99,19 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T @Override public void onActivityDestroy() { + dbExecutor.execute(new Runnable() { + @Override + public void run() { + try { + messageTracker + .storeMessageId(groupId, + listener.getFirstVisibleMessageId()); + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + } + }); } @CallSuper @@ -144,7 +159,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T @Override public void loadItems( - final ResultExceptionHandler<Collection<I>, DbException> handler) { + final ResultExceptionHandler<ThreadItemList<I>, DbException> handler) { checkGroupId(); runOnDbThread(new Runnable() { @Override @@ -293,11 +308,16 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T @DatabaseExecutor protected abstract void deleteNamedGroup(G groupItem) throws DbException; - private List<I> buildItems(Collection<H> headers) { - List<I> items = new ArrayList<>(); + private ThreadItemList<I> buildItems(Collection<H> headers) + throws DbException { + ThreadItemList<I> items = new ThreadItemListImpl<>(); for (H h : headers) { items.add(buildItem(h, bodyCache.get(h.getId()))); } + MessageId msgId = messageTracker.loadStoredMessageId(groupId); + if (LOG.isLoggable(INFO)) + LOG.info("Loaded last top visible message id " + msgId); + items.setFirstVisibleId(msgId); return items; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/view/UnreadMessageButton.java b/briar-android/src/main/java/org/briarproject/briar/android/view/UnreadMessageButton.java index e7f4fa92f5387377a72ea82ba02f7b0d1b3ae9bf..4a5360043e21128e68b15c276db786f1d7c01d14 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/view/UnreadMessageButton.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/view/UnreadMessageButton.java @@ -36,8 +36,7 @@ public class UnreadMessageButton extends FrameLayout { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater - .inflate(R.layout.unread_message_button, this, true); + inflater.inflate(R.layout.unread_message_button, this, true); fab = (FloatingActionButton) findViewById(R.id.fab); unread = (TextView) findViewById(R.id.unreadCountView); @@ -64,15 +63,11 @@ public class UnreadMessageButton extends FrameLayout { public void setUnreadCount(int count) { if (count == 0) { - fab.setVisibility(GONE); -// fab.hide(); - unread.setVisibility(GONE); + setVisibility(INVISIBLE); } else { // FIXME: Use animations when upgrading to support library 24.2.0 // https://code.google.com/p/android/issues/detail?id=216469 - fab.setVisibility(VISIBLE); -// if (!fab.isShown()) fab.show(); - unread.setVisibility(VISIBLE); + setVisibility(VISIBLE); unread.setText(String.valueOf(count)); } } diff --git a/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java b/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java index 8a3461c2eb1c4472cbf86a1d20db8ce08f06e8ff..96ffc88eb15de617846e5c5aba9d4c6242651f8d 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java @@ -13,6 +13,8 @@ import org.briarproject.briar.BuildConfig; import org.briarproject.briar.android.TestBriarApplication; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.threaded.ThreadItemAdapter; +import org.briarproject.briar.android.threaded.ThreadItemList; +import org.briarproject.briar.android.threaded.ThreadItemListImpl; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -23,10 +25,7 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.annotation.Config; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.List; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; @@ -81,7 +80,7 @@ public class ForumActivityTest { private TestForumActivity forumActivity; @Captor - private ArgumentCaptor<UiResultExceptionHandler<Collection<ForumItem>, DbException>> + private ArgumentCaptor<UiResultExceptionHandler<ThreadItemList<ForumItem>, DbException>> rc; @Before @@ -93,7 +92,7 @@ public class ForumActivityTest { .withIntent(intent).create().resume().get(); } - private List<ForumItem> getDummyData() { + private ThreadItemList<ForumItem> getDummyData() { ForumItem[] forumItems = new ForumItem[6]; for (int i = 0; i < forumItems.length; i++) { AuthorId authorId = new AuthorId(TestUtils.getRandomId()); @@ -103,13 +102,15 @@ public class ForumActivityTest { AUTHORS[i], System.currentTimeMillis(), author, UNKNOWN); forumItems[i].setLevel(LEVELS[i]); } - return new ArrayList<>(Arrays.asList(forumItems)); + ThreadItemList<ForumItem> list = new ThreadItemListImpl<>(); + list.addAll(Arrays.asList(forumItems)); + return list; } @Test public void testNestedEntries() { ForumController mc = forumActivity.getController(); - List<ForumItem> dummyData = getDummyData(); + ThreadItemList<ForumItem> dummyData = getDummyData(); verify(mc, times(1)).loadItems(rc.capture()); rc.getValue().onResult(dummyData); ThreadItemAdapter<ForumItem> adapter = forumActivity.getAdapter(); diff --git a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTracker.java b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTracker.java index 44c26fef4d583fc947e5e59e35954513c5ec3d98..5d6418f0aceaf17b46714509ec96c881187a31f1 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTracker.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/client/MessageTracker.java @@ -7,6 +7,8 @@ import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; +import javax.annotation.Nullable; + @NotNullByDefault public interface MessageTracker { @@ -38,6 +40,19 @@ public interface MessageTracker { void trackMessage(Transaction txn, GroupId g, long timestamp, boolean read) throws DbException; + /** + * Loads the stored message id for the respective group id or returns null + * if none is available. + */ + @Nullable + MessageId loadStoredMessageId(GroupId g) throws DbException; + + /** + * Stores the message id for the respective group id. Exactly one message id + * can be stored for any group id at any time, older values are overwritten. + */ + void storeMessageId(GroupId g, MessageId m) throws DbException; + /** * Marks a message as read or unread and updates the group count. */ diff --git a/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerConstants.java b/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerConstants.java index ca689fc1ca2543ecb6ae80e922831233a9daf319..81a1d603bfedd505c1b10f3ec4d321d862c27067 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerConstants.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerConstants.java @@ -2,6 +2,7 @@ package org.briarproject.briar.client; public interface MessageTrackerConstants { + String GROUP_KEY_STORED_MESSAGE_ID = "storedMessageId"; String GROUP_KEY_MSG_COUNT = "messageCount"; String GROUP_KEY_UNREAD_COUNT = "unreadCount"; String GROUP_KEY_LATEST_MSG = "latestMessageTime"; diff --git a/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerImpl.java index bd230dca8ea0ab8efda10cc9f1c6744a0186bbda..18e034027c906507119141a3b5e99993f9f7429d 100644 --- a/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/client/MessageTrackerImpl.java @@ -13,11 +13,13 @@ import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.briar.api.client.MessageTracker; +import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import javax.inject.Inject; import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_LATEST_MSG; import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_MSG_COUNT; +import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_STORED_MESSAGE_ID; import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_UNREAD_COUNT; import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ; @@ -57,6 +59,30 @@ class MessageTrackerImpl implements MessageTracker { latestMsgTime)); } + @Nullable + @Override + public MessageId loadStoredMessageId(GroupId g) throws DbException { + try { + BdfDictionary d = clientHelper.getGroupMetadataAsDictionary(g); + byte[] msgBytes = d.getOptionalRaw(GROUP_KEY_STORED_MESSAGE_ID); + return msgBytes != null ? new MessageId(msgBytes) : null; + } catch (FormatException e) { + throw new DbException(e); + } + } + + @Override + public void storeMessageId(GroupId g, MessageId m) throws DbException { + BdfDictionary d = BdfDictionary.of( + new BdfEntry(GROUP_KEY_STORED_MESSAGE_ID, m) + ); + try { + clientHelper.mergeGroupMetadata(g, d); + } catch (FormatException e) { + throw new DbException(e); + } + } + @Override public GroupCount getGroupCount(GroupId g) throws DbException { GroupCount count; diff --git a/briar-core/src/test/java/org/briarproject/briar/client/MessageTrackerTest.java b/briar-core/src/test/java/org/briarproject/briar/client/MessageTrackerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a7e8603202c358a92e625380282fbba3e35d619e --- /dev/null +++ b/briar-core/src/test/java/org/briarproject/briar/client/MessageTrackerTest.java @@ -0,0 +1,50 @@ +package org.briarproject.briar.client; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.sync.GroupId; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.briarproject.bramble.test.TestUtils; +import org.briarproject.briar.api.client.MessageTracker; +import org.jmock.Expectations; +import org.junit.Assert; +import org.junit.Test; + +import static org.briarproject.briar.client.MessageTrackerConstants.GROUP_KEY_STORED_MESSAGE_ID; + +public class MessageTrackerTest extends BrambleMockTestCase { + + protected final GroupId groupId = new GroupId(TestUtils.getRandomId()); + protected final ClientHelper clientHelper = + context.mock(ClientHelper.class); + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final MessageId messageId = new MessageId(TestUtils.getRandomId()); + private final MessageTracker messageTracker = + new MessageTrackerImpl(db, clientHelper); + private final BdfDictionary dictionary = BdfDictionary.of( + new BdfEntry(GROUP_KEY_STORED_MESSAGE_ID, messageId) + ); + + @Test + public void testMessageStore() throws Exception { + context.checking(new Expectations() {{ + oneOf(clientHelper).mergeGroupMetadata(groupId, dictionary); + }}); + messageTracker.storeMessageId(groupId, messageId); + } + + @Test + public void testMessageLoad() throws Exception { + context.checking(new Expectations() {{ + oneOf(clientHelper).getGroupMetadataAsDictionary(groupId); + will(returnValue(dictionary)); + }}); + MessageId loadedId = messageTracker.loadStoredMessageId(groupId); + Assert.assertNotNull(loadedId); + Assert.assertTrue(messageId.equals(loadedId)); + } + +}