diff --git a/briar-android/src/org/briarproject/android/BaseActivity.java b/briar-android/src/org/briarproject/android/BaseActivity.java index fa3671b5013b9fe29f2823d09719d0a578ec88bf..a144adb9e7e969d9b12b55acdf2cc8177fbb4d2b 100644 --- a/briar-android/src/org/briarproject/android/BaseActivity.java +++ b/briar-android/src/org/briarproject/android/BaseActivity.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.List; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static android.view.inputmethod.InputMethodManager.SHOW_FORCED; import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT; import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS; @@ -62,18 +61,18 @@ public abstract class BaseActivity extends AppCompatActivity } @Override - protected void onResume() { - super.onResume(); + protected void onStart() { + super.onStart(); for (ActivityLifecycleController alc : lifecycleControllers) { - alc.onActivityResume(); + alc.onActivityStart(); } } @Override - protected void onPause() { - super.onPause(); + protected void onStop() { + super.onStop(); for (ActivityLifecycleController alc : lifecycleControllers) { - alc.onActivityPause(); + alc.onActivityStop(); } } diff --git a/briar-android/src/org/briarproject/android/BriarActivity.java b/briar-android/src/org/briarproject/android/BriarActivity.java index 4c01808af7cba45255b7d091657471145c1d3112..c0bda36a8a722b6526e075fa7bf73ea2526f1544 100644 --- a/briar-android/src/org/briarproject/android/BriarActivity.java +++ b/briar-android/src/org/briarproject/android/BriarActivity.java @@ -34,10 +34,11 @@ public abstract class BriarActivity extends BaseActivity { Logger.getLogger(BriarActivity.class.getName()); @Inject - protected BriarController briarController; - // TODO remove this when the deprecated method runOnDbThread is removed + BriarController briarController; + + @Deprecated @Inject - protected DbController dbController; + DbController dbController; @Override protected void onActivityResult(int request, int result, Intent data) { @@ -49,8 +50,8 @@ public abstract class BriarActivity extends BaseActivity { } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); if (!briarController.hasEncryptionKey() && !isFinishing()) { Intent i = new Intent(this, PasswordActivity.class); i.setFlags(FLAG_ACTIVITY_NO_ANIMATION | FLAG_ACTIVITY_SINGLE_TOP); diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java index 751700559ebc95d966a558eb0e4ad39458d51fea..5d69216a75ed1eafb08890864033c8b93587a399 100644 --- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java +++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java @@ -141,8 +141,8 @@ public class NavDrawerActivity extends BriarFragmentActivity implements } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); updateTransports(); } diff --git a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java index 0015bc111e22d70032ce92114a1839cac71db4b5..7ff91c18bde74948718295728c12630f3b1483e4 100644 --- a/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/BaseControllerImpl.java @@ -12,8 +12,6 @@ import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; -import org.briarproject.api.event.BlogPostAddedEvent; -import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; import org.briarproject.api.identity.IdentityManager; @@ -47,7 +45,7 @@ abstract class BaseControllerImpl extends DbControllerImpl private final Map<MessageId, BlogPostHeader> headerCache = new ConcurrentHashMap<>(); - protected volatile OnBlogPostAddedListener listener; + private volatile OnBlogPostAddedListener listener; BaseControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, EventBus eventBus, @@ -63,9 +61,7 @@ abstract class BaseControllerImpl extends DbControllerImpl @Override @CallSuper public void onStart() { - if (listener == null) - throw new IllegalStateException( - "OnBlogPostAddedListener needs to be attached"); + if (listener == null) throw new IllegalStateException(); eventBus.addListener(this); } @@ -75,26 +71,30 @@ abstract class BaseControllerImpl extends DbControllerImpl eventBus.removeListener(this); } - @Override - @CallSuper - public void eventOccurred(Event e) { - if (e instanceof BlogPostAddedEvent) { - final BlogPostAddedEvent b = (BlogPostAddedEvent) e; - LOG.info("New blog post added"); - listener.runOnUiThreadUnlessDestroyed(new Runnable() { - @Override - public void run() { - listener.onBlogPostAdded(b.getHeader(), b.isLocal()); - } - }); - } - } - @Override public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) { this.listener = listener; } + void onBlogPostAdded(final BlogPostHeader h, final boolean local) { + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onBlogPostAdded(h, local); + } + }); + } + + void onBlogRemoved() { + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onBlogRemoved(); + } + }); + } + + @Override public void loadBlogPosts(final GroupId groupId, final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) { diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java index 5394821f8751dce0fc439445c3ca645434026b56..64cee65a5b89d45644f6fab5674e5398b0e05e10 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java @@ -26,6 +26,7 @@ import java.util.logging.Logger; import javax.inject.Inject; +import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; public class BlogControllerImpl extends BaseControllerImpl @@ -50,15 +51,15 @@ public class BlogControllerImpl extends BaseControllerImpl } @Override - public void onActivityResume() { - super.onStart(); // TODO: Should be called when activity starts. #609 + public void onActivityStart() { + super.onStart(); notificationManager.blockNotification(groupId); notificationManager.clearBlogPostNotification(groupId); } @Override - public void onActivityPause() { - super.onStop(); // TODO: Should be called when activity stops. #609 + public void onActivityStop() { + super.onStop(); notificationManager.unblockNotification(groupId); } @@ -75,20 +76,16 @@ public class BlogControllerImpl extends BaseControllerImpl public void eventOccurred(Event e) { if (groupId == null) throw new IllegalStateException(); if (e instanceof BlogPostAddedEvent) { - BlogPostAddedEvent s = (BlogPostAddedEvent) e; - if (s.getGroupId().equals(groupId)) { - super.eventOccurred(e); + BlogPostAddedEvent b = (BlogPostAddedEvent) e; + if (b.getGroupId().equals(groupId)) { + LOG.info("Blog post added"); + onBlogPostAdded(b.getHeader(), b.isLocal()); } } else if (e instanceof GroupRemovedEvent) { - GroupRemovedEvent s = (GroupRemovedEvent) e; - if (s.getGroup().getId().equals(groupId)) { + GroupRemovedEvent g = (GroupRemovedEvent) e; + if (g.getGroup().getId().equals(groupId)) { LOG.info("Blog removed"); - listener.runOnUiThreadUnlessDestroyed(new Runnable() { - @Override - public void run() { - listener.onBlogRemoved(); - } - }); + onBlogRemoved(); } } } @@ -115,11 +112,15 @@ public class BlogControllerImpl extends BaseControllerImpl @Override public void run() { try { + long now = System.currentTimeMillis(); LocalAuthor a = identityManager.getLocalAuthor(); Blog b = blogManager.getBlog(groupId); boolean ours = a.getId().equals(b.getAuthor().getId()); boolean removable = blogManager.canBeRemoved(groupId); BlogItem blog = new BlogItem(b, ours, removable); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading blog took " + duration + " ms"); handler.onResult(blog); } catch (DbException e) { if (LOG.isLoggable(WARNING)) @@ -138,8 +139,12 @@ public class BlogControllerImpl extends BaseControllerImpl @Override public void run() { try { + long now = System.currentTimeMillis(); Blog b = blogManager.getBlog(groupId); blogManager.removeBlog(b); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Removing blog took " + duration + " ms"); handler.onResult(null); } catch (DbException e) { if (LOG.isLoggable(WARNING)) diff --git a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java index 25d266c312050286eeeb69289005f19930edb29e..eb655209108c6a290394c31c3fc3241d4f711c12 100644 --- a/briar-android/src/org/briarproject/android/blogs/BlogFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/BlogFragment.java @@ -123,18 +123,13 @@ public class BlogFragment extends BaseFragment implements public void onStart() { super.onStart(); loadBlog(); - } - - @Override - public void onResume() { - super.onResume(); loadBlogPosts(false); list.startPeriodicUpdate(); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); list.stopPeriodicUpdate(); } @@ -215,9 +210,11 @@ public class BlogFragment extends BaseFragment implements adapter.add(post); if (local) { list.scrollToPosition(0); - displaySnackbar(R.string.blogs_blog_post_created, false); + displaySnackbar(R.string.blogs_blog_post_created, + false); } else { - displaySnackbar(R.string.blogs_blog_post_received, true); + displaySnackbar(R.string.blogs_blog_post_received, + true); } } @@ -236,11 +233,11 @@ public class BlogFragment extends BaseFragment implements listener) { @Override public void onResultUi(Collection<BlogPostItem> posts) { - if (posts.size() > 0) { + if (posts.isEmpty()) { + list.showData(); + } else { adapter.addAll(posts); if (reload) list.scrollToPosition(0); - } else { - list.showData(); } } diff --git a/briar-android/src/org/briarproject/android/blogs/FeedController.java b/briar-android/src/org/briarproject/android/blogs/FeedController.java index e2934f49fd67757128d0dc521f020d86953fcb4a..90015b066f6a17f9a79dc76c1d8dd66e6fc04cde 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedController.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedController.java @@ -1,7 +1,6 @@ package org.briarproject.android.blogs; import org.briarproject.android.controller.handler.ResultExceptionHandler; -import org.briarproject.android.controller.handler.ResultHandler; import org.briarproject.api.blogs.Blog; import org.briarproject.api.db.DbException; @@ -12,6 +11,6 @@ public interface FeedController extends BaseController { void loadBlogPosts( ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler); - void loadPersonalBlog(ResultHandler<Blog> resultHandler); + void loadPersonalBlog(ResultExceptionHandler<Blog, DbException> handler); } diff --git a/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java index 0d85144d8dd08e5389cfe62019216d876493fd6e..faf626b5462e281dd83e95e61a52f7eda29e6fa3 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedControllerImpl.java @@ -2,14 +2,16 @@ package org.briarproject.android.blogs; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.controller.handler.ResultExceptionHandler; -import org.briarproject.android.controller.handler.ResultHandler; import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.db.NoSuchMessageException; +import org.briarproject.api.event.BlogPostAddedEvent; +import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; +import org.briarproject.api.event.GroupRemovedEvent; import org.briarproject.api.identity.Author; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.lifecycle.LifecycleManager; @@ -52,15 +54,28 @@ public class FeedControllerImpl extends BaseControllerImpl notificationManager.unblockAllBlogPostNotifications(); } + @Override + public void eventOccurred(Event e) { + if (e instanceof BlogPostAddedEvent) { + BlogPostAddedEvent b = (BlogPostAddedEvent) e; + LOG.info("Blog post added"); + onBlogPostAdded(b.getHeader(), b.isLocal()); + } else if (e instanceof GroupRemovedEvent) { + GroupRemovedEvent g = (GroupRemovedEvent) e; + if (g.getGroup().getClientId().equals(blogManager.getClientId())) { + LOG.info("Blog removed"); + onBlogRemoved(); + } + } + } + @Override public void loadBlogPosts( final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) { - LOG.info("Loading all blog posts..."); runOnDbThread(new Runnable() { @Override public void run() { try { - // load blog posts long now = System.currentTimeMillis(); Collection<BlogPostItem> posts = new ArrayList<>(); for (Blog b : blogManager.getBlogs()) { @@ -85,24 +100,23 @@ public class FeedControllerImpl extends BaseControllerImpl } @Override - public void loadPersonalBlog(final ResultHandler<Blog> resultHandler) { - LOG.info("Loading personal blog..."); + public void loadPersonalBlog( + final ResultExceptionHandler<Blog, DbException> handler) { runOnDbThread(new Runnable() { @Override public void run() { try { - // load blog posts long now = System.currentTimeMillis(); Author a = identityManager.getLocalAuthor(); Blog b = blogManager.getPersonalBlog(a); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) - LOG.info("Loading pers. blog took " + duration + " ms"); - resultHandler.onResult(b); + LOG.info("Loading blog took " + duration + " ms"); + handler.onResult(b); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - resultHandler.onResult(null); + handler.onException(e); } } }); diff --git a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java index d07a800d59a6bbe61b5de4b0e453e68326b2ece9..9545f34ff4c8f7fe58e7d431bcb501269b01ab0d 100644 --- a/briar-android/src/org/briarproject/android/blogs/FeedFragment.java +++ b/briar-android/src/org/briarproject/android/blogs/FeedFragment.java @@ -20,7 +20,6 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener; import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; import org.briarproject.android.controller.handler.UiResultExceptionHandler; -import org.briarproject.android.controller.handler.UiResultHandler; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.api.blogs.Blog; @@ -28,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; @@ -42,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; @@ -99,40 +100,61 @@ public class FeedFragment extends BaseFragment implements public void onStart() { super.onStart(); feedController.onStart(); + loadPersonalBlog(); + loadBlogPosts(false); + } + + @Override + public void onStop() { + super.onStop(); + feedController.onStop(); + adapter.clear(); + list.showProgressBar(); + list.stopPeriodicUpdate(); + // TODO save list position in database/preferences? + } + + private void loadPersonalBlog() { feedController.loadPersonalBlog( - new UiResultHandler<Blog>(listener) { + new UiResultExceptionHandler<Blog, DbException>(listener) { @Override public void onResultUi(Blog b) { personalBlog = b; } + + @Override + public void onExceptionUi(DbException exception) { + // TODO: Decide how to handle errors in the UI + } }); + } + + 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 (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 { - adapter.addAll(posts); + LOG.info("Concurrent update, reloading"); + loadBlogPosts(clear); } } + @Override - public void onExceptionUi(DbException exception) { - // TODO + public void onExceptionUi(DbException e) { + // TODO: Decide how to handle errors in the UI } }); list.startPeriodicUpdate(); } - @Override - public void onStop() { - super.onStop(); - feedController.onStop(); - list.stopPeriodicUpdate(); - // TODO save list position in database/preferences? - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.blogs_feed_actions, menu); @@ -178,6 +200,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); @@ -185,6 +208,7 @@ public class FeedFragment extends BaseFragment implements showSnackBar(R.string.blogs_blog_post_received); } } + @Override public void onExceptionUi(DbException exception) { // TODO: Decide how to handle errors in the UI @@ -234,6 +258,6 @@ public class FeedFragment extends BaseFragment implements @Override public void onBlogRemoved() { - finish(); + loadBlogPosts(true); } } diff --git a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java index 2fe3ad30ccf90ac3496ef0d5989cbd4b6856e45d..ecffd0cd2007c7349b7a3766685cf93434909ef8 100644 --- a/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/RssFeedManageActivity.java @@ -37,9 +37,7 @@ public class RssFeedManageActivity extends BriarActivity private BriarRecyclerView list; private RssFeedAdapter adapter; - - // Fields that are accessed from background threads must be volatile - private volatile GroupId groupId = null; + private GroupId groupId; @Inject @SuppressWarnings("WeakerAccess") @@ -65,11 +63,18 @@ public class RssFeedManageActivity extends BriarActivity } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); loadFeeds(); } + @Override + public void onStop() { + super.onStop(); + adapter.clear(); + list.showProgressBar(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); @@ -120,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 displayFeeds(final int revision, final List<Feed> feeds) { + runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (feeds.isEmpty()) list.showData(); + else adapter.addAll(feeds); + } else { + LOG.info("Concurrent update, reloading"); + loadFeeds(); } } }); } - private void addFeeds(final List<Feed> feeds) { + private void onLoadError() { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - if (feeds.size() == 0) list.showData(); - else adapter.addAll(feeds); + list.setEmptyText(R.string.blogs_rss_feeds_manage_error); + list.showData(); } }); } @@ -149,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/blogs/WriteBlogPostActivity.java b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java index 70d326ca42266b50da015e30191740ad786e0a2c..014f0d1f48541911a14c218008bb78fd70429165 100644 --- a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java +++ b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java @@ -94,15 +94,15 @@ public class WriteBlogPostActivity extends BriarActivity } @Override - public void onPause() { - super.onPause(); - notificationManager.unblockNotification(groupId); + public void onStart() { + super.onStart(); + notificationManager.blockNotification(groupId); } @Override - public void onResume() { - super.onResume(); - notificationManager.blockNotification(groupId); + public void onStop() { + super.onStop(); + notificationManager.unblockNotification(groupId); } @Override diff --git a/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java index aa48714f77f086e031054e7d5929e5b70c11ed24..cd01ad1a426faa92757372bade5fe2db41005e7f 100644 --- a/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/BaseContactListAdapter.java @@ -13,7 +13,6 @@ import org.briarproject.R; import org.briarproject.android.util.BriarAdapter; import org.briarproject.api.contact.ContactId; import org.briarproject.api.identity.Author; -import org.briarproject.api.sync.GroupId; import org.briarproject.util.StringUtils; import im.delight.android.identicons.IdenticonDrawable; @@ -90,17 +89,6 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B return INVALID_POSITION; // Not found } - int findItemPosition(GroupId g) { - int count = getItemCount(); - for (int i = 0; i < count; i++) { - ContactListItem item = getItemAt(i); - if (item != null && item.getGroupId().equals(g)) { - return i; - } - } - return INVALID_POSITION; // Not found - } - public static class BaseContactHolder extends RecyclerView.ViewHolder { public final ViewGroup layout; diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index 891bfcfbc254b38e4b3242b1e092ae8d9d39f211..474b2cc2b7b7b7f854541eb1128776378b266679 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -170,8 +170,8 @@ public class ContactListFragment extends BaseFragment implements EventListener { } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); notificationManager.blockAllContactNotifications(); notificationManager.clearAllContactNotifications(); eventBus.addListener(this); @@ -180,8 +180,8 @@ public class ContactListFragment extends BaseFragment implements EventListener { } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); eventBus.removeListener(this); notificationManager.unblockAllContactNotifications(); adapter.clear(); @@ -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() { @@ -213,10 +214,10 @@ public class ContactListFragment extends BaseFragment implements EventListener { // Continue } } - displayContacts(contacts); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Full load took " + duration + " ms"); + 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.size() == 0) 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(); + } } }); } @@ -238,40 +246,45 @@ public class ContactListFragment extends BaseFragment implements EventListener { @Override public void eventOccurred(Event e) { if (e instanceof ContactStatusChangedEvent) { - LOG.info("Contact Status changed, reloading"); - // is also broadcast when contact was added - loadContacts(); + ContactStatusChangedEvent c = (ContactStatusChangedEvent) e; + if (c.isActive()) { + LOG.info("Contact activated, reloading"); + loadContacts(); + } else { + LOG.info("Contact deactivated, removing item"); + removeItem(c.getContactId()); + } } else if (e instanceof ContactConnectedEvent) { setConnected(((ContactConnectedEvent) e).getContactId(), true); } else if (e instanceof ContactDisconnectedEvent) { setConnected(((ContactDisconnectedEvent) e).getContactId(), false); } else if (e instanceof ContactRemovedEvent) { - LOG.info("Contact removed"); + LOG.info("Contact removed, removing item"); removeItem(((ContactRemovedEvent) e).getContactId()); } else if (e instanceof PrivateMessageReceivedEvent) { - LOG.info("Message received, update contact"); + LOG.info("Private message received, updating item"); PrivateMessageReceivedEvent p = (PrivateMessageReceivedEvent) e; PrivateMessageHeader h = p.getMessageHeader(); - updateItem(p.getGroupId(), ConversationItem.from(h)); + updateItem(p.getContactId(), ConversationItem.from(h)); } else if (e instanceof IntroductionRequestReceivedEvent) { - LOG.info("Introduction Request received, update contact"); + LOG.info("Introduction request received, updating item"); IntroductionRequestReceivedEvent m = (IntroductionRequestReceivedEvent) e; IntroductionRequest ir = m.getIntroductionRequest(); updateItem(m.getContactId(), ConversationItem.from(ir)); } else if (e instanceof IntroductionResponseReceivedEvent) { - LOG.info("Introduction Response received, update contact"); + LOG.info("Introduction response received, updating item"); IntroductionResponseReceivedEvent m = (IntroductionResponseReceivedEvent) e; IntroductionResponse ir = m.getIntroductionResponse(); updateItem(m.getContactId(), ConversationItem.from(ir)); } else if (e instanceof InvitationRequestReceivedEvent) { - LOG.info("Invitation Request received, update contact"); + LOG.info("Invitation request received, updating item"); InvitationRequestReceivedEvent m = (InvitationRequestReceivedEvent) e; InvitationRequest ir = m.getRequest(); updateItem(m.getContactId(), ConversationItem.from(ir)); } else if (e instanceof InvitationResponseReceivedEvent) { - LOG.info("Invitation Response received, update contact"); + LOG.info("Invitation response received, updating item"); InvitationResponseReceivedEvent m = (InvitationResponseReceivedEvent) e; InvitationResponse ir = m.getResponse(); @@ -283,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) { @@ -293,24 +307,11 @@ public class ContactListFragment extends BaseFragment implements EventListener { }); } - private void updateItem(final GroupId g, final ConversationItem m) { - listener.runOnUiThreadUnlessDestroyed(new Runnable() { - @Override - public void run() { - int position = adapter.findItemPosition(g); - ContactListItem item = adapter.getItemAt(position); - if (item != null) { - item.addMessage(m); - adapter.updateItemAt(position, item); - } - } - }); - } - private void removeItem(final ContactId c) { 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); @@ -322,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 1d563ad0ce1679a7d218e911d083d3d270eb95d4..c92c5195121ea976f4b64d5a1f0350da3daf1457 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -34,7 +34,6 @@ import org.briarproject.android.view.TextInputView; import org.briarproject.android.view.TextInputView.TextInputListener; import org.briarproject.api.FormatException; import org.briarproject.api.blogs.BlogSharingManager; -import org.briarproject.api.clients.BaseMessageHeader; import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; @@ -42,7 +41,6 @@ import org.briarproject.api.contact.ContactManager; import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchContactException; -import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.event.ContactConnectedEvent; import org.briarproject.api.event.ContactDisconnectedEvent; import org.briarproject.api.event.ContactRemovedEvent; @@ -150,7 +148,6 @@ public class ConversationActivity extends BriarActivity private volatile ContactId contactId = null; private volatile String contactName = null; private volatile byte[] contactIdenticonKey = null; - private volatile boolean connected = false; @SuppressWarnings("ConstantConditions") @Override @@ -214,18 +211,19 @@ public class ConversationActivity extends BriarActivity } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); eventBus.addListener(this); notificationManager.blockNotification(groupId); notificationManager.clearPrivateMessageNotification(groupId); - loadData(); + loadContactDetails(); + loadMessages(); list.startPeriodicUpdate(); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); eventBus.removeListener(this); notificationManager.unblockNotification(groupId); list.stopPeriodicUpdate(); @@ -277,7 +275,7 @@ public class ConversationActivity extends BriarActivity finish(); } - private void loadData() { + private void loadContactDetails() { runOnDbThread(new Runnable() { @Override public void run() { @@ -291,13 +289,10 @@ public class ConversationActivity extends BriarActivity contactIdenticonKey = contact.getAuthor().getId().getBytes(); } - connected = connectionRegistry.isConnected(contactId); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading contact took " + duration + " ms"); displayContactDetails(); - // Load the messages here to make sure we have a contactId - loadMessages(); } catch (NoSuchContactException e) { finishOnUiThread(); } catch (DbException e) { @@ -316,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)); @@ -335,6 +330,7 @@ public class ConversationActivity extends BriarActivity } private void loadMessages() { + final int revision = adapter.getRevision(); runOnDbThread(new Runnable() { @Override public void run() { @@ -359,8 +355,9 @@ public class ConversationActivity extends BriarActivity invitations.addAll(blogInvitations); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) - LOG.info("Loading headers took " + duration + " ms"); - displayMessages(headers, introductions, invitations); + LOG.info("Loading messages took " + duration + " ms"); + displayMessages(revision, headers, introductions, + invitations); } catch (NoSuchContactException e) { finishOnUiThread(); } catch (DbException e) { @@ -371,59 +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); - if (headers.isEmpty() && introductions.isEmpty() && - invitations.isEmpty()) { - // we have no messages, - // so let the list know to hide progress bar - list.showData(); - } else { - List<ConversationItem> items = new ArrayList<>(); - 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 m : introductions) { - ConversationItem item; - if (m instanceof IntroductionRequest) { - item = ConversationItem - .from((IntroductionRequest) m); - } else { - item = ConversationItem - .from(ConversationActivity.this, - contactName, - (IntroductionResponse) m); - } - items.add(item); - } - for (InvitationMessage i : invitations) { - if (i instanceof InvitationRequest) { - InvitationRequest r = (InvitationRequest) i; - items.add(ConversationItem.from(r)); - } else if (i instanceof InvitationResponse) { - InvitationResponse r = (InvitationResponse) i; - items.add(ConversationItem - .from(ConversationActivity.this, - contactName, r)); - } - } - 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 @@ -435,8 +439,6 @@ public class ConversationActivity extends BriarActivity if (LOG.isLoggable(INFO)) LOG.info("Loading body took " + duration + " ms"); displayMessageBody(m, body); - } catch (NoSuchMessageException e) { - // The item will be removed when we get the event } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -469,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); @@ -496,9 +499,10 @@ public class ConversationActivity extends BriarActivity public void run() { try { long now = System.currentTimeMillis(); - for (Map.Entry<MessageId, GroupId> e : unread.entrySet()) - messagingManager - .setReadFlag(e.getValue(), e.getKey(), true); + for (Map.Entry<MessageId, GroupId> e : unread.entrySet()) { + messagingManager.setReadFlag(e.getValue(), e.getKey(), + true); + } long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Marking read took " + duration + " ms"); @@ -525,7 +529,6 @@ public class ConversationActivity extends BriarActivity PrivateMessageHeader h = p.getMessageHeader(); addConversationItem(ConversationItem.from(h)); loadMessageBody(h.getId()); - markMessageReadIfNew(h); } } else if (e instanceof MessagesSentEvent) { MessagesSentEvent m = (MessagesSentEvent) e; @@ -543,14 +546,12 @@ public class ConversationActivity extends BriarActivity ContactConnectedEvent c = (ContactConnectedEvent) e; if (c.getContactId().equals(contactId)) { LOG.info("Contact connected"); - connected = true; displayContactDetails(); } } else if (e instanceof ContactDisconnectedEvent) { ContactDisconnectedEvent c = (ContactDisconnectedEvent) e; if (c.getContactId().equals(contactId)) { LOG.info("Contact disconnected"); - connected = false; displayContactDetails(); } } else if (e instanceof IntroductionRequestReceivedEvent) { @@ -561,7 +562,6 @@ public class ConversationActivity extends BriarActivity IntroductionRequest ir = event.getIntroductionRequest(); ConversationItem item = new ConversationIntroductionInItem(ir); addConversationItem(item); - markMessageReadIfNew(ir); } } else if (e instanceof IntroductionResponseReceivedEvent) { IntroductionResponseReceivedEvent event = @@ -572,7 +572,6 @@ public class ConversationActivity extends BriarActivity ConversationItem item = ConversationItem.from(this, contactName, ir); addConversationItem(item); - markMessageReadIfNew(ir); } } else if (e instanceof InvitationRequestReceivedEvent) { InvitationRequestReceivedEvent event = @@ -582,7 +581,6 @@ public class ConversationActivity extends BriarActivity InvitationRequest ir = event.getRequest(); ConversationItem item = ConversationItem.from(ir); addConversationItem(item); - markMessageReadIfNew(ir); } } else if (e instanceof InvitationResponseReceivedEvent) { InvitationResponseReceivedEvent event = @@ -593,51 +591,16 @@ public class ConversationActivity extends BriarActivity ConversationItem item = ConversationItem.from(this, contactName, ir); addConversationItem(item); - markMessageReadIfNew(ir); } } } - private void markMessageReadIfNew(final BaseMessageHeader h) { - runOnUiThreadUnlessDestroyed(new Runnable() { - @Override - public void run() { - ConversationItem item = adapter.getLastItem(); - if (item != null) { - // Mark the message read if it's the newest message - long lastMsgTime = item.getTime(); - long newMsgTime = h.getTimestamp(); - if (newMsgTime > lastMsgTime) - markNewMessageRead(h.getGroupId(), h.getId()); - else loadMessages(); - } else { - // mark the message as read as well if it is the first one - markNewMessageRead(h.getGroupId(), h.getId()); - } - } - }); - } - - private void markNewMessageRead(final GroupId g, final MessageId m) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - messagingManager.setReadFlag(g, m, true); - loadMessages(); - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - private void markMessages(final Collection<MessageId> messageIds, final boolean sent, final boolean seen) { 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++) { @@ -654,7 +617,6 @@ public class ConversationActivity extends BriarActivity @Override public void onSendClick(String text) { - markMessagesRead(); if (text.equals("")) return; long timestamp = System.currentTimeMillis(); timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); @@ -857,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/controller/ActivityLifecycleController.java b/briar-android/src/org/briarproject/android/controller/ActivityLifecycleController.java index 303f9b89b40e36fd8a90f0e3dfc06092201c47c2..96d0cf4f0201539b39c35974db08e8b7be60dacb 100644 --- a/briar-android/src/org/briarproject/android/controller/ActivityLifecycleController.java +++ b/briar-android/src/org/briarproject/android/controller/ActivityLifecycleController.java @@ -6,9 +6,9 @@ public interface ActivityLifecycleController { void onActivityCreate(Activity activity); - void onActivityResume(); + void onActivityStart(); - void onActivityPause(); + void onActivityStop(); void onActivityDestroy(); } diff --git a/briar-android/src/org/briarproject/android/controller/BriarControllerImpl.java b/briar-android/src/org/briarproject/android/controller/BriarControllerImpl.java index 459730ebd2b616224156ace7baefe430060b286a..f49d6485f7b58e4673e06f4f40e1125275e85610 100644 --- a/briar-android/src/org/briarproject/android/controller/BriarControllerImpl.java +++ b/briar-android/src/org/briarproject/android/controller/BriarControllerImpl.java @@ -19,18 +19,18 @@ public class BriarControllerImpl implements BriarController { private static final Logger LOG = Logger.getLogger(BriarControllerImpl.class.getName()); - @Inject - BriarServiceConnection serviceConnection; - @Inject - DatabaseConfig databaseConfig; - @Inject - Activity activity; + private final BriarServiceConnection serviceConnection; + private final DatabaseConfig databaseConfig; + private final Activity activity; private boolean bound = false; @Inject - public BriarControllerImpl() { - + BriarControllerImpl(BriarServiceConnection serviceConnection, + DatabaseConfig databaseConfig, Activity activity) { + this.serviceConnection = serviceConnection; + this.databaseConfig = databaseConfig; + this.activity = activity; } @Override @@ -40,13 +40,11 @@ public class BriarControllerImpl implements BriarController { } @Override - @CallSuper - public void onActivityResume() { + public void onActivityStart() { } @Override - @CallSuper - public void onActivityPause() { + public void onActivityStop() { } @Override diff --git a/briar-android/src/org/briarproject/android/controller/NavDrawerControllerImpl.java b/briar-android/src/org/briarproject/android/controller/NavDrawerControllerImpl.java index 060f77933e3993d214d548d8b8a9810e7e0b05d9..55d5d267f74c8fb16b8befdae50ee54c5ba61c48 100644 --- a/briar-android/src/org/briarproject/android/controller/NavDrawerControllerImpl.java +++ b/briar-android/src/org/briarproject/android/controller/NavDrawerControllerImpl.java @@ -57,12 +57,12 @@ public class NavDrawerControllerImpl extends DbControllerImpl } @Override - public void onActivityResume() { + public void onActivityStart() { eventBus.addListener(this); } @Override - public void onActivityPause() { + public void onActivityStop() { eventBus.removeListener(this); } diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 81b41f99fed3dbc19467fd6ce6e56dc7c6067ee4..12487efc946fa28c551df033daf29f9a76920687 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -1,6 +1,7 @@ package org.briarproject.android.forum; import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; import android.support.annotation.LayoutRes; @@ -98,9 +99,8 @@ public class ForumActivity extends @Override public boolean onOptionsItemSelected(final MenuItem item) { - ActivityOptionsCompat options = - makeCustomAnimation(this, android.R.anim.slide_in_left, - android.R.anim.slide_out_right); + ActivityOptionsCompat options = makeCustomAnimation(this, + android.R.anim.slide_in_left, android.R.anim.slide_out_right); // Handle presses on the action bar items switch (item.getItemId()) { case R.id.action_forum_compose_post: @@ -110,9 +110,8 @@ public class ForumActivity extends Intent i2 = new Intent(this, ShareForumActivity.class); i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP); i2.putExtra(GROUP_ID, groupId.getBytes()); - ActivityCompat - .startActivityForResult(this, i2, REQUEST_FORUM_SHARED, - options.toBundle()); + ActivityCompat.startActivityForResult(this, i2, + REQUEST_FORUM_SHARED, options.toBundle()); return true; case R.id.action_forum_sharing_status: Intent i3 = new Intent(this, SharingStatusForumActivity.class); @@ -146,17 +145,14 @@ public class ForumActivity extends } private void showUnsubscribeDialog() { - DialogInterface.OnClickListener okListener = - new DialogInterface.OnClickListener() { - @Override - public void onClick(final DialogInterface dialog, - int which) { - deleteNamedGroup(); - } - }; - AlertDialog.Builder builder = - new AlertDialog.Builder(ForumActivity.this, - R.style.BriarDialogTheme); + OnClickListener okListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + deleteNamedGroup(); + } + }; + AlertDialog.Builder builder = new AlertDialog.Builder( + ForumActivity.this, R.style.BriarDialogTheme); builder.setTitle(getString(R.string.dialog_title_leave_forum)); builder.setMessage(getString(R.string.dialog_message_leave_forum)); builder.setNegativeButton(R.string.dialog_button_leave, okListener); @@ -166,19 +162,15 @@ 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, - R.string.forum_left_toast, - LENGTH_SHORT) - .show(); + R.string.forum_left_toast, LENGTH_SHORT).show(); } @Override - public void onExceptionUi( - DbException exception) { + public void onExceptionUi(DbException exception) { // TODO proper error handling finish(); } diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java index fa62f1b292740855de64345b1b2e9866c0404dca..a0b89b0ed4aa4d9363bf20f4fb0487a3de5fc313 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java +++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java @@ -28,8 +28,8 @@ import java.util.logging.Logger; import javax.inject.Inject; -public class ForumControllerImpl - extends ThreadListControllerImpl<Forum, ForumItem, ForumPostHeader, ForumPost> +public class ForumControllerImpl extends + ThreadListControllerImpl<Forum, ForumItem, ForumPostHeader, ForumPost> implements ForumController { private static final Logger LOG = @@ -49,8 +49,8 @@ public class ForumControllerImpl } @Override - public void onActivityResume() { - super.onActivityResume(); + public void onActivityStart() { + super.onActivityStart(); notificationManager.clearForumPostNotification(getGroupId()); } @@ -59,7 +59,7 @@ public class ForumControllerImpl super.eventOccurred(e); if (e instanceof ForumPostReceivedEvent) { - final ForumPostReceivedEvent pe = (ForumPostReceivedEvent) e; + ForumPostReceivedEvent pe = (ForumPostReceivedEvent) e; if (pe.getGroupId().equals(getGroupId())) { LOG.info("Forum post received, adding..."); final ForumPostHeader fph = pe.getForumPostHeader(); @@ -102,9 +102,8 @@ public class ForumControllerImpl @Override protected ForumPost createLocalMessage(String body, long timestamp, @Nullable MessageId parentId, LocalAuthor author) { - return forumManager - .createLocalPost(getGroupId(), body, timestamp, parentId, - author); + return forumManager.createLocalPost(getGroupId(), body, timestamp, + parentId, author); } @Override diff --git a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java index 1ebba8c1c327b16b23c9489e623ec4c62713784c..6bbf1965776b3ea3c90c7eccce1b2d661d89282c 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumListFragment.java +++ b/briar-android/src/org/briarproject/android/forum/ForumListFragment.java @@ -111,9 +111,8 @@ public class ForumListFragment extends BaseEventFragment implements } @Override - public void onResume() { - super.onResume(); - + public void onStart() { + super.onStart(); notificationManager.blockAllForumPostNotifications(); notificationManager.clearAllForumPostNotifications(); loadForums(); @@ -122,9 +121,8 @@ public class ForumListFragment extends BaseEventFragment implements } @Override - public void onPause() { - super.onPause(); - + public void onStop() { + super.onStop(); notificationManager.unblockAllForumPostNotifications(); adapter.clear(); list.showProgressBar(); @@ -152,11 +150,11 @@ public class ForumListFragment extends BaseEventFragment implements } private void loadForums() { + final int revision = adapter.getRevision(); listener.runOnDbThread(new Runnable() { @Override public void run() { try { - // load forums long now = System.currentTimeMillis(); Collection<ForumListItem> forums = new ArrayList<>(); for (Forum f : forumManager.getForums()) { @@ -168,10 +166,10 @@ public class ForumListFragment extends BaseEventFragment implements // Continue } } - displayForums(forums); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Full load took " + duration + " ms"); + displayForums(revision, forums); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -180,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.size() > 0) adapter.addAll(forums); - else list.showData(); + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (forums.isEmpty()) list.showData(); + else adapter.addAll(forums); + } else { + LOG.info("Concurrent update, reloading"); + loadForums(); + } } }); } @@ -245,9 +250,10 @@ public class ForumListFragment extends BaseEventFragment implements } } else if (e instanceof ForumPostReceivedEvent) { ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; - LOG.info("Forum post added, updating..."); + LOG.info("Forum post added, updating item"); updateItem(f.getGroupId(), f.getForumPostHeader()); } else if (e instanceof ForumInvitationReceivedEvent) { + LOG.info("Forum invitation received, reloading available forums"); loadAvailableForums(); } } @@ -256,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) { @@ -270,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/fragment/BaseEventFragment.java b/briar-android/src/org/briarproject/android/fragment/BaseEventFragment.java index 69ceab43a6281a5a7ae1f0ad901697a6b2651d82..cf78840532ca486968a10c09d320a57b617d63bd 100644 --- a/briar-android/src/org/briarproject/android/fragment/BaseEventFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/BaseEventFragment.java @@ -12,14 +12,14 @@ public abstract class BaseEventFragment extends BaseFragment implements protected volatile EventBus eventBus; @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); eventBus.addListener(this); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); eventBus.removeListener(this); } } diff --git a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java index d671bf245c692463ea937e7d6532e831dea79278..2cb6b13e6152d89b1e118e8b2cd8494559e78b26 100644 --- a/briar-android/src/org/briarproject/android/fragment/BaseFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/BaseFragment.java @@ -44,6 +44,7 @@ public abstract class BaseFragment extends Fragment public interface BaseFragmentListener extends DestroyableContext { + @Deprecated void runOnDbThread(Runnable runnable); @UiThread diff --git a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java index 15c718b6709519831533590334fa7b702d860f05..a3d6551267d1cd6fca14e571e2ebeea12236c011 100644 --- a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java @@ -155,14 +155,14 @@ public class SettingsFragment extends PreferenceFragmentCompat } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); eventBus.addListener(this); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); eventBus.removeListener(this); } diff --git a/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java b/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java index 059ab8cd11e00b8714cd1b6ec94baf97f71b2994..e291302b8e6b86323bfbd8ff587bce6854fbc8ee 100644 --- a/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java +++ b/briar-android/src/org/briarproject/android/introduction/ContactChooserAdapter.java @@ -1,19 +1,19 @@ package org.briarproject.android.introduction; import android.content.Context; +import android.support.annotation.UiThread; import android.view.View; import org.briarproject.android.contact.ContactListAdapter; import org.briarproject.android.contact.ContactListItem; import org.briarproject.api.identity.AuthorId; -public class ContactChooserAdapter extends ContactListAdapter { +@UiThread +class ContactChooserAdapter extends ContactListAdapter { private AuthorId localAuthorId; - public ContactChooserAdapter(Context context, - OnItemClickListener listener) { - + ContactChooserAdapter(Context context, OnItemClickListener listener) { super(context, listener); } @@ -46,7 +46,7 @@ public class ContactChooserAdapter extends ContactListAdapter { * * @param authorId The ID of the local Author */ - public void setLocalAuthor(AuthorId authorId) { + void setLocalAuthor(AuthorId authorId) { localAuthorId = authorId; notifyDataSetChanged(); } diff --git a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java index b26d3a8dc05c9dbdf8911d96d13d81d116437846..888cc61e79eb48b713a61ffeba6788c759f02abb 100644 --- a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java +++ b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java @@ -45,18 +45,18 @@ public class ContactChooserFragment extends BaseFragment { private IntroductionActivity introductionActivity; private BriarRecyclerView list; private ContactChooserAdapter adapter; - private int contactId; + private ContactId contactId; // Fields that are accessed from background threads must be volatile - protected volatile Contact c1; + volatile Contact c1; @Inject - protected volatile ContactManager contactManager; + volatile ContactManager contactManager; @Inject - protected volatile IdentityManager identityManager; + volatile IdentityManager identityManager; @Inject - protected volatile ConversationManager conversationManager; + volatile ConversationManager conversationManager; @Inject - protected volatile ConnectionRegistry connectionRegistry; + volatile ConnectionRegistry connectionRegistry; public static ContactChooserFragment newInstance() { @@ -87,9 +87,7 @@ public class ContactChooserFragment extends BaseFragment { new ContactListAdapter.OnItemClickListener() { @Override public void onItemClick(View view, ContactListItem item) { - if (c1 == null) { - throw new RuntimeException("c1 not accountExists"); - } + if (c1 == null) throw new IllegalStateException(); Contact c2 = item.getContact(); if (!c1.getLocalAuthorId() .equals(c2.getLocalAuthorId())) { @@ -113,15 +111,14 @@ public class ContactChooserFragment extends BaseFragment { } @Override - public void onResume() { - super.onResume(); - + public void onStart() { + super.onStart(); loadContacts(); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); adapter.clear(); list.showProgressBar(); } @@ -145,7 +142,7 @@ public class ContactChooserFragment extends BaseFragment { AuthorId localAuthorId = identityManager.getLocalAuthor().getId(); for (Contact c : contactManager.getActiveContacts()) { - if (c.getId().getInt() == contactId) { + if (c.getId().equals(contactId)) { c1 = c; } else { ContactId id = c.getId(); @@ -176,7 +173,7 @@ public class ContactChooserFragment extends BaseFragment { @Override public void run() { adapter.setLocalAuthor(localAuthorId); - if (contacts.size() == 0) list.showData(); + if (contacts.isEmpty()) list.showData(); else adapter.addAll(contacts); } }); diff --git a/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java b/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java index 8c4e6465243c87fabdb56a27fab183485025dfdd..aeada3f1d1187b954a05a2b2b719bc5d8e559435 100644 --- a/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java +++ b/briar-android/src/org/briarproject/android/introduction/IntroductionActivity.java @@ -14,6 +14,7 @@ import org.briarproject.android.ActivityComponent; import org.briarproject.android.BriarActivity; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; // TODO extend the BriarFragmentActivity ? public class IntroductionActivity extends BriarActivity implements @@ -21,16 +22,16 @@ public class IntroductionActivity extends BriarActivity implements public static final String CONTACT_ID = "briar.CONTACT_ID"; - private int contactId; + private ContactId contactId; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); - contactId = intent.getIntExtra(CONTACT_ID, -1); - if (contactId == -1) - throw new IllegalArgumentException("Wrong ContactId"); + int id = intent.getIntExtra(CONTACT_ID, -1); + if (id == -1) throw new IllegalStateException("No ContactId"); + contactId = new ContactId(id); setContentView(R.layout.activity_fragment_container); @@ -75,7 +76,7 @@ public class IntroductionActivity extends BriarActivity implements } } - int getContactId() { + ContactId getContactId() { return contactId; } diff --git a/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java b/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java index bec5b346f6e2c1dd711febfcd23a6fb790431471..9a8f592e17243a71c1d9512aff68ca08fcd11b08 100644 --- a/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java +++ b/briar-android/src/org/briarproject/android/invitation/AddContactActivity.java @@ -37,9 +37,13 @@ public class AddContactActivity extends BriarActivity private static final Logger LOG = Logger.getLogger(AddContactActivity.class.getName()); - @Inject protected CryptoComponent crypto; - @Inject protected InvitationTaskFactory invitationTaskFactory; - @Inject protected ReferenceManager referenceManager; + @Inject + CryptoComponent crypto; + @Inject + InvitationTaskFactory invitationTaskFactory; + @Inject + ReferenceManager referenceManager; + private AddContactView view = null; private InvitationTask task = null; private long taskHandle = -1; @@ -52,7 +56,8 @@ public class AddContactActivity extends BriarActivity private String contactName = null; // Fields that are accessed from background threads must be volatile - @Inject protected volatile IdentityManager identityManager; + @Inject + volatile IdentityManager identityManager; @Override public void onCreate(Bundle state) { @@ -150,8 +155,8 @@ public class AddContactActivity extends BriarActivity } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); view.populate(); } diff --git a/briar-android/src/org/briarproject/android/keyagreement/KeyAgreementActivity.java b/briar-android/src/org/briarproject/android/keyagreement/KeyAgreementActivity.java index 8ef2764cf42ea9af3fd483057b866ad38b6bcb86..8a075f151d31e2d13ef6e746f5656eb58512ee6f 100644 --- a/briar-android/src/org/briarproject/android/keyagreement/KeyAgreementActivity.java +++ b/briar-android/src/org/briarproject/android/keyagreement/KeyAgreementActivity.java @@ -40,15 +40,13 @@ public class KeyAgreementActivity extends BriarFragmentActivity implements private static final int STEP_QR = 2; @Inject - protected EventBus eventBus; - - private Toolbar toolbar; + EventBus eventBus; // Fields that are accessed from background threads must be volatile @Inject - protected volatile ContactExchangeTask contactExchangeTask; + volatile ContactExchangeTask contactExchangeTask; @Inject - protected volatile IdentityManager identityManager; + volatile IdentityManager identityManager; @Override public void injectActivity(ActivityComponent component) { @@ -61,7 +59,7 @@ public class KeyAgreementActivity extends BriarFragmentActivity implements super.onCreate(state); setContentView(R.layout.activity_plain); - toolbar = (Toolbar) findViewById(R.id.toolbar); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -83,14 +81,14 @@ public class KeyAgreementActivity extends BriarFragmentActivity implements } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); eventBus.addListener(this); } @Override - protected void onPause() { - super.onPause(); + protected void onStop() { + super.onStop(); eventBus.removeListener(this); } diff --git a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java index cef537288c3c845d24137d380994bed35b9f93d3..fc5d286b0d0406fd017fab8cca3f071c1e8b8b5c 100644 --- a/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java +++ b/briar-android/src/org/briarproject/android/keyagreement/ShowQrCodeFragment.java @@ -162,25 +162,16 @@ public class ShowQrCodeFragment extends BaseEventFragment } else { startListening(); } - } - @Override - public void onResume() { - super.onResume(); openCamera(); } - @Override - public void onPause() { - super.onPause(); - releaseCamera(); - } - @Override public void onStop() { super.onStop(); stopListening(); if (receiver != null) getActivity().unregisterReceiver(receiver); + releaseCamera(); } @UiThread diff --git a/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java b/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java index 094eeaac2b07300a3404637d0c218ec88feede21..f2f69739431231059f03631def954c9c27a45556 100644 --- a/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java +++ b/briar-android/src/org/briarproject/android/panic/PanicPreferencesFragment.java @@ -132,16 +132,16 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); getPreferenceScreen().getSharedPreferences() .registerOnSharedPreferenceChangeListener(this); showPanicApp(PanicResponder.getTriggerPackageName(getActivity())); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); getPreferenceScreen().getSharedPreferences() .unregisterOnSharedPreferenceChangeListener(this); } diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java index fb0cd8ac59136aa84e4fc01910f5c5ecffb5072a..8a6644efddaebff7b71a226c7631604e28b6f284 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -27,8 +27,8 @@ import java.util.logging.Logger; import javax.inject.Inject; -public class GroupControllerImpl - extends ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage> +public class GroupControllerImpl extends + ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage> implements GroupController { private static final Logger LOG = @@ -48,8 +48,8 @@ public class GroupControllerImpl } @Override - public void onActivityResume() { - super.onActivityResume(); + public void onActivityStart() { + super.onActivityStart(); // TODO: Add new notification manager methods for private groups } @@ -101,9 +101,8 @@ public class GroupControllerImpl @Override protected GroupMessage createLocalMessage(String body, long timestamp, @Nullable MessageId parentId, LocalAuthor author) { - return privateGroupManager - .createLocalMessage(getGroupId(), body, timestamp, parentId, - author); + return privateGroupManager.createLocalMessage(getGroupId(), body, + timestamp, parentId, author); } @Override diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java index 89b208b00e2638488760fc8a2fd6259099e72933..ac75f29cd57ef089b8cfc0a26306619093dccfd5 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java @@ -1,56 +1,51 @@ package org.briarproject.android.privategroup.list; +import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.identity.Author; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.sync.GroupId; -import org.jetbrains.annotations.NotNull; // This class is not thread-safe +@NotNullByDefault class GroupItem { private final PrivateGroup privateGroup; - private int messageCount; - private long lastUpdate; - private int unreadCount; + private int messageCount, unreadCount; + private long timestamp; private boolean dissolved; - GroupItem(@NotNull PrivateGroup privateGroup, int messageCount, - long lastUpdate, int unreadCount, boolean dissolved) { - + GroupItem(PrivateGroup privateGroup, GroupCount count, boolean dissolved) { this.privateGroup = privateGroup; - this.messageCount = messageCount; - this.lastUpdate = lastUpdate; - this.unreadCount = unreadCount; + this.messageCount = count.getMsgCount(); + this.unreadCount = count.getUnreadCount(); + this.timestamp = count.getLatestMsgTime(); this.dissolved = dissolved; } void addMessageHeader(GroupMessageHeader header) { messageCount++; - if (header.getTimestamp() > lastUpdate) { - lastUpdate = header.getTimestamp(); + if (header.getTimestamp() > timestamp) { + timestamp = header.getTimestamp(); } if (!header.isRead()) { unreadCount++; } } - @NotNull PrivateGroup getPrivateGroup() { return privateGroup; } - @NotNull GroupId getId() { return privateGroup.getId(); } - @NotNull Author getCreator() { return privateGroup.getAuthor(); } - @NotNull String getName() { return privateGroup.getName(); } @@ -63,8 +58,8 @@ class GroupItem { return messageCount; } - long getLastUpdate() { - return lastUpdate; + long getTimestamp() { + return timestamp; } int getUnreadCount() { diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java index bae48022a7e8599cf4bbeef00f069ee88ed7651c..b5588e3440b559f2fc38989c6b30745c1d8044ff 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListAdapter.java @@ -38,7 +38,7 @@ class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> { public int compare(GroupItem a, GroupItem b) { if (a == b) return 0; // The group with the latest message comes first - long aTime = a.getLastUpdate(), bTime = b.getLastUpdate(); + long aTime = a.getTimestamp(), bTime = b.getTimestamp(); if (aTime > bTime) return -1; if (aTime < bTime) return 1; // Break ties by group name @@ -50,7 +50,7 @@ class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> { @Override public boolean areContentsTheSame(GroupItem a, GroupItem b) { return a.getMessageCount() == b.getMessageCount() && - a.getLastUpdate() == b.getLastUpdate() && + a.getTimestamp() == b.getTimestamp() && a.getUnreadCount() == b.getUnreadCount() && a.isDissolved() == b.isDissolved(); } diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java index 27e8c90d4a04104df35a4e26a4bff4b1c3773a97..fbe05315d01fcb316fb57ef3f74b95e27f5d107a 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListController.java @@ -31,6 +31,7 @@ public interface GroupListController extends DbController { ResultExceptionHandler<Void, DbException> result); interface GroupListListener extends DestroyableContext { + @UiThread void onGroupMessageAdded(GroupMessageHeader header); diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java index 9db84cdcadfa143748c975dce863815075ba0d1c..efa36fe5f2079e2bc818e0d0baad8b53e428d4c7 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java @@ -8,6 +8,7 @@ import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; +import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventListener; @@ -16,6 +17,7 @@ import org.briarproject.api.event.GroupMessageAddedEvent; import org.briarproject.api.event.GroupRemovedEvent; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.lifecycle.LifecycleManager; +import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.api.sync.ClientId; @@ -29,6 +31,7 @@ import java.util.logging.Logger; import javax.inject.Inject; +import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; public class GroupListControllerImpl extends DbControllerImpl @@ -81,59 +84,77 @@ public class GroupListControllerImpl extends DbControllerImpl @CallSuper public void eventOccurred(Event e) { if (e instanceof GroupMessageAddedEvent) { - final GroupMessageAddedEvent m = (GroupMessageAddedEvent) e; - LOG.info("New group message added"); - listener.runOnUiThreadUnlessDestroyed(new Runnable() { - @Override - public void run() { - listener.onGroupMessageAdded(m.getHeader()); - } - }); + GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; + LOG.info("Private group message added"); + onGroupMessageAdded(g.getHeader()); } else if (e instanceof GroupAddedEvent) { - final GroupAddedEvent gae = (GroupAddedEvent) e; - ClientId id = gae.getGroup().getClientId(); + GroupAddedEvent g = (GroupAddedEvent) e; + ClientId id = g.getGroup().getClientId(); if (id.equals(groupManager.getClientId())) { LOG.info("Private group added"); - listener.runOnUiThreadUnlessDestroyed(new Runnable() { - @Override - public void run() { - listener.onGroupAdded(gae.getGroup().getId()); - } - }); + onGroupAdded(g.getGroup().getId()); } } else if (e instanceof GroupRemovedEvent) { - final GroupRemovedEvent gre = (GroupRemovedEvent) e; - ClientId id = gre.getGroup().getClientId(); + GroupRemovedEvent g = (GroupRemovedEvent) e; + ClientId id = g.getGroup().getClientId(); if (id.equals(groupManager.getClientId())) { LOG.info("Private group removed"); - listener.runOnUiThreadUnlessDestroyed(new Runnable() { - @Override - public void run() { - listener.onGroupRemoved(gre.getGroup().getId()); - } - }); + onGroupRemoved(g.getGroup().getId()); } } } + private void onGroupMessageAdded(final GroupMessageHeader h) { + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onGroupMessageAdded(h); + } + }); + } + + private void onGroupAdded(final GroupId g) { + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onGroupAdded(g); + } + }); + } + + private void onGroupRemoved(final GroupId g) { + listener.runOnUiThreadUnlessDestroyed(new Runnable() { + @Override + public void run() { + listener.onGroupRemoved(g); + } + }); + } + @Override public void loadGroups( final ResultExceptionHandler<Collection<GroupItem>, DbException> handler) { runOnDbThread(new Runnable() { @Override public void run() { - LOG.info("Loading groups from database..."); try { + long now = System.currentTimeMillis(); Collection<PrivateGroup> groups = groupManager.getPrivateGroups(); List<GroupItem> items = new ArrayList<>(groups.size()); for (PrivateGroup g : groups) { - GroupCount c = groupManager.getGroupCount(g.getId()); - boolean dissolved = groupManager.isDissolved(g.getId()); - items.add(new GroupItem(g, c.getMsgCount(), - c.getLatestMsgTime(), c.getUnreadCount(), - dissolved)); + try { + GroupId id = g.getId(); + GroupCount count = groupManager.getGroupCount(id); + boolean dissolved = groupManager.isDissolved(id); + items.add(new GroupItem(g, count, dissolved)); + } catch (NoSuchGroupException e) { + // Continue + } } + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading groups took " + duration + " ms"); handler.onResult(items); } catch (DbException e) { if (LOG.isLoggable(WARNING)) @@ -150,9 +171,13 @@ public class GroupListControllerImpl extends DbControllerImpl runOnDbThread(new Runnable() { @Override public void run() { - LOG.info("Removing group from database..."); try { + long now = System.currentTimeMillis(); groupManager.removePrivateGroup(g); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Removing group took " + duration + " ms"); + handler.onResult(null); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); 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 bf773c28c654d0be47ad835c51de88d40b1e1947..4489dd6bf003aa9829dc0d2fcd6d4e8ef5f438d2 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListFragment.java @@ -1,6 +1,5 @@ package org.briarproject.android.privategroup.list; -import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.UiThread; @@ -16,7 +15,6 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.fragment.BaseFragment; -import org.briarproject.android.invitation.AddContactActivity; import org.briarproject.android.privategroup.list.GroupListController.GroupListListener; import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; import org.briarproject.android.view.BriarRecyclerView; @@ -25,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; @@ -32,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(); @@ -114,7 +114,6 @@ public class GroupListFragment extends BaseFragment implements @Override public void onExceptionUi(DbException exception) { // TODO handle error - finish(); } }); } @@ -122,6 +121,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) { @@ -139,6 +139,7 @@ public class GroupListFragment extends BaseFragment implements @UiThread @Override public void onGroupRemoved(GroupId groupId) { + adapter.incrementRevision(); adapter.removeItem(groupId); } @@ -148,22 +149,25 @@ 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> result) { - if (result.isEmpty()) { - list.showData(); + public void onResultUi(Collection<GroupItem> groups) { + if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (groups.isEmpty()) list.showData(); + else adapter.addAll(groups); } else { - adapter.addAll(result); + LOG.info("Concurrent update, reloading"); + loadGroups(); } } @Override public void onExceptionUi(DbException exception) { // TODO handle this error - finish(); } }); } diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java index c7d59dd1ff12611509016cd1d77a99d17dffa4dd..7f3031aec9f66d0bacbab0ebca4b0be37a014561 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupViewHolder.java @@ -88,7 +88,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder { postCount.setTextColor( getColor(ctx, R.color.briar_text_secondary)); - long lastUpdate = group.getLastUpdate(); + long lastUpdate = group.getTimestamp(); date.setText(AndroidUtils.formatDate(ctx, lastUpdate)); date.setVisibility(VISIBLE); avatar.setProblem(false); diff --git a/briar-android/src/org/briarproject/android/report/DevReportActivity.java b/briar-android/src/org/briarproject/android/report/DevReportActivity.java index 919a0b6200a202cadc10f41f8334e38b3358a3b2..0d142757e17849f0f3069573787e6f2c91711459 100644 --- a/briar-android/src/org/briarproject/android/report/DevReportActivity.java +++ b/briar-android/src/org/briarproject/android/report/DevReportActivity.java @@ -163,8 +163,8 @@ public class DevReportActivity extends BaseCrashReportDialog } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); if (chevron.isSelected()) refresh(); } diff --git a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java index 8e270d6407ca27f62a3b4f2d411efa49023230a5..82db75adf15b2ad6ab70e893935628d68601cf9a 100644 --- a/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java +++ b/briar-android/src/org/briarproject/android/sharing/ContactSelectorFragment.java @@ -30,7 +30,6 @@ import org.briarproject.api.sync.GroupId; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.logging.Logger; @@ -57,11 +56,11 @@ public class ContactSelectorFragment extends BaseFragment implements // Fields that are accessed from background threads must be volatile @Inject - protected volatile ContactManager contactManager; + volatile ContactManager contactManager; @Inject - protected volatile IdentityManager identityManager; + volatile IdentityManager identityManager; @Inject - protected volatile ForumSharingManager forumSharingManager; + volatile ForumSharingManager forumSharingManager; private volatile GroupId groupId; @@ -91,8 +90,9 @@ public class ContactSelectorFragment extends BaseFragment implements setHasOptionsMenu(true); Bundle args = getArguments(); - groupId = new GroupId(args.getByteArray(GROUP_ID)); - if (groupId == null) throw new IllegalStateException("No GroupId"); + byte[] b = args.getByteArray(GROUP_ID); + if (b == null) throw new IllegalStateException("No GroupId"); + groupId = new GroupId(b); } @Override @@ -125,12 +125,16 @@ public class ContactSelectorFragment extends BaseFragment implements } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); + loadContacts(selectedContacts); + } - if (selectedContacts != null) - loadContacts(Collections.unmodifiableCollection(selectedContacts)); - else loadContacts(null); + @Override + public void onStop() { + super.onStop(); + adapter.clear(); + list.showProgressBar(); } @Override @@ -202,9 +206,8 @@ public class ContactSelectorFragment extends BaseFragment implements long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Load took " + duration + " ms"); - displayContacts(Collections.unmodifiableList(contacts)); + displayContacts(contacts); } catch (DbException e) { - displayContacts(Collections.<ContactListItem>emptyList()); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } @@ -216,8 +219,8 @@ public class ContactSelectorFragment extends BaseFragment implements shareActivity.runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - if (!contacts.isEmpty()) adapter.addAll(contacts); - else list.showData(); + if (contacts.isEmpty()) list.showData(); + else adapter.addAll(contacts); updateMenuItem(); } }); diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java index a6b33a0d8c601d32f06ddb3b895df849a7e6a556..772be9bf605e7dd472ffb152a2f395b360147988 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsActivity.java @@ -2,6 +2,7 @@ package org.briarproject.android.sharing; import android.content.Context; import android.os.Bundle; +import android.support.annotation.CallSuper; import android.support.v7.widget.LinearLayoutManager; import android.widget.Toast; @@ -28,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 @@ -42,7 +43,6 @@ abstract class InvitationsActivity extends BriarActivity adapter = getAdapter(this, this); - list = (BriarRecyclerView) findViewById(R.id.list); if (list != null) { list.setLayoutManager(new LinearLayoutManager(this)); @@ -51,21 +51,22 @@ abstract class InvitationsActivity extends BriarActivity } @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); eventBus.addListener(this); loadInvitations(false); } @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); eventBus.removeListener(this); adapter.clear(); list.showProgressBar(); } @Override + @CallSuper public void eventOccurred(Event e) { if (e instanceof ContactRemovedEvent) { LOG.info("Contact removed, reloading..."); @@ -83,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(); @@ -101,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 @@ -109,9 +111,13 @@ abstract class InvitationsActivity extends BriarActivity if (invitations.isEmpty()) { LOG.info("No more invitations available, finishing"); finish(); + } else if (revision == adapter.getRevision()) { + adapter.incrementRevision(); + if (clear) adapter.setItems(invitations); + else adapter.addAll(invitations); } else { - if (clear) adapter.clear(); - adapter.addAll(invitations); + 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 58e818269f87af830749632353e88d3abbe9241a..1b0e5f3bd7be5ce10c72991abb40792584d66bd0 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsBlogActivity.java @@ -29,9 +29,9 @@ public class InvitationsBlogActivity extends InvitationsActivity { // Fields that are accessed from background threads must be volatile @Inject - protected volatile BlogManager blogManager; + volatile BlogManager blogManager; @Inject - protected volatile BlogSharingManager blogSharingManager; + volatile BlogSharingManager blogSharingManager; @Override public void injectActivity(ActivityComponent component) { @@ -62,31 +62,35 @@ public class InvitationsBlogActivity extends InvitationsActivity { } } + @Override protected InvitationAdapter getAdapter(Context ctx, AvailableForumClickListener listener) { return new BlogInvitationAdapter(ctx, listener); } + @Override protected void loadInvitations(final boolean clear) { + final int revision = adapter.getRevision(); runOnDbThread(new Runnable() { @Override public void run() { - Collection<InvitationItem> invitations = new ArrayList<>(); try { + Collection<InvitationItem> invitations = new ArrayList<>(); long now = System.currentTimeMillis(); invitations.addAll(blogSharingManager.getInvitations()); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Load took " + duration + " ms"); + displayInvitations(revision, invitations, clear); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - displayInvitations(invitations, clear); } }); } + @Override protected void respondToInvitation(final InvitationItem item, final boolean accept) { runOnDbThread(new Runnable() { @@ -95,6 +99,7 @@ public class InvitationsBlogActivity extends InvitationsActivity { try { Blog b = (Blog) item.getShareable(); for (Contact c : item.getNewSharers()) { + // TODO: What happens if a contact has been removed? blogSharingManager.respondToInvitation(b, c, accept); } } catch (DbException e) { @@ -105,10 +110,12 @@ public class InvitationsBlogActivity extends InvitationsActivity { }); } + @Override protected int getAcceptRes() { return R.string.blogs_sharing_joined_toast; } + @Override protected int getDeclineRes() { return R.string.blogs_sharing_declined_toast; } diff --git a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java index 51ddcc838a73f349f5c40096b6fa089cbe9bf0fb..d64dab69f04af496aa5469aa79464c8eaa82d5a9 100644 --- a/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/InvitationsForumActivity.java @@ -29,9 +29,9 @@ public class InvitationsForumActivity extends InvitationsActivity { // Fields that are accessed from background threads must be volatile @Inject - protected volatile ForumManager forumManager; + volatile ForumManager forumManager; @Inject - protected volatile ForumSharingManager forumSharingManager; + volatile ForumSharingManager forumSharingManager; @Override public void injectActivity(ActivityComponent component) { @@ -62,31 +62,35 @@ public class InvitationsForumActivity extends InvitationsActivity { } } + @Override protected InvitationAdapter getAdapter(Context ctx, AvailableForumClickListener listener) { return new ForumInvitationAdapter(ctx, listener); } + @Override protected void loadInvitations(final boolean clear) { + final int revision = adapter.getRevision(); runOnDbThread(new Runnable() { @Override public void run() { - Collection<InvitationItem> invitations = new ArrayList<>(); try { + Collection<InvitationItem> invitations = new ArrayList<>(); long now = System.currentTimeMillis(); invitations.addAll(forumSharingManager.getInvitations()); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Load took " + duration + " ms"); + displayInvitations(revision, invitations, clear); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - displayInvitations(invitations, clear); } }); } + @Override protected void respondToInvitation(final InvitationItem item, final boolean accept) { runOnDbThread(new Runnable() { @@ -95,6 +99,7 @@ public class InvitationsForumActivity extends InvitationsActivity { try { Forum f = (Forum) item.getShareable(); for (Contact c : item.getNewSharers()) { + // TODO: What happens if a contact has been removed? forumSharingManager.respondToInvitation(f, c, accept); } } catch (DbException e) { @@ -105,10 +110,12 @@ public class InvitationsForumActivity extends InvitationsActivity { }); } + @Override protected int getAcceptRes() { return R.string.forum_joined_toast; } + @Override protected int getDeclineRes() { return R.string.forum_declined_toast; } diff --git a/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java b/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java index 969d525a3fdf2328ec9f0dea1358d0196da94778..377b10d71c52ead6583db71e6d44d92eb5048efa 100644 --- a/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java +++ b/briar-android/src/org/briarproject/android/sharing/SharingStatusActivity.java @@ -63,13 +63,21 @@ abstract class SharingStatusActivity extends BriarActivity { } @Override - public void onResume() { - super.onResume(); - + public void onStart() { + super.onStart(); loadSharedBy(); loadSharedWith(); } + @Override + public void onStop() { + super.onStop(); + sharedByAdapter.clear(); + sharedByList.showProgressBar(); + sharedWithAdapter.clear(); + sharedWithList.showProgressBar(); + } + @Override public boolean onOptionsItemSelected(final MenuItem item) { // Handle presses on the action bar items @@ -97,11 +105,11 @@ abstract class SharingStatusActivity extends BriarActivity { } private void loadSharedBy() { - dbController.runOnDbThread(new Runnable() { + runOnDbThread(new Runnable() { @Override public void run() { - List<ContactListItem> contactItems = new ArrayList<>(); try { + List<ContactListItem> contactItems = new ArrayList<>(); for (Contact c : getSharedBy()) { LocalAuthor localAuthor = identityManager .getLocalAuthor(c.getLocalAuthorId()); @@ -110,11 +118,11 @@ abstract class SharingStatusActivity extends BriarActivity { groupId, new GroupCount(0, 0, 0)); contactItems.add(item); } + displaySharedBy(contactItems); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - displaySharedBy(contactItems); } }); } @@ -123,21 +131,18 @@ abstract class SharingStatusActivity extends BriarActivity { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - if (contacts.isEmpty()) { - sharedByList.showData(); - } else { - sharedByAdapter.addAll(contacts); - } + if (contacts.isEmpty()) sharedByList.showData(); + else sharedByAdapter.addAll(contacts); } }); } private void loadSharedWith() { - dbController.runOnDbThread(new Runnable() { + runOnDbThread(new Runnable() { @Override public void run() { - List<ContactListItem> contactItems = new ArrayList<>(); try { + List<ContactListItem> contactItems = new ArrayList<>(); for (Contact c : getSharedWith()) { LocalAuthor localAuthor = identityManager .getLocalAuthor(c.getLocalAuthorId()); @@ -146,11 +151,11 @@ abstract class SharingStatusActivity extends BriarActivity { groupId, new GroupCount(0, 0, 0)); contactItems.add(item); } + displaySharedWith(contactItems); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - displaySharedWith(contactItems); } }); } @@ -159,11 +164,8 @@ abstract class SharingStatusActivity extends BriarActivity { runOnUiThreadUnlessDestroyed(new Runnable() { @Override public void run() { - if (contacts.isEmpty()) { - sharedWithList.showData(); - } else { - sharedWithAdapter.addAll(contacts); - } + if (contacts.isEmpty()) sharedWithList.showData(); + else sharedWithAdapter.addAll(contacts); } }); } diff --git a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java index f089f7c7ea148c9f7184eedfea6bd42d0fe7ccf6..c8ab7f2ba0bf173f8688939167c40db894683663 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadItemAdapter.java @@ -6,6 +6,7 @@ import android.support.annotation.UiThread; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import org.briarproject.android.util.VersionedAdapter; import org.briarproject.api.sync.MessageId; import java.util.ArrayList; @@ -16,21 +17,23 @@ import java.util.Map; import static android.support.v7.widget.RecyclerView.NO_POSITION; -@UiThread public abstract class ThreadItemAdapter<I extends ThreadItem> - extends RecyclerView.Adapter<ThreadItemViewHolder<I>> { + extends RecyclerView.Adapter<ThreadItemViewHolder<I>> + implements VersionedAdapter { static final int UNDEFINED = -1; 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 +293,17 @@ public abstract class ThreadItemAdapter<I extends ThreadItem> animatingItems.remove(item); } + @Override + public int getRevision() { + return revision; + } + + @UiThread + @Override + 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 889399525de6ab9a880511dd43789533d65b835d..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; @@ -75,7 +79,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI if (state != null) { byte[] replyIdBytes = state.getByteArray(KEY_REPLY_ID); - if(replyIdBytes != null) replyId = new MessageId(replyIdBytes); + if (replyIdBytes != null) replyId = new MessageId(replyIdBytes); } loadItems(); @@ -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(); } } @@ -131,35 +141,33 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI @CallSuper @Override - public void onResume() { - super.onResume(); + public void onStart() { + super.onStart(); list.startPeriodicUpdate(); } @CallSuper @Override - public void onPause() { - super.onPause(); + public void onStop() { + super.onStop(); list.stopPeriodicUpdate(); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); - textInput.setVisibility( - savedInstanceState.getBoolean(KEY_INPUT_VISIBILITY) ? - VISIBLE : GONE); + boolean visible = savedInstanceState.getBoolean(KEY_INPUT_VISIBILITY); + textInput.setVisibility(visible ? VISIBLE : GONE); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putBoolean(KEY_INPUT_VISIBILITY, - textInput.getVisibility() == VISIBLE); + boolean visible = textInput.getVisibility() == VISIBLE; + outState.putBoolean(KEY_INPUT_VISIBILITY, visible); ThreadItem replyItem = adapter.getReplyItem(); if (replyItem != null) { - outState.putByteArray(KEY_REPLY_ID, - replyItem.getId().getBytes()); + outState.putByteArray(KEY_REPLY_ID, replyItem.getId().getBytes()); } } @@ -273,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/threaded/ThreadListControllerImpl.java b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java index 7dd039dfee74575a790718b7f1010366901a6cf6..80a5b44ea2ab4a968dc897dcd5d6c6f517c7b84a 100644 --- a/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java +++ b/briar-android/src/org/briarproject/android/threaded/ThreadListControllerImpl.java @@ -49,8 +49,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T private final EventBus eventBus; private final Clock clock; - private final Map<MessageId, String> bodyCache = - new ConcurrentHashMap<>(); + private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>(); private volatile GroupId groupId; @@ -82,14 +81,14 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T @CallSuper @Override - public void onActivityResume() { + public void onActivityStart() { notificationManager.blockNotification(getGroupId()); eventBus.addListener(this); } @CallSuper @Override - public void onActivityPause() { + public void onActivityStop() { notificationManager.unblockNotification(getGroupId()); eventBus.removeListener(this); } @@ -127,8 +126,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T G groupItem = loadNamedGroup(); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) - LOG.info( - "Loading named group took " + duration + " ms"); + LOG.info("Loading group took " + duration + " ms"); handler.onResult(groupItem); } catch (DbException e) { if (LOG.isLoggable(WARNING)) @@ -149,7 +147,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T runOnDbThread(new Runnable() { @Override public void run() { - LOG.info("Loading items..."); try { // Load headers long now = System.currentTimeMillis(); @@ -193,8 +190,8 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T runOnDbThread(new Runnable() { @Override public void run() { - LOG.info("Loading item..."); try { + long now = System.currentTimeMillis(); String body; if (!bodyCache.containsKey(header.getId())) { body = loadMessageBody(header.getId()); @@ -202,6 +199,9 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T } else { body = bodyCache.get(header.getId()); } + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading item took " + duration + " ms"); I item = buildItem(header, body); handler.onResult(item); } catch (DbException e) { @@ -250,12 +250,16 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T @Override public void run() { try { + long now = System.currentTimeMillis(); LocalAuthor author = identityManager.getLocalAuthor(); long timestamp = getLatestTimestamp(); - timestamp = - Math.max(timestamp, clock.currentTimeMillis()); - createMessage(body, timestamp, parentId, author, - handler); + timestamp = Math.max(timestamp, clock.currentTimeMillis()); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) { + LOG.info("Loading identity and timestamp took " + + duration + " ms"); + } + createMessage(body, timestamp, parentId, author, handler); } catch (DbException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -274,8 +278,11 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T cryptoExecutor.execute(new Runnable() { @Override public void run() { - LOG.info("Creating message..."); + long now = System.currentTimeMillis(); M msg = createLocalMessage(body, timestamp, parentId, author); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Creating message took " + duration + " ms"); storePost(msg, body, handler); } }); @@ -291,7 +298,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T @Override public void run() { try { - LOG.info("Store message..."); long now = System.currentTimeMillis(); H header = addLocalMessage(msg); bodyCache.put(msg.getMessage().getId(), body); @@ -354,10 +360,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T } private void checkGroupId() { - if (groupId == null) { - throw new IllegalStateException( - "You must set the GroupId before the controller is started."); - } + if (groupId == null) throw new IllegalStateException(); } } diff --git a/briar-android/src/org/briarproject/android/util/BriarAdapter.java b/briar-android/src/org/briarproject/android/util/BriarAdapter.java index 0385c56c362027dad5ced4167a10ef574657d48d..091dcc8a1b4f2a87df741142fd1ea8fbc3cf548e 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; @@ -11,11 +12,13 @@ import java.util.Collection; import static android.support.v7.util.SortedList.INVALID_POSITION; public abstract class BriarAdapter<T, V extends ViewHolder> - extends Adapter<V> { + extends Adapter<V> implements VersionedAdapter { 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>() { @@ -75,6 +78,13 @@ public abstract class BriarAdapter<T, V extends ViewHolder> this.items.addAll(items); } + public void setItems(Collection<T> items) { + this.items.beginBatchedUpdates(); + this.items.clear(); + this.items.addAll(items); + this.items.endBatchedUpdates(); + } + @Nullable public T getItemAt(int position) { if (position == INVALID_POSITION || position >= items.size()) { @@ -103,4 +113,14 @@ public abstract class BriarAdapter<T, V extends ViewHolder> return items.size() == 0; } + @Override + public int getRevision() { + return revision; + } + + @UiThread + @Override + public void incrementRevision() { + revision++; + } } diff --git a/briar-android/src/org/briarproject/android/util/VersionedAdapter.java b/briar-android/src/org/briarproject/android/util/VersionedAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..26df190cd2e124e96facd474e506f180f0ceb951 --- /dev/null +++ b/briar-android/src/org/briarproject/android/util/VersionedAdapter.java @@ -0,0 +1,26 @@ +package org.briarproject.android.util; + +import android.support.annotation.UiThread; + +public interface VersionedAdapter { + + /** + * 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. + */ + int getRevision(); + + /** + * 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 + void incrementRevision(); + +} diff --git a/briar-api/src/org/briarproject/api/crypto/CryptoExecutor.java b/briar-api/src/org/briarproject/api/crypto/CryptoExecutor.java index 3d531c4e05f6c51d5f0270ae6b8fbc40b2226956..f1002e66b9d424cfbadc7199c65a42bc48911678 100644 --- a/briar-api/src/org/briarproject/api/crypto/CryptoExecutor.java +++ b/briar-api/src/org/briarproject/api/crypto/CryptoExecutor.java @@ -1,17 +1,25 @@ package org.briarproject.api.crypto; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; -/** Annotation for injecting the executor for long-running crypto tasks. */ +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for injecting the executor for long-running crypto tasks. Also + * used for annotating methods that should run on the crypto executor. + * <p> + * The contract of this executor is that tasks may be run concurrently, and + * submitting a task will never block. Tasks must not run indefinitely. Tasks + * submitted during shutdown are discarded. + */ @Qualifier @Target({FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) -public @interface CryptoExecutor {} +public @interface CryptoExecutor { +} diff --git a/briar-api/src/org/briarproject/api/db/DatabaseExecutor.java b/briar-api/src/org/briarproject/api/db/DatabaseExecutor.java index f682121d077dea6805e7ac4ecf99827ba3c02bab..5421f451f872684ab02305f42a0261aa87203010 100644 --- a/briar-api/src/org/briarproject/api/db/DatabaseExecutor.java +++ b/briar-api/src/org/briarproject/api/db/DatabaseExecutor.java @@ -1,21 +1,23 @@ package org.briarproject.api.db; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** - * Annotation for injecting the executor for database tasks. + * Annotation for injecting the executor for database tasks. Also used for + * annotating methods that should run on the database executor. * <p> - * The contract of this executor is that tasks are executed in the order - * they're submitted, tasks are not executed concurrently, and submitting a - * task will never block. + * The contract of this executor is that tasks are run in the order they're + * submitted, tasks are not run concurrently, and submitting a task will never + * block. Tasks must not run indefinitely. Tasks submitted during shutdown are + * discarded. */ @Qualifier @Target({ FIELD, METHOD, PARAMETER }) diff --git a/briar-api/src/org/briarproject/api/event/PrivateMessageReceivedEvent.java b/briar-api/src/org/briarproject/api/event/PrivateMessageReceivedEvent.java index 04f7f9164575eadc2021c2286676df01e32e87be..e1ddbfd1942494e2a3079ff4f28148142ecf62f1 100644 --- a/briar-api/src/org/briarproject/api/event/PrivateMessageReceivedEvent.java +++ b/briar-api/src/org/briarproject/api/event/PrivateMessageReceivedEvent.java @@ -1,5 +1,6 @@ package org.briarproject.api.event; +import org.briarproject.api.contact.ContactId; import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.sync.GroupId; @@ -9,11 +10,13 @@ import org.briarproject.api.sync.GroupId; public class PrivateMessageReceivedEvent extends Event { private final PrivateMessageHeader messageHeader; + private final ContactId contactId; private final GroupId groupId; public PrivateMessageReceivedEvent(PrivateMessageHeader messageHeader, - GroupId groupId) { + ContactId contactId, GroupId groupId) { this.messageHeader = messageHeader; + this.contactId = contactId; this.groupId = groupId; } @@ -21,6 +24,10 @@ public class PrivateMessageReceivedEvent extends Event { return messageHeader; } + public ContactId getContactId() { + return contactId; + } + public GroupId getGroupId() { return groupId; } diff --git a/briar-api/src/org/briarproject/api/lifecycle/IoExecutor.java b/briar-api/src/org/briarproject/api/lifecycle/IoExecutor.java index 02bc5bf875a941be136b0939a268028739a4d2f4..1bffc3c3cc73c9fa9f32b48c87e65d014c9f5b87 100644 --- a/briar-api/src/org/briarproject/api/lifecycle/IoExecutor.java +++ b/briar-api/src/org/briarproject/api/lifecycle/IoExecutor.java @@ -1,17 +1,25 @@ package org.briarproject.api.lifecycle; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; -/** Annotation for injecting the executor used by long-lived IO tasks. */ +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for injecting the executor for long-running IO tasks. Also used + * for annotating methods that should run on the UI executor. + * <p> + * The contract of this executor is that tasks may be run concurrently, and + * submitting a task will never block. Tasks may run indefinitely. Tasks + * submitted during shutdown are discarded. + */ @Qualifier -@Target({ FIELD, METHOD, PARAMETER }) +@Target({FIELD, METHOD, PARAMETER}) @Retention(RUNTIME) -public @interface IoExecutor {} \ No newline at end of file +public @interface IoExecutor { +} \ No newline at end of file diff --git a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java index 8a591849a24ca5643abf77ce54f12b2340cf8166..a8fa365cb71dcc8f0f7d2dfc705937d1543a0a10 100644 --- a/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java +++ b/briar-core/src/org/briarproject/messaging/MessagingManagerImpl.java @@ -102,10 +102,11 @@ class MessagingManagerImpl extends ConversationClientImpl boolean local = meta.getBoolean("local"); boolean read = meta.getBoolean(MSG_KEY_READ); PrivateMessageHeader header = new PrivateMessageHeader( - m.getId(), m.getGroupId(), timestamp, contentType, local, read, + m.getId(), groupId, timestamp, contentType, local, read, false, false); + ContactId contactId = getContactId(txn, groupId); PrivateMessageReceivedEvent event = new PrivateMessageReceivedEvent( - header, groupId); + header, contactId, groupId); txn.attach(event); trackIncomingMessage(txn, m); @@ -133,6 +134,17 @@ class MessagingManagerImpl extends ConversationClientImpl } } + private ContactId getContactId(Transaction txn, GroupId g) + throws DbException { + try { + BdfDictionary meta = + clientHelper.getGroupMetadataAsDictionary(txn, g); + return new ContactId(meta.getLong("contactId").intValue()); + } catch (FormatException e) { + throw new DbException(e); + } + } + @Override public ContactId getContactId(GroupId g) throws DbException { try {