diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java index eb655209108c6a290394c31c3fc3241d4f711c12..d70bdb14e6d9ddfd550af069c35ac1d41777292f 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java @@ -32,6 +32,7 @@ import org.briarproject.api.identity.Author; import org.briarproject.api.sync.GroupId; import java.util.Collection; +import java.util.logging.Logger; import javax.inject.Inject; @@ -51,6 +52,7 @@ public class BlogFragment extends BaseFragment implements OnBlogPostAddedListener { public final static String TAG = BlogFragment.class.getName(); + private static final Logger LOG = Logger.getLogger(TAG); @Inject BlogController blogController; @@ -207,6 +209,7 @@ public class BlogFragment extends BaseFragment implements listener) { @Override public void onResultUi(BlogPostItem post) { + adapter.incrementRevision(); adapter.add(post); if (local) { list.scrollToPosition(0); @@ -228,16 +231,23 @@ public class BlogFragment extends BaseFragment implements } void loadBlogPosts(final boolean reload) { + final int revision = adapter.getRevision(); blogController.loadBlogPosts( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( listener) { @Override public void onResultUi(Collection<BlogPostItem> posts) { - if (posts.isEmpty()) { - list.showData(); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (posts.isEmpty()) { + list.showData(); + } else { + adapter.addAll(posts); + if (reload) list.scrollToPosition(0); + } } else { - adapter.addAll(posts); - if (reload) list.scrollToPosition(0); + LOG.info("Concurrent update, reloading"); + loadBlogPosts(reload); } } diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java index 6f960d3c128a310de26d699dbe8919c7f910c100..cf9784160dfb6fbf195a27dc82f9bfc1075999f9 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java @@ -27,6 +27,7 @@ import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DbException; import java.util.Collection; +import java.util.logging.Logger; import javax.inject.Inject; @@ -41,6 +42,7 @@ public class FeedFragment extends BaseFragment implements OnBlogPostClickListener, OnBlogPostAddedListener { public final static String TAG = FeedFragment.class.getName(); + private static final Logger LOG = Logger.getLogger(TAG); @Inject FeedController feedController; @@ -129,14 +131,21 @@ public class FeedFragment extends BaseFragment implements } private void loadBlogPosts(final boolean clear) { + final int revision = adapter.getRevision(); feedController.loadBlogPosts( new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>( listener) { @Override public void onResultUi(Collection<BlogPostItem> posts) { - if (clear) adapter.setItems(posts); - else adapter.addAll(posts); - if (posts.isEmpty()) list.showData(); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (clear) adapter.setItems(posts); + else adapter.addAll(posts); + if (posts.isEmpty()) list.showData(); + } else { + LOG.info("Concurrent update, reloading"); + loadBlogPosts(clear); + } } @Override @@ -193,6 +202,7 @@ public class FeedFragment extends BaseFragment implements listener) { @Override public void onResultUi(BlogPostItem post) { + adapter.incrementRevision(); adapter.add(post); if (local) { showSnackBar(R.string.blogs_blog_post_created); diff --git a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java index 0eb6df792e525221d74becf993e0ba3a20665cde..ecffd0cd2007c7349b7a3766685cf93434909ef8 100644 --- a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java @@ -125,27 +125,43 @@ public class RssFeedManageActivity extends BriarActivity } private void loadFeeds() { + final int revision = adapter.getRevision(); runOnDbThread(new Runnable() { @Override public void run() { try { - addFeeds(feedManager.getFeeds()); + displayFeeds(revision, feedManager.getFeeds()); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - list.setEmptyText(R.string.blogs_rss_feeds_manage_error); - list.showData(); + onLoadError(); } } }); } - private void addFeeds(final List<Feed> feeds) { + private void displayFeeds(final int revision, final List<Feed> feeds) { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - if (feeds.isEmpty()) list.showData(); - else adapter.addAll(feeds); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (feeds.isEmpty()) list.showData(); + else adapter.addAll(feeds); + } else { + LOG.info("Concurrent update, reloading"); + loadFeeds(); + } + } + }); + } + + private void onLoadError() { + runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + list.setEmptyText(R.string.blogs_rss_feeds_manage_error); + list.showData(); } }); } @@ -154,6 +170,7 @@ public class RssFeedManageActivity extends BriarActivity runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); adapter.remove(feed); } }); diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index d640439a68697a7da96bf0ea3aacc48eda1adb0f..474b2cc2b7b7b7f854541eb1128776378b266679 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -190,6 +190,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { } private void loadContacts() { + final int revision = adapter.getRevision(); listener.runOnDbThread(new Runnable() { @Override public void run() { @@ -216,7 +217,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Full load took " + duration + " ms"); - displayContacts(contacts); + displayContacts(revision, contacts); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -225,12 +226,19 @@ public class ContactListFragment extends BaseFragment implements EventListener { }); } - private void displayContacts(final List<ContactListItem> contacts) { + private void displayContacts(final int revision, + final List<ContactListItem> contacts) { listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - if (contacts.isEmpty()) list.showData(); - else adapter.addAll(contacts); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (contacts.isEmpty()) list.showData(); + else adapter.addAll(contacts); + } else { + LOG.info("Concurrent update, reloading"); + loadContacts(); + } } }); } @@ -288,6 +296,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); int position = adapter.findItemPosition(c); ContactListItem item = adapter.getItemAt(position); if (item != null) { @@ -302,6 +311,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); int position = adapter.findItemPosition(c); ContactListItem item = adapter.getItemAt(position); if (item != null) adapter.remove(item); @@ -313,6 +323,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); int position = adapter.findItemPosition(c); ContactListItem item = adapter.getItemAt(position); if (item != null) { diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index fd948d0aadca0ff38cc02730bbe007fe4d5d01d2..c92c5195121ea976f4b64d5a1f0350da3daf1457 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -289,12 +289,10 @@ public class ConversationActivity extends BriarActivity contactIdenticonKey = contact.getAuthor().getId().getBytes(); } - boolean connected = - connectionRegistry.isConnected(contactId); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading contact took " + duration + " ms"); - displayContactDetails(connected); + displayContactDetails(); } catch (NoSuchContactException e) { finishOnUiThread(); } catch (DbException e) { @@ -305,7 +303,7 @@ public class ConversationActivity extends BriarActivity }); } - private void displayContactDetails(final boolean connected) { + private void displayContactDetails() { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { @@ -313,7 +311,7 @@ public class ConversationActivity extends BriarActivity new IdenticonDrawable(contactIdenticonKey)); toolbarTitle.setText(contactName); - if (connected) { + if (connectionRegistry.isConnected(contactId)) { toolbarStatus.setImageDrawable(ContextCompat .getDrawable(ConversationActivity.this, R.drawable.contact_online)); @@ -332,6 +330,7 @@ public class ConversationActivity extends BriarActivity } private void loadMessages() { + final int revision = adapter.getRevision(); runOnDbThread(new Runnable() { @Override public void run() { @@ -357,7 +356,8 @@ public class ConversationActivity extends BriarActivity long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading messages took " + duration + " ms"); - displayMessages(headers, introductions, invitations); + displayMessages(revision, headers, introductions, + invitations); } catch (NoSuchContactException e) { finishOnUiThread(); } catch (DbException e) { @@ -368,56 +368,66 @@ public class ConversationActivity extends BriarActivity }); } - private void displayMessages(final Collection<PrivateMessageHeader> headers, + private void displayMessages(final int revision, + final Collection<PrivateMessageHeader> headers, final Collection<IntroductionMessage> introductions, final Collection<InvitationMessage> invitations) { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - textInputView.setSendButtonEnabled(true); - int size = headers.size() + introductions.size() + - invitations.size(); - if (size == 0) { - list.showData(); - } else { - List<ConversationItem> items = new ArrayList<>(size); - for (PrivateMessageHeader h : headers) { - ConversationMessageItem item = ConversationItem.from(h); - byte[] body = bodyCache.get(h.getId()); - if (body == null) loadMessageBody(h.getId()); - else item.setBody(body); - items.add(item); - } - for (IntroductionMessage im : introductions) { - if (im instanceof IntroductionRequest) { - IntroductionRequest ir = (IntroductionRequest) im; - items.add(ConversationItem.from(ir)); - } else { - IntroductionResponse ir = (IntroductionResponse) im; - items.add(ConversationItem - .from(ConversationActivity.this, - contactName, ir)); - } - } - for (InvitationMessage im : invitations) { - if (im instanceof InvitationRequest) { - InvitationRequest ir = (InvitationRequest) im; - items.add(ConversationItem.from(ir)); - } else if (im instanceof InvitationResponse) { - InvitationResponse ir = (InvitationResponse) im; - items.add(ConversationItem - .from(ConversationActivity.this, - contactName, ir)); - } - } - adapter.addAll(items); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + textInputView.setSendButtonEnabled(true); + List<ConversationItem> items = createItems(headers, + introductions, invitations); + if (items.isEmpty()) list.showData(); + else adapter.addAll(items); // Scroll to the bottom list.scrollToPosition(adapter.getItemCount() - 1); + } else { + LOG.info("Concurrent update, reloading"); + loadMessages(); } } }); } + private List<ConversationItem> createItems( + Collection<PrivateMessageHeader> headers, + Collection<IntroductionMessage> introductions, + Collection<InvitationMessage> invitations) { + int size = headers.size() + introductions.size() + invitations.size(); + List<ConversationItem> items = new ArrayList<>(size); + for (PrivateMessageHeader h : headers) { + ConversationMessageItem item = ConversationItem.from(h); + byte[] body = bodyCache.get(h.getId()); + if (body == null) loadMessageBody(h.getId()); + else item.setBody(body); + items.add(item); + } + for (IntroductionMessage im : introductions) { + if (im instanceof IntroductionRequest) { + IntroductionRequest ir = (IntroductionRequest) im; + items.add(ConversationItem.from(ir)); + } else { + IntroductionResponse ir = (IntroductionResponse) im; + items.add(ConversationItem.from(ConversationActivity.this, + contactName, ir)); + } + } + for (InvitationMessage im : invitations) { + if (im instanceof InvitationRequest) { + InvitationRequest ir = (InvitationRequest) im; + items.add(ConversationItem.from(ir)); + } else if (im instanceof InvitationResponse) { + InvitationResponse ir = (InvitationResponse) im; + items.add(ConversationItem.from(ConversationActivity.this, + contactName, ir)); + } + } + return items; + } + private void loadMessageBody(final MessageId m) { runOnDbThread(new Runnable() { @Override @@ -461,6 +471,7 @@ public class ConversationActivity extends BriarActivity runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); adapter.add(item); // Scroll to the bottom list.scrollToPosition(adapter.getItemCount() - 1); @@ -535,13 +546,13 @@ public class ConversationActivity extends BriarActivity ContactConnectedEvent c = (ContactConnectedEvent) e; if (c.getContactId().equals(contactId)) { LOG.info("Contact connected"); - displayContactDetails(true); + displayContactDetails(); } } else if (e instanceof ContactDisconnectedEvent) { ContactDisconnectedEvent c = (ContactDisconnectedEvent) e; if (c.getContactId().equals(contactId)) { LOG.info("Contact disconnected"); - displayContactDetails(false); + displayContactDetails(); } } else if (e instanceof IntroductionRequestReceivedEvent) { IntroductionRequestReceivedEvent event = @@ -589,6 +600,7 @@ public class ConversationActivity extends BriarActivity runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); Set<MessageId> messages = new HashSet<>(messageIds); SparseArray<OutgoingItem> list = adapter.getOutgoingMessages(); for (int i = 0; i < list.size(); i++) { @@ -807,13 +819,11 @@ public class ConversationActivity extends BriarActivity timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); try { if (accept) { - introductionManager - .acceptIntroduction(contactId, sessionId, - timestamp); + introductionManager.acceptIntroduction(contactId, + sessionId, timestamp); } else { - introductionManager - .declineIntroduction(contactId, sessionId, - timestamp); + introductionManager.declineIntroduction(contactId, + sessionId, timestamp); } loadMessages(); } catch (DbException | FormatException e) { diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 03ebdf438e960042e49012b49a0c3f3e9ce53eca..12487efc946fa28c551df033daf29f9a76920687 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -147,7 +147,7 @@ public class ForumActivity extends private void showUnsubscribeDialog() { OnClickListener okListener = new OnClickListener() { @Override - public void onClick(final DialogInterface dialog, int which) { + public void onClick(DialogInterface dialog, int which) { deleteNamedGroup(); } }; @@ -162,8 +162,7 @@ public class ForumActivity extends private void deleteNamedGroup() { forumController.deleteNamedGroup( - new UiResultExceptionHandler<Void, DbException>( - ForumActivity.this) { + new UiResultExceptionHandler<Void, DbException>(this) { @Override public void onResultUi(Void v) { Toast.makeText(ForumActivity.this, diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java index 0edb1a0755a26c801fc92d2aca220008257835ae..6bbf1965776b3ea3c90c7eccce1b2d661d89282c 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java @@ -150,6 +150,7 @@ public class ForumListFragment extends BaseEventFragment implements } private void loadForums() { + final int revision = adapter.getRevision(); listener.runOnDbThread(new Runnable() { @Override public void run() { @@ -168,7 +169,7 @@ public class ForumListFragment extends BaseEventFragment implements long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Full load took " + duration + " ms"); - displayForums(forums); + displayForums(revision, forums); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -177,12 +178,19 @@ public class ForumListFragment extends BaseEventFragment implements }); } - private void displayForums(final Collection<ForumListItem> forums) { + private void displayForums(final int revision, + final Collection<ForumListItem> forums) { listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - if (forums.isEmpty()) list.showData(); - else adapter.addAll(forums); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (forums.isEmpty()) list.showData(); + else adapter.addAll(forums); + } else { + LOG.info("Concurrent update, reloading"); + loadForums(); + } } }); } @@ -254,6 +262,7 @@ public class ForumListFragment extends BaseEventFragment implements listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); int position = adapter.findItemPosition(g); ForumListItem item = adapter.getItemAt(position); if (item != null) { @@ -268,6 +277,7 @@ public class ForumListFragment extends BaseEventFragment implements listener.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { + adapter.incrementRevision(); int position = adapter.findItemPosition(g); ForumListItem item = adapter.getItemAt(position); if (item != null) adapter.remove(item); diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java index a786e766c98c70ba1b1456afab2eca3de94548f2..551c8f30bda68e50c95a1b510c2a08d03e08af64 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java @@ -23,6 +23,7 @@ import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.sync.GroupId; import java.util.Collection; +import java.util.logging.Logger; import javax.inject.Inject; @@ -30,6 +31,7 @@ public class GroupListFragment extends BaseFragment implements GroupListListener, OnGroupRemoveClickListener { public final static String TAG = GroupListFragment.class.getName(); + private static final Logger LOG = Logger.getLogger(TAG); public static GroupListFragment newInstance() { return new GroupListFragment(); @@ -120,6 +122,7 @@ public class GroupListFragment extends BaseFragment implements @UiThread @Override public void onGroupMessageAdded(GroupMessageHeader header) { + adapter.incrementRevision(); int position = adapter.findItemPosition(header.getGroupId()); GroupItem item = adapter.getItemAt(position); if (item != null) { @@ -137,6 +140,7 @@ public class GroupListFragment extends BaseFragment implements @UiThread @Override public void onGroupRemoved(GroupId groupId) { + adapter.incrementRevision(); adapter.removeItem(groupId); } @@ -146,13 +150,20 @@ public class GroupListFragment extends BaseFragment implements } private void loadGroups() { + final int revision = adapter.getRevision(); controller.loadGroups( new UiResultExceptionHandler<Collection<GroupItem>, DbException>( listener) { @Override public void onResultUi(Collection<GroupItem> groups) { - if (groups.isEmpty()) list.showData(); - else adapter.addAll(groups); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (groups.isEmpty()) list.showData(); + else adapter.addAll(groups); + } else { + LOG.info("Concurrent update, reloading"); + loadGroups(); + } } @Override diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java index fc691ac2b0f725854e3adb70a7caa7202de55b59..772be9bf605e7dd472ffb152a2f395b360147988 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java @@ -29,7 +29,7 @@ abstract class InvitationsActivity extends BriarActivity protected static final Logger LOG = Logger.getLogger(InvitationsActivity.class.getName()); - private InvitationAdapter adapter; + protected InvitationAdapter adapter; private BriarRecyclerView list; @Inject @@ -84,6 +84,7 @@ abstract class InvitationsActivity extends BriarActivity Toast.makeText(this, res, LENGTH_SHORT).show(); // remove item and finish if it was the last + adapter.incrementRevision(); adapter.remove(item); if (adapter.getItemCount() == 0) { supportFinishAfterTransition(); @@ -102,7 +103,7 @@ abstract class InvitationsActivity extends BriarActivity abstract protected int getDeclineRes(); - protected void displayInvitations( + protected void displayInvitations(final int revision, final Collection<InvitationItem> invitations, final boolean clear) { runOnUiThreadUnlessDestroyed(new Runnable() { @Override @@ -110,9 +111,13 @@ abstract class InvitationsActivity extends BriarActivity if (invitations.isEmpty()) { LOG.info("No more invitations available, finishing"); finish(); - } else { + } else if (revision == adapter.getRevision()) { + adapter.incrementRevision(); if (clear) adapter.setItems(invitations); else adapter.addAll(invitations); + } else { + LOG.info("Concurrent update, reloading"); + loadInvitations(clear); } } }); diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java index 014391d5b6a8940d0ad35109083209ab2bf679fb..1b0e5f3bd7be5ce10c72991abb40792584d66bd0 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java @@ -70,6 +70,7 @@ public class InvitationsBlogActivity extends InvitationsActivity { @Override protected void loadInvitations(final boolean clear) { + final int revision = adapter.getRevision(); runOnDbThread(new Runnable() { @Override public void run() { @@ -80,7 +81,7 @@ public class InvitationsBlogActivity extends InvitationsActivity { long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Load took " + duration + " ms"); - displayInvitations(invitations, clear); + displayInvitations(revision, invitations, clear); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java index 6b5811eaac51682ddeb9c4c0ac56214b4fcf7539..d64dab69f04af496aa5469aa79464c8eaa82d5a9 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java @@ -70,6 +70,7 @@ public class InvitationsForumActivity extends InvitationsActivity { @Override protected void loadInvitations(final boolean clear) { + final int revision = adapter.getRevision(); runOnDbThread(new Runnable() { @Override public void run() { @@ -80,7 +81,7 @@ public class InvitationsForumActivity extends InvitationsActivity { long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Load took " + duration + " ms"); - displayInvitations(invitations, clear); + displayInvitations(revision, invitations, clear); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java index f089f7c7ea148c9f7184eedfea6bd42d0fe7ccf6..034b6924f45500b074dddf1a5421a5a8f598311e 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java @@ -24,13 +24,15 @@ public abstract class ThreadItemAdapter<I extends ThreadItem> private final NestedTreeList<I> items = new NestedTreeList<>(); private final Map<I, ValueAnimator> animatingItems = new HashMap<>(); + private final ThreadItemListener<I> listener; + private final LinearLayoutManager layoutManager; + // highlight not dependant on time private I replyItem; // temporary highlight private I addedItem; - private final ThreadItemListener<I> listener; - private final LinearLayoutManager layoutManager; + private volatile int revision = 0; public ThreadItemAdapter(ThreadItemListener<I> listener, LinearLayoutManager layoutManager) { @@ -290,6 +292,29 @@ public abstract class ThreadItemAdapter<I extends ThreadItem> animatingItems.remove(item); } + /** + * Returns the adapter's revision counter. This method should be called on + * any thread before starting an asynchronous load that could overwrite + * other changes to the adapter, and called again on the UI thread before + * applying the changes from the asynchronous load. If the revision has + * changed between the two calls, the asynchronous load should be restarted + * without applying its changes. Otherwise {@link #incrementRevision()} + * should be called before applying the changes. + */ + public int getRevision() { + return revision; + } + + /** + * Increments the adapter's revision counter. This method should be called + * on the UI thread before applying any changes to the adapter that could + * be overwritten by an asynchronous load. + */ + @UiThread + public void incrementRevision() { + revision++; + } + protected interface ThreadItemListener<I> { void onItemVisible(I item); diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java index 5ae9b5c6ff72b2bf6b806ac03c467274db965e37..6a1ec4883dfcc6ea3c8563aef42f556741b41de0 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListActivity.java @@ -29,6 +29,7 @@ import org.briarproject.api.sync.MessageId; import org.briarproject.util.StringUtils; import java.util.Collection; +import java.util.logging.Logger; import static android.support.design.widget.Snackbar.make; import static android.view.View.GONE; @@ -42,6 +43,9 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI protected static final String KEY_INPUT_VISIBILITY = "inputVisibility"; protected static final String KEY_REPLY_ID = "replyId"; + private static final Logger LOG = + Logger.getLogger(ThreadListActivity.class.getName()); + protected A adapter; protected BriarRecyclerView list; protected TextInputView textInput; @@ -106,18 +110,24 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI protected abstract void onNamedGroupLoaded(G groupItem); private void loadItems() { + final int revision = adapter.getRevision(); getController().loadItems( - new UiResultExceptionHandler<Collection<I>, DbException>( - this) { + new UiResultExceptionHandler<Collection<I>, DbException>(this) { @Override public void onResultUi(Collection<I> items) { - if (items.isEmpty()) { - list.showData(); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (items.isEmpty()) { + list.showData(); + } else { + adapter.setItems(items); + list.showData(); + if (replyId != null) + adapter.setReplyItemById(replyId); + } } else { - adapter.setItems(items); - list.showData(); - if (replyId != null) - adapter.setReplyItemById(replyId); + LOG.info("Concurrent update, reloading"); + loadItems(); } } @@ -271,6 +281,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI } protected void addItem(final I item, boolean isLocal) { + adapter.incrementRevision(); adapter.add(item); if (isLocal && adapter.isVisible(item)) { displaySnackbarShort(getItemPostedString()); diff --git a/briar-android/src/org/briarproject/android/util/BriarAdapter.java b/briar-android/src/org/briarproject/android/util/BriarAdapter.java index e9e4b3dbac97de7b4067e1c31905043c46e50c7a..eb566b8bd8e41c27b371ae27d2313d4a6d303f60 100644 --- a/briar-android/src/org/briarproject/android/util/BriarAdapter.java +++ b/briar-android/src/org/briarproject/android/util/BriarAdapter.java @@ -2,6 +2,7 @@ package org.briarproject.android.util; import android.content.Context; import android.support.annotation.Nullable; +import android.support.annotation.UiThread; import android.support.v7.util.SortedList; import android.support.v7.widget.RecyclerView.Adapter; import android.support.v7.widget.RecyclerView.ViewHolder; @@ -16,6 +17,8 @@ public abstract class BriarAdapter<T, V extends ViewHolder> protected final Context ctx; protected final SortedList<T> items; + private volatile int revision = 0; + public BriarAdapter(Context ctx, Class<T> c) { this.ctx = ctx; this.items = new SortedList<>(c, new SortedList.Callback<T>() { @@ -110,4 +113,26 @@ public abstract class BriarAdapter<T, V extends ViewHolder> return items.size() == 0; } + /** + * Returns the adapter's revision counter. This method should be called on + * any thread before starting an asynchronous load that could overwrite + * other changes to the adapter, and called again on the UI thread before + * applying the changes from the asynchronous load. If the revision has + * changed between the two calls, the asynchronous load should be restarted + * without applying its changes. Otherwise {@link #incrementRevision()} + * should be called before applying the changes. + */ + public int getRevision() { + return revision; + } + + /** + * Increments the adapter's revision counter. This method should be called + * on the UI thread before applying any changes to the adapter that could + * be overwritten by an asynchronous load. + */ + @UiThread + public void incrementRevision() { + revision++; + } }