Merge branch '705-adapter-revisions' into 'master'

Fix race conditions when updating UI from events (again)

This is my second attempt at fixing race conditions caused by updating the UI from events while background tasks are loading data from the DB. Unlike my first attempt, this one is pretty simple and doesn't require too much reasoning about possible races.

The first commit fixes a few list loading bugs I found while working on this problem, and moves the lifecycle callbacks from resume/pause to start/stop, closing #609. The second commit contains the fix for #705, which works as follows:

* Each BriarAdapter has a revision counter
* Before making a change to the adapter that could be overwritten by a background task, increment the revision
* Before starting a background task that could overwrite other changes, get the current revision
* Before applying changes from a background task that could overwrite other changes, check whether the revision has changed
* If the revision has changed, restart the background task
* Otherwise apply the changes

Closes #609. #705 remains open because the PagerAdapters for blogs need to be updated.

See merge request !356
This commit is contained in:
akwizgran
2016-10-21 10:28:02 +00:00
53 changed files with 743 additions and 584 deletions

View File

@@ -12,7 +12,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE; 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 android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS; import static org.briarproject.android.TestingConstants.PREVENT_SCREENSHOTS;
@@ -62,18 +61,18 @@ public abstract class BaseActivity extends AppCompatActivity
} }
@Override @Override
protected void onResume() { protected void onStart() {
super.onResume(); super.onStart();
for (ActivityLifecycleController alc : lifecycleControllers) { for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityResume(); alc.onActivityStart();
} }
} }
@Override @Override
protected void onPause() { protected void onStop() {
super.onPause(); super.onStop();
for (ActivityLifecycleController alc : lifecycleControllers) { for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityPause(); alc.onActivityStop();
} }
} }

View File

@@ -34,10 +34,11 @@ public abstract class BriarActivity extends BaseActivity {
Logger.getLogger(BriarActivity.class.getName()); Logger.getLogger(BriarActivity.class.getName());
@Inject @Inject
protected BriarController briarController; BriarController briarController;
// TODO remove this when the deprecated method runOnDbThread is removed
@Deprecated
@Inject @Inject
protected DbController dbController; DbController dbController;
@Override @Override
protected void onActivityResult(int request, int result, Intent data) { protected void onActivityResult(int request, int result, Intent data) {
@@ -49,8 +50,8 @@ public abstract class BriarActivity extends BaseActivity {
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
if (!briarController.hasEncryptionKey() && !isFinishing()) { if (!briarController.hasEncryptionKey() && !isFinishing()) {
Intent i = new Intent(this, PasswordActivity.class); Intent i = new Intent(this, PasswordActivity.class);
i.setFlags(FLAG_ACTIVITY_NO_ANIMATION | FLAG_ACTIVITY_SINGLE_TOP); i.setFlags(FLAG_ACTIVITY_NO_ANIMATION | FLAG_ACTIVITY_SINGLE_TOP);

View File

@@ -141,8 +141,8 @@ public class NavDrawerActivity extends BriarFragmentActivity implements
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
updateTransports(); updateTransports();
} }

View File

@@ -12,8 +12,6 @@ import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPostHeader; import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; 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.EventBus;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
@@ -47,7 +45,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
private final Map<MessageId, BlogPostHeader> headerCache = private final Map<MessageId, BlogPostHeader> headerCache =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
protected volatile OnBlogPostAddedListener listener; private volatile OnBlogPostAddedListener listener;
BaseControllerImpl(@DatabaseExecutor Executor dbExecutor, BaseControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus, LifecycleManager lifecycleManager, EventBus eventBus,
@@ -63,9 +61,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
@Override @Override
@CallSuper @CallSuper
public void onStart() { public void onStart() {
if (listener == null) if (listener == null) throw new IllegalStateException();
throw new IllegalStateException(
"OnBlogPostAddedListener needs to be attached");
eventBus.addListener(this); eventBus.addListener(this);
} }
@@ -75,26 +71,30 @@ abstract class BaseControllerImpl extends DbControllerImpl
eventBus.removeListener(this); 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 @Override
public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) { public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) {
this.listener = 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 @Override
public void loadBlogPosts(final GroupId groupId, public void loadBlogPosts(final GroupId groupId,
final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) { final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {

View File

@@ -26,6 +26,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
public class BlogControllerImpl extends BaseControllerImpl public class BlogControllerImpl extends BaseControllerImpl
@@ -50,15 +51,15 @@ public class BlogControllerImpl extends BaseControllerImpl
} }
@Override @Override
public void onActivityResume() { public void onActivityStart() {
super.onStart(); // TODO: Should be called when activity starts. #609 super.onStart();
notificationManager.blockNotification(groupId); notificationManager.blockNotification(groupId);
notificationManager.clearBlogPostNotification(groupId); notificationManager.clearBlogPostNotification(groupId);
} }
@Override @Override
public void onActivityPause() { public void onActivityStop() {
super.onStop(); // TODO: Should be called when activity stops. #609 super.onStop();
notificationManager.unblockNotification(groupId); notificationManager.unblockNotification(groupId);
} }
@@ -75,20 +76,16 @@ public class BlogControllerImpl extends BaseControllerImpl
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (groupId == null) throw new IllegalStateException(); if (groupId == null) throw new IllegalStateException();
if (e instanceof BlogPostAddedEvent) { if (e instanceof BlogPostAddedEvent) {
BlogPostAddedEvent s = (BlogPostAddedEvent) e; BlogPostAddedEvent b = (BlogPostAddedEvent) e;
if (s.getGroupId().equals(groupId)) { if (b.getGroupId().equals(groupId)) {
super.eventOccurred(e); LOG.info("Blog post added");
onBlogPostAdded(b.getHeader(), b.isLocal());
} }
} else if (e instanceof GroupRemovedEvent) { } else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent s = (GroupRemovedEvent) e; GroupRemovedEvent g = (GroupRemovedEvent) e;
if (s.getGroup().getId().equals(groupId)) { if (g.getGroup().getId().equals(groupId)) {
LOG.info("Blog removed"); LOG.info("Blog removed");
listener.runOnUiThreadUnlessDestroyed(new Runnable() { onBlogRemoved();
@Override
public void run() {
listener.onBlogRemoved();
}
});
} }
} }
} }
@@ -115,11 +112,15 @@ public class BlogControllerImpl extends BaseControllerImpl
@Override @Override
public void run() { public void run() {
try { try {
long now = System.currentTimeMillis();
LocalAuthor a = identityManager.getLocalAuthor(); LocalAuthor a = identityManager.getLocalAuthor();
Blog b = blogManager.getBlog(groupId); Blog b = blogManager.getBlog(groupId);
boolean ours = a.getId().equals(b.getAuthor().getId()); boolean ours = a.getId().equals(b.getAuthor().getId());
boolean removable = blogManager.canBeRemoved(groupId); boolean removable = blogManager.canBeRemoved(groupId);
BlogItem blog = new BlogItem(b, ours, removable); 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); handler.onResult(blog);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -138,8 +139,12 @@ public class BlogControllerImpl extends BaseControllerImpl
@Override @Override
public void run() { public void run() {
try { try {
long now = System.currentTimeMillis();
Blog b = blogManager.getBlog(groupId); Blog b = blogManager.getBlog(groupId);
blogManager.removeBlog(b); blogManager.removeBlog(b);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Removing blog took " + duration + " ms");
handler.onResult(null); handler.onResult(null);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))

View File

@@ -123,18 +123,13 @@ public class BlogFragment extends BaseFragment implements
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
loadBlog(); loadBlog();
}
@Override
public void onResume() {
super.onResume();
loadBlogPosts(false); loadBlogPosts(false);
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
} }
@@ -215,9 +210,11 @@ public class BlogFragment extends BaseFragment implements
adapter.add(post); adapter.add(post);
if (local) { if (local) {
list.scrollToPosition(0); list.scrollToPosition(0);
displaySnackbar(R.string.blogs_blog_post_created, false); displaySnackbar(R.string.blogs_blog_post_created,
false);
} else { } 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) { listener) {
@Override @Override
public void onResultUi(Collection<BlogPostItem> posts) { public void onResultUi(Collection<BlogPostItem> posts) {
if (posts.size() > 0) { if (posts.isEmpty()) {
list.showData();
} else {
adapter.addAll(posts); adapter.addAll(posts);
if (reload) list.scrollToPosition(0); if (reload) list.scrollToPosition(0);
} else {
list.showData();
} }
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.android.blogs; package org.briarproject.android.blogs;
import org.briarproject.android.controller.handler.ResultExceptionHandler; 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.Blog;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
@@ -12,6 +11,6 @@ public interface FeedController extends BaseController {
void loadBlogPosts( void loadBlogPosts(
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler); ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
void loadPersonalBlog(ResultHandler<Blog> resultHandler); void loadPersonalBlog(ResultExceptionHandler<Blog, DbException> handler);
} }

View File

@@ -2,14 +2,16 @@ package org.briarproject.android.blogs;
import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.api.AndroidNotificationManager;
import org.briarproject.android.controller.handler.ResultExceptionHandler; 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.Blog;
import org.briarproject.api.blogs.BlogManager; import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.db.NoSuchMessageException; 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.EventBus;
import org.briarproject.api.event.GroupRemovedEvent;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
@@ -52,15 +54,28 @@ public class FeedControllerImpl extends BaseControllerImpl
notificationManager.unblockAllBlogPostNotifications(); 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 @Override
public void loadBlogPosts( public void loadBlogPosts(
final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) { final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
LOG.info("Loading all blog posts...");
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
// load blog posts
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
Collection<BlogPostItem> posts = new ArrayList<>(); Collection<BlogPostItem> posts = new ArrayList<>();
for (Blog b : blogManager.getBlogs()) { for (Blog b : blogManager.getBlogs()) {
@@ -85,24 +100,23 @@ public class FeedControllerImpl extends BaseControllerImpl
} }
@Override @Override
public void loadPersonalBlog(final ResultHandler<Blog> resultHandler) { public void loadPersonalBlog(
LOG.info("Loading personal blog..."); final ResultExceptionHandler<Blog, DbException> handler) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
// load blog posts
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
Author a = identityManager.getLocalAuthor(); Author a = identityManager.getLocalAuthor();
Blog b = blogManager.getPersonalBlog(a); Blog b = blogManager.getPersonalBlog(a);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading pers. blog took " + duration + " ms"); LOG.info("Loading blog took " + duration + " ms");
resultHandler.onResult(b); handler.onResult(b);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
resultHandler.onResult(null); handler.onException(e);
} }
} }
}); });

View File

@@ -20,7 +20,6 @@ import org.briarproject.android.ActivityComponent;
import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener; import org.briarproject.android.blogs.BaseController.OnBlogPostAddedListener;
import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener; import org.briarproject.android.blogs.BlogPostAdapter.OnBlogPostClickListener;
import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.controller.handler.UiResultHandler;
import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.android.view.BriarRecyclerView;
import org.briarproject.api.blogs.Blog; import org.briarproject.api.blogs.Blog;
@@ -28,6 +27,7 @@ import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -42,6 +42,7 @@ public class FeedFragment extends BaseFragment implements
OnBlogPostClickListener, OnBlogPostAddedListener { OnBlogPostClickListener, OnBlogPostAddedListener {
public final static String TAG = FeedFragment.class.getName(); public final static String TAG = FeedFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@Inject @Inject
FeedController feedController; FeedController feedController;
@@ -99,40 +100,61 @@ public class FeedFragment extends BaseFragment implements
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
feedController.onStart(); feedController.onStart();
feedController.loadPersonalBlog( loadPersonalBlog();
new UiResultHandler<Blog>(listener) { loadBlogPosts(false);
@Override
public void onResultUi(Blog b) {
personalBlog = b;
}
});
feedController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>, DbException>(
listener) {
@Override
public void onResultUi(Collection<BlogPostItem> posts) {
if (posts.isEmpty()) {
list.showData();
} else {
adapter.addAll(posts);
}
}
@Override
public void onExceptionUi(DbException exception) {
// TODO
}
});
list.startPeriodicUpdate();
} }
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
feedController.onStop(); feedController.onStop();
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
// TODO save list position in database/preferences? // TODO save list position in database/preferences?
} }
private void loadPersonalBlog() {
feedController.loadPersonalBlog(
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 (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (clear) adapter.setItems(posts);
else adapter.addAll(posts);
if (posts.isEmpty()) list.showData();
} else {
LOG.info("Concurrent update, reloading");
loadBlogPosts(clear);
}
}
@Override
public void onExceptionUi(DbException e) {
// TODO: Decide how to handle errors in the UI
}
});
list.startPeriodicUpdate();
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.blogs_feed_actions, menu); inflater.inflate(R.menu.blogs_feed_actions, menu);
@@ -178,6 +200,7 @@ public class FeedFragment extends BaseFragment implements
listener) { listener) {
@Override @Override
public void onResultUi(BlogPostItem post) { public void onResultUi(BlogPostItem post) {
adapter.incrementRevision();
adapter.add(post); adapter.add(post);
if (local) { if (local) {
showSnackBar(R.string.blogs_blog_post_created); showSnackBar(R.string.blogs_blog_post_created);
@@ -185,6 +208,7 @@ public class FeedFragment extends BaseFragment implements
showSnackBar(R.string.blogs_blog_post_received); showSnackBar(R.string.blogs_blog_post_received);
} }
} }
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
// TODO: Decide how to handle errors in the UI // TODO: Decide how to handle errors in the UI
@@ -234,6 +258,6 @@ public class FeedFragment extends BaseFragment implements
@Override @Override
public void onBlogRemoved() { public void onBlogRemoved() {
finish(); loadBlogPosts(true);
} }
} }

View File

@@ -37,9 +37,7 @@ public class RssFeedManageActivity extends BriarActivity
private BriarRecyclerView list; private BriarRecyclerView list;
private RssFeedAdapter adapter; private RssFeedAdapter adapter;
private GroupId groupId;
// Fields that are accessed from background threads must be volatile
private volatile GroupId groupId = null;
@Inject @Inject
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@@ -65,11 +63,18 @@ public class RssFeedManageActivity extends BriarActivity
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
loadFeeds(); loadFeeds();
} }
@Override
public void onStop() {
super.onStop();
adapter.clear();
list.showProgressBar();
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
@@ -120,27 +125,43 @@ public class RssFeedManageActivity extends BriarActivity
} }
private void loadFeeds() { private void loadFeeds() {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
addFeeds(feedManager.getFeeds()); displayFeeds(revision, feedManager.getFeeds());
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
list.setEmptyText(R.string.blogs_rss_feeds_manage_error); onLoadError();
list.showData();
} }
} }
}); });
} }
private void addFeeds(final List<Feed> feeds) { private void displayFeeds(final int revision, final List<Feed> feeds) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (feeds.size() == 0) list.showData(); if (revision == adapter.getRevision()) {
else adapter.addAll(feeds); adapter.incrementRevision();
if (feeds.isEmpty()) list.showData();
else adapter.addAll(feeds);
} else {
LOG.info("Concurrent update, reloading");
loadFeeds();
}
}
});
}
private void onLoadError() {
runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
list.setEmptyText(R.string.blogs_rss_feeds_manage_error);
list.showData();
} }
}); });
} }
@@ -149,6 +170,7 @@ public class RssFeedManageActivity extends BriarActivity
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
adapter.remove(feed); adapter.remove(feed);
} }
}); });

View File

@@ -94,15 +94,15 @@ public class WriteBlogPostActivity extends BriarActivity
} }
@Override @Override
public void onPause() { public void onStart() {
super.onPause(); super.onStart();
notificationManager.unblockNotification(groupId); notificationManager.blockNotification(groupId);
} }
@Override @Override
public void onResume() { public void onStop() {
super.onResume(); super.onStop();
notificationManager.blockNotification(groupId); notificationManager.unblockNotification(groupId);
} }
@Override @Override

View File

@@ -13,7 +13,6 @@ import org.briarproject.R;
import org.briarproject.android.util.BriarAdapter; import org.briarproject.android.util.BriarAdapter;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import im.delight.android.identicons.IdenticonDrawable; import im.delight.android.identicons.IdenticonDrawable;
@@ -90,17 +89,6 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
return INVALID_POSITION; // Not found 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 static class BaseContactHolder extends RecyclerView.ViewHolder {
public final ViewGroup layout; public final ViewGroup layout;

View File

@@ -170,8 +170,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
notificationManager.blockAllContactNotifications(); notificationManager.blockAllContactNotifications();
notificationManager.clearAllContactNotifications(); notificationManager.clearAllContactNotifications();
eventBus.addListener(this); eventBus.addListener(this);
@@ -180,8 +180,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
eventBus.removeListener(this); eventBus.removeListener(this);
notificationManager.unblockAllContactNotifications(); notificationManager.unblockAllContactNotifications();
adapter.clear(); adapter.clear();
@@ -190,6 +190,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
} }
private void loadContacts() { private void loadContacts() {
final int revision = adapter.getRevision();
listener.runOnDbThread(new Runnable() { listener.runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -213,10 +214,10 @@ public class ContactListFragment extends BaseFragment implements EventListener {
// Continue // Continue
} }
} }
displayContacts(contacts);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms"); LOG.info("Full load took " + duration + " ms");
displayContacts(revision, contacts);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); 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() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (contacts.size() == 0) list.showData(); if (revision == adapter.getRevision()) {
else adapter.addAll(contacts); 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 @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof ContactStatusChangedEvent) { if (e instanceof ContactStatusChangedEvent) {
LOG.info("Contact Status changed, reloading"); ContactStatusChangedEvent c = (ContactStatusChangedEvent) e;
// is also broadcast when contact was added if (c.isActive()) {
loadContacts(); LOG.info("Contact activated, reloading");
loadContacts();
} else {
LOG.info("Contact deactivated, removing item");
removeItem(c.getContactId());
}
} else if (e instanceof ContactConnectedEvent) { } else if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId(), true); setConnected(((ContactConnectedEvent) e).getContactId(), true);
} else if (e instanceof ContactDisconnectedEvent) { } else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId(), false); setConnected(((ContactDisconnectedEvent) e).getContactId(), false);
} else if (e instanceof ContactRemovedEvent) { } else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed"); LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId()); removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof PrivateMessageReceivedEvent) { } else if (e instanceof PrivateMessageReceivedEvent) {
LOG.info("Message received, update contact"); LOG.info("Private message received, updating item");
PrivateMessageReceivedEvent p = (PrivateMessageReceivedEvent) e; PrivateMessageReceivedEvent p = (PrivateMessageReceivedEvent) e;
PrivateMessageHeader h = p.getMessageHeader(); PrivateMessageHeader h = p.getMessageHeader();
updateItem(p.getGroupId(), ConversationItem.from(h)); updateItem(p.getContactId(), ConversationItem.from(h));
} else if (e instanceof IntroductionRequestReceivedEvent) { } else if (e instanceof IntroductionRequestReceivedEvent) {
LOG.info("Introduction Request received, update contact"); LOG.info("Introduction request received, updating item");
IntroductionRequestReceivedEvent m = IntroductionRequestReceivedEvent m =
(IntroductionRequestReceivedEvent) e; (IntroductionRequestReceivedEvent) e;
IntroductionRequest ir = m.getIntroductionRequest(); IntroductionRequest ir = m.getIntroductionRequest();
updateItem(m.getContactId(), ConversationItem.from(ir)); updateItem(m.getContactId(), ConversationItem.from(ir));
} else if (e instanceof IntroductionResponseReceivedEvent) { } else if (e instanceof IntroductionResponseReceivedEvent) {
LOG.info("Introduction Response received, update contact"); LOG.info("Introduction response received, updating item");
IntroductionResponseReceivedEvent m = IntroductionResponseReceivedEvent m =
(IntroductionResponseReceivedEvent) e; (IntroductionResponseReceivedEvent) e;
IntroductionResponse ir = m.getIntroductionResponse(); IntroductionResponse ir = m.getIntroductionResponse();
updateItem(m.getContactId(), ConversationItem.from(ir)); updateItem(m.getContactId(), ConversationItem.from(ir));
} else if (e instanceof InvitationRequestReceivedEvent) { } else if (e instanceof InvitationRequestReceivedEvent) {
LOG.info("Invitation Request received, update contact"); LOG.info("Invitation request received, updating item");
InvitationRequestReceivedEvent m = (InvitationRequestReceivedEvent) e; InvitationRequestReceivedEvent m = (InvitationRequestReceivedEvent) e;
InvitationRequest ir = m.getRequest(); InvitationRequest ir = m.getRequest();
updateItem(m.getContactId(), ConversationItem.from(ir)); updateItem(m.getContactId(), ConversationItem.from(ir));
} else if (e instanceof InvitationResponseReceivedEvent) { } else if (e instanceof InvitationResponseReceivedEvent) {
LOG.info("Invitation Response received, update contact"); LOG.info("Invitation response received, updating item");
InvitationResponseReceivedEvent m = InvitationResponseReceivedEvent m =
(InvitationResponseReceivedEvent) e; (InvitationResponseReceivedEvent) e;
InvitationResponse ir = m.getResponse(); InvitationResponse ir = m.getResponse();
@@ -283,6 +296,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(c); int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) { 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) { private void removeItem(final ContactId c) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(c); int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item); if (item != null) adapter.remove(item);
@@ -322,6 +323,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(c); int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {

View File

@@ -34,7 +34,6 @@ import org.briarproject.android.view.TextInputView;
import org.briarproject.android.view.TextInputView.TextInputListener; import org.briarproject.android.view.TextInputView.TextInputListener;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.blogs.BlogSharingManager; import org.briarproject.api.blogs.BlogSharingManager;
import org.briarproject.api.clients.BaseMessageHeader;
import org.briarproject.api.clients.SessionId; import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; 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.crypto.CryptoExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.event.ContactConnectedEvent; import org.briarproject.api.event.ContactConnectedEvent;
import org.briarproject.api.event.ContactDisconnectedEvent; import org.briarproject.api.event.ContactDisconnectedEvent;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
@@ -150,7 +148,6 @@ public class ConversationActivity extends BriarActivity
private volatile ContactId contactId = null; private volatile ContactId contactId = null;
private volatile String contactName = null; private volatile String contactName = null;
private volatile byte[] contactIdenticonKey = null; private volatile byte[] contactIdenticonKey = null;
private volatile boolean connected = false;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@Override @Override
@@ -214,18 +211,19 @@ public class ConversationActivity extends BriarActivity
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
eventBus.addListener(this); eventBus.addListener(this);
notificationManager.blockNotification(groupId); notificationManager.blockNotification(groupId);
notificationManager.clearPrivateMessageNotification(groupId); notificationManager.clearPrivateMessageNotification(groupId);
loadData(); loadContactDetails();
loadMessages();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
eventBus.removeListener(this); eventBus.removeListener(this);
notificationManager.unblockNotification(groupId); notificationManager.unblockNotification(groupId);
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
@@ -277,7 +275,7 @@ public class ConversationActivity extends BriarActivity
finish(); finish();
} }
private void loadData() { private void loadContactDetails() {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -291,13 +289,10 @@ public class ConversationActivity extends BriarActivity
contactIdenticonKey = contactIdenticonKey =
contact.getAuthor().getId().getBytes(); contact.getAuthor().getId().getBytes();
} }
connected = connectionRegistry.isConnected(contactId);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading contact took " + duration + " ms"); LOG.info("Loading contact took " + duration + " ms");
displayContactDetails(); displayContactDetails();
// Load the messages here to make sure we have a contactId
loadMessages();
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
finishOnUiThread(); finishOnUiThread();
} catch (DbException e) { } catch (DbException e) {
@@ -316,7 +311,7 @@ public class ConversationActivity extends BriarActivity
new IdenticonDrawable(contactIdenticonKey)); new IdenticonDrawable(contactIdenticonKey));
toolbarTitle.setText(contactName); toolbarTitle.setText(contactName);
if (connected) { if (connectionRegistry.isConnected(contactId)) {
toolbarStatus.setImageDrawable(ContextCompat toolbarStatus.setImageDrawable(ContextCompat
.getDrawable(ConversationActivity.this, .getDrawable(ConversationActivity.this,
R.drawable.contact_online)); R.drawable.contact_online));
@@ -335,6 +330,7 @@ public class ConversationActivity extends BriarActivity
} }
private void loadMessages() { private void loadMessages() {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -359,8 +355,9 @@ public class ConversationActivity extends BriarActivity
invitations.addAll(blogInvitations); invitations.addAll(blogInvitations);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading headers took " + duration + " ms"); LOG.info("Loading messages took " + duration + " ms");
displayMessages(headers, introductions, invitations); displayMessages(revision, headers, introductions,
invitations);
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
finishOnUiThread(); finishOnUiThread();
} catch (DbException e) { } 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<IntroductionMessage> introductions,
final Collection<InvitationMessage> invitations) { final Collection<InvitationMessage> invitations) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
textInputView.setSendButtonEnabled(true); if (revision == adapter.getRevision()) {
if (headers.isEmpty() && introductions.isEmpty() && adapter.incrementRevision();
invitations.isEmpty()) { textInputView.setSendButtonEnabled(true);
// we have no messages, List<ConversationItem> items = createItems(headers,
// so let the list know to hide progress bar introductions, invitations);
list.showData(); if (items.isEmpty()) list.showData();
} else { else adapter.addAll(items);
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);
// Scroll to the bottom // Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1); 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) { private void loadMessageBody(final MessageId m) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
@@ -435,8 +439,6 @@ public class ConversationActivity extends BriarActivity
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Loading body took " + duration + " ms"); LOG.info("Loading body took " + duration + " ms");
displayMessageBody(m, body); displayMessageBody(m, body);
} catch (NoSuchMessageException e) {
// The item will be removed when we get the event
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -469,6 +471,7 @@ public class ConversationActivity extends BriarActivity
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
adapter.add(item); adapter.add(item);
// Scroll to the bottom // Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1); list.scrollToPosition(adapter.getItemCount() - 1);
@@ -496,9 +499,10 @@ public class ConversationActivity extends BriarActivity
public void run() { public void run() {
try { try {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
for (Map.Entry<MessageId, GroupId> e : unread.entrySet()) for (Map.Entry<MessageId, GroupId> e : unread.entrySet()) {
messagingManager messagingManager.setReadFlag(e.getValue(), e.getKey(),
.setReadFlag(e.getValue(), e.getKey(), true); true);
}
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Marking read took " + duration + " ms"); LOG.info("Marking read took " + duration + " ms");
@@ -525,7 +529,6 @@ public class ConversationActivity extends BriarActivity
PrivateMessageHeader h = p.getMessageHeader(); PrivateMessageHeader h = p.getMessageHeader();
addConversationItem(ConversationItem.from(h)); addConversationItem(ConversationItem.from(h));
loadMessageBody(h.getId()); loadMessageBody(h.getId());
markMessageReadIfNew(h);
} }
} else if (e instanceof MessagesSentEvent) { } else if (e instanceof MessagesSentEvent) {
MessagesSentEvent m = (MessagesSentEvent) e; MessagesSentEvent m = (MessagesSentEvent) e;
@@ -543,14 +546,12 @@ public class ConversationActivity extends BriarActivity
ContactConnectedEvent c = (ContactConnectedEvent) e; ContactConnectedEvent c = (ContactConnectedEvent) e;
if (c.getContactId().equals(contactId)) { if (c.getContactId().equals(contactId)) {
LOG.info("Contact connected"); LOG.info("Contact connected");
connected = true;
displayContactDetails(); displayContactDetails();
} }
} else if (e instanceof ContactDisconnectedEvent) { } else if (e instanceof ContactDisconnectedEvent) {
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e; ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
if (c.getContactId().equals(contactId)) { if (c.getContactId().equals(contactId)) {
LOG.info("Contact disconnected"); LOG.info("Contact disconnected");
connected = false;
displayContactDetails(); displayContactDetails();
} }
} else if (e instanceof IntroductionRequestReceivedEvent) { } else if (e instanceof IntroductionRequestReceivedEvent) {
@@ -561,7 +562,6 @@ public class ConversationActivity extends BriarActivity
IntroductionRequest ir = event.getIntroductionRequest(); IntroductionRequest ir = event.getIntroductionRequest();
ConversationItem item = new ConversationIntroductionInItem(ir); ConversationItem item = new ConversationIntroductionInItem(ir);
addConversationItem(item); addConversationItem(item);
markMessageReadIfNew(ir);
} }
} else if (e instanceof IntroductionResponseReceivedEvent) { } else if (e instanceof IntroductionResponseReceivedEvent) {
IntroductionResponseReceivedEvent event = IntroductionResponseReceivedEvent event =
@@ -572,7 +572,6 @@ public class ConversationActivity extends BriarActivity
ConversationItem item = ConversationItem item =
ConversationItem.from(this, contactName, ir); ConversationItem.from(this, contactName, ir);
addConversationItem(item); addConversationItem(item);
markMessageReadIfNew(ir);
} }
} else if (e instanceof InvitationRequestReceivedEvent) { } else if (e instanceof InvitationRequestReceivedEvent) {
InvitationRequestReceivedEvent event = InvitationRequestReceivedEvent event =
@@ -582,7 +581,6 @@ public class ConversationActivity extends BriarActivity
InvitationRequest ir = event.getRequest(); InvitationRequest ir = event.getRequest();
ConversationItem item = ConversationItem.from(ir); ConversationItem item = ConversationItem.from(ir);
addConversationItem(item); addConversationItem(item);
markMessageReadIfNew(ir);
} }
} else if (e instanceof InvitationResponseReceivedEvent) { } else if (e instanceof InvitationResponseReceivedEvent) {
InvitationResponseReceivedEvent event = InvitationResponseReceivedEvent event =
@@ -593,51 +591,16 @@ public class ConversationActivity extends BriarActivity
ConversationItem item = ConversationItem item =
ConversationItem.from(this, contactName, ir); ConversationItem.from(this, contactName, ir);
addConversationItem(item); 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, private void markMessages(final Collection<MessageId> messageIds,
final boolean sent, final boolean seen) { final boolean sent, final boolean seen) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
Set<MessageId> messages = new HashSet<>(messageIds); Set<MessageId> messages = new HashSet<>(messageIds);
SparseArray<OutgoingItem> list = adapter.getOutgoingMessages(); SparseArray<OutgoingItem> list = adapter.getOutgoingMessages();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
@@ -654,7 +617,6 @@ public class ConversationActivity extends BriarActivity
@Override @Override
public void onSendClick(String text) { public void onSendClick(String text) {
markMessagesRead();
if (text.equals("")) return; if (text.equals("")) return;
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
@@ -857,13 +819,11 @@ public class ConversationActivity extends BriarActivity
timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try { try {
if (accept) { if (accept) {
introductionManager introductionManager.acceptIntroduction(contactId,
.acceptIntroduction(contactId, sessionId, sessionId, timestamp);
timestamp);
} else { } else {
introductionManager introductionManager.declineIntroduction(contactId,
.declineIntroduction(contactId, sessionId, sessionId, timestamp);
timestamp);
} }
loadMessages(); loadMessages();
} catch (DbException | FormatException e) { } catch (DbException | FormatException e) {

View File

@@ -6,9 +6,9 @@ public interface ActivityLifecycleController {
void onActivityCreate(Activity activity); void onActivityCreate(Activity activity);
void onActivityResume(); void onActivityStart();
void onActivityPause(); void onActivityStop();
void onActivityDestroy(); void onActivityDestroy();
} }

View File

@@ -19,18 +19,18 @@ public class BriarControllerImpl implements BriarController {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(BriarControllerImpl.class.getName()); Logger.getLogger(BriarControllerImpl.class.getName());
@Inject private final BriarServiceConnection serviceConnection;
BriarServiceConnection serviceConnection; private final DatabaseConfig databaseConfig;
@Inject private final Activity activity;
DatabaseConfig databaseConfig;
@Inject
Activity activity;
private boolean bound = false; private boolean bound = false;
@Inject @Inject
public BriarControllerImpl() { BriarControllerImpl(BriarServiceConnection serviceConnection,
DatabaseConfig databaseConfig, Activity activity) {
this.serviceConnection = serviceConnection;
this.databaseConfig = databaseConfig;
this.activity = activity;
} }
@Override @Override
@@ -40,13 +40,11 @@ public class BriarControllerImpl implements BriarController {
} }
@Override @Override
@CallSuper public void onActivityStart() {
public void onActivityResume() {
} }
@Override @Override
@CallSuper public void onActivityStop() {
public void onActivityPause() {
} }
@Override @Override

View File

@@ -57,12 +57,12 @@ public class NavDrawerControllerImpl extends DbControllerImpl
} }
@Override @Override
public void onActivityResume() { public void onActivityStart() {
eventBus.addListener(this); eventBus.addListener(this);
} }
@Override @Override
public void onActivityPause() { public void onActivityStop() {
eventBus.removeListener(this); eventBus.removeListener(this);
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.android.forum; package org.briarproject.android.forum;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.LayoutRes; import android.support.annotation.LayoutRes;
@@ -98,9 +99,8 @@ public class ForumActivity extends
@Override @Override
public boolean onOptionsItemSelected(final MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
ActivityOptionsCompat options = ActivityOptionsCompat options = makeCustomAnimation(this,
makeCustomAnimation(this, android.R.anim.slide_in_left, android.R.anim.slide_in_left, android.R.anim.slide_out_right);
android.R.anim.slide_out_right);
// Handle presses on the action bar items // Handle presses on the action bar items
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_forum_compose_post: case R.id.action_forum_compose_post:
@@ -110,9 +110,8 @@ public class ForumActivity extends
Intent i2 = new Intent(this, ShareForumActivity.class); Intent i2 = new Intent(this, ShareForumActivity.class);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP); i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
i2.putExtra(GROUP_ID, groupId.getBytes()); i2.putExtra(GROUP_ID, groupId.getBytes());
ActivityCompat ActivityCompat.startActivityForResult(this, i2,
.startActivityForResult(this, i2, REQUEST_FORUM_SHARED, REQUEST_FORUM_SHARED, options.toBundle());
options.toBundle());
return true; return true;
case R.id.action_forum_sharing_status: case R.id.action_forum_sharing_status:
Intent i3 = new Intent(this, SharingStatusForumActivity.class); Intent i3 = new Intent(this, SharingStatusForumActivity.class);
@@ -146,17 +145,14 @@ public class ForumActivity extends
} }
private void showUnsubscribeDialog() { private void showUnsubscribeDialog() {
DialogInterface.OnClickListener okListener = OnClickListener okListener = new OnClickListener() {
new DialogInterface.OnClickListener() { @Override
@Override public void onClick(DialogInterface dialog, int which) {
public void onClick(final DialogInterface dialog, deleteNamedGroup();
int which) { }
deleteNamedGroup(); };
} AlertDialog.Builder builder = new AlertDialog.Builder(
}; ForumActivity.this, R.style.BriarDialogTheme);
AlertDialog.Builder builder =
new AlertDialog.Builder(ForumActivity.this,
R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.dialog_title_leave_forum)); builder.setTitle(getString(R.string.dialog_title_leave_forum));
builder.setMessage(getString(R.string.dialog_message_leave_forum)); builder.setMessage(getString(R.string.dialog_message_leave_forum));
builder.setNegativeButton(R.string.dialog_button_leave, okListener); builder.setNegativeButton(R.string.dialog_button_leave, okListener);
@@ -166,19 +162,15 @@ public class ForumActivity extends
private void deleteNamedGroup() { private void deleteNamedGroup() {
forumController.deleteNamedGroup( forumController.deleteNamedGroup(
new UiResultExceptionHandler<Void, DbException>( new UiResultExceptionHandler<Void, DbException>(this) {
ForumActivity.this) {
@Override @Override
public void onResultUi(Void v) { public void onResultUi(Void v) {
Toast.makeText(ForumActivity.this, Toast.makeText(ForumActivity.this,
R.string.forum_left_toast, R.string.forum_left_toast, LENGTH_SHORT).show();
LENGTH_SHORT)
.show();
} }
@Override @Override
public void onExceptionUi( public void onExceptionUi(DbException exception) {
DbException exception) {
// TODO proper error handling // TODO proper error handling
finish(); finish();
} }

View File

@@ -28,8 +28,8 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
public class ForumControllerImpl public class ForumControllerImpl extends
extends ThreadListControllerImpl<Forum, ForumItem, ForumPostHeader, ForumPost> ThreadListControllerImpl<Forum, ForumItem, ForumPostHeader, ForumPost>
implements ForumController { implements ForumController {
private static final Logger LOG = private static final Logger LOG =
@@ -49,8 +49,8 @@ public class ForumControllerImpl
} }
@Override @Override
public void onActivityResume() { public void onActivityStart() {
super.onActivityResume(); super.onActivityStart();
notificationManager.clearForumPostNotification(getGroupId()); notificationManager.clearForumPostNotification(getGroupId());
} }
@@ -59,7 +59,7 @@ public class ForumControllerImpl
super.eventOccurred(e); super.eventOccurred(e);
if (e instanceof ForumPostReceivedEvent) { if (e instanceof ForumPostReceivedEvent) {
final ForumPostReceivedEvent pe = (ForumPostReceivedEvent) e; ForumPostReceivedEvent pe = (ForumPostReceivedEvent) e;
if (pe.getGroupId().equals(getGroupId())) { if (pe.getGroupId().equals(getGroupId())) {
LOG.info("Forum post received, adding..."); LOG.info("Forum post received, adding...");
final ForumPostHeader fph = pe.getForumPostHeader(); final ForumPostHeader fph = pe.getForumPostHeader();
@@ -102,9 +102,8 @@ public class ForumControllerImpl
@Override @Override
protected ForumPost createLocalMessage(String body, long timestamp, protected ForumPost createLocalMessage(String body, long timestamp,
@Nullable MessageId parentId, LocalAuthor author) { @Nullable MessageId parentId, LocalAuthor author) {
return forumManager return forumManager.createLocalPost(getGroupId(), body, timestamp,
.createLocalPost(getGroupId(), body, timestamp, parentId, parentId, author);
author);
} }
@Override @Override

View File

@@ -111,9 +111,8 @@ public class ForumListFragment extends BaseEventFragment implements
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
notificationManager.blockAllForumPostNotifications(); notificationManager.blockAllForumPostNotifications();
notificationManager.clearAllForumPostNotifications(); notificationManager.clearAllForumPostNotifications();
loadForums(); loadForums();
@@ -122,9 +121,8 @@ public class ForumListFragment extends BaseEventFragment implements
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
notificationManager.unblockAllForumPostNotifications(); notificationManager.unblockAllForumPostNotifications();
adapter.clear(); adapter.clear();
list.showProgressBar(); list.showProgressBar();
@@ -152,11 +150,11 @@ public class ForumListFragment extends BaseEventFragment implements
} }
private void loadForums() { private void loadForums() {
final int revision = adapter.getRevision();
listener.runOnDbThread(new Runnable() { listener.runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
// load forums
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
Collection<ForumListItem> forums = new ArrayList<>(); Collection<ForumListItem> forums = new ArrayList<>();
for (Forum f : forumManager.getForums()) { for (Forum f : forumManager.getForums()) {
@@ -168,10 +166,10 @@ public class ForumListFragment extends BaseEventFragment implements
// Continue // Continue
} }
} }
displayForums(forums);
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Full load took " + duration + " ms"); LOG.info("Full load took " + duration + " ms");
displayForums(revision, forums);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); 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() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (forums.size() > 0) adapter.addAll(forums); if (revision == adapter.getRevision()) {
else list.showData(); 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) { } else if (e instanceof ForumPostReceivedEvent) {
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
LOG.info("Forum post added, updating..."); LOG.info("Forum post added, updating item");
updateItem(f.getGroupId(), f.getForumPostHeader()); updateItem(f.getGroupId(), f.getForumPostHeader());
} else if (e instanceof ForumInvitationReceivedEvent) { } else if (e instanceof ForumInvitationReceivedEvent) {
LOG.info("Forum invitation received, reloading available forums");
loadAvailableForums(); loadAvailableForums();
} }
} }
@@ -256,6 +262,7 @@ public class ForumListFragment extends BaseEventFragment implements
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(g); int position = adapter.findItemPosition(g);
ForumListItem item = adapter.getItemAt(position); ForumListItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {
@@ -270,6 +277,7 @@ public class ForumListFragment extends BaseEventFragment implements
listener.runOnUiThreadUnlessDestroyed(new Runnable() { listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
adapter.incrementRevision();
int position = adapter.findItemPosition(g); int position = adapter.findItemPosition(g);
ForumListItem item = adapter.getItemAt(position); ForumListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item); if (item != null) adapter.remove(item);

View File

@@ -12,14 +12,14 @@ public abstract class BaseEventFragment extends BaseFragment implements
protected volatile EventBus eventBus; protected volatile EventBus eventBus;
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
eventBus.addListener(this); eventBus.addListener(this);
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
eventBus.removeListener(this); eventBus.removeListener(this);
} }
} }

View File

@@ -44,6 +44,7 @@ public abstract class BaseFragment extends Fragment
public interface BaseFragmentListener extends DestroyableContext { public interface BaseFragmentListener extends DestroyableContext {
@Deprecated
void runOnDbThread(Runnable runnable); void runOnDbThread(Runnable runnable);
@UiThread @UiThread

View File

@@ -155,14 +155,14 @@ public class SettingsFragment extends PreferenceFragmentCompat
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
eventBus.addListener(this); eventBus.addListener(this);
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
eventBus.removeListener(this); eventBus.removeListener(this);
} }

View File

@@ -1,19 +1,19 @@
package org.briarproject.android.introduction; package org.briarproject.android.introduction;
import android.content.Context; import android.content.Context;
import android.support.annotation.UiThread;
import android.view.View; import android.view.View;
import org.briarproject.android.contact.ContactListAdapter; import org.briarproject.android.contact.ContactListAdapter;
import org.briarproject.android.contact.ContactListItem; import org.briarproject.android.contact.ContactListItem;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
public class ContactChooserAdapter extends ContactListAdapter { @UiThread
class ContactChooserAdapter extends ContactListAdapter {
private AuthorId localAuthorId; private AuthorId localAuthorId;
public ContactChooserAdapter(Context context, ContactChooserAdapter(Context context, OnItemClickListener listener) {
OnItemClickListener listener) {
super(context, listener); super(context, listener);
} }
@@ -46,7 +46,7 @@ public class ContactChooserAdapter extends ContactListAdapter {
* *
* @param authorId The ID of the local Author * @param authorId The ID of the local Author
*/ */
public void setLocalAuthor(AuthorId authorId) { void setLocalAuthor(AuthorId authorId) {
localAuthorId = authorId; localAuthorId = authorId;
notifyDataSetChanged(); notifyDataSetChanged();
} }

View File

@@ -45,18 +45,18 @@ public class ContactChooserFragment extends BaseFragment {
private IntroductionActivity introductionActivity; private IntroductionActivity introductionActivity;
private BriarRecyclerView list; private BriarRecyclerView list;
private ContactChooserAdapter adapter; private ContactChooserAdapter adapter;
private int contactId; private ContactId contactId;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
protected volatile Contact c1; volatile Contact c1;
@Inject @Inject
protected volatile ContactManager contactManager; volatile ContactManager contactManager;
@Inject @Inject
protected volatile IdentityManager identityManager; volatile IdentityManager identityManager;
@Inject @Inject
protected volatile ConversationManager conversationManager; volatile ConversationManager conversationManager;
@Inject @Inject
protected volatile ConnectionRegistry connectionRegistry; volatile ConnectionRegistry connectionRegistry;
public static ContactChooserFragment newInstance() { public static ContactChooserFragment newInstance() {
@@ -87,9 +87,7 @@ public class ContactChooserFragment extends BaseFragment {
new ContactListAdapter.OnItemClickListener() { new ContactListAdapter.OnItemClickListener() {
@Override @Override
public void onItemClick(View view, ContactListItem item) { public void onItemClick(View view, ContactListItem item) {
if (c1 == null) { if (c1 == null) throw new IllegalStateException();
throw new RuntimeException("c1 not accountExists");
}
Contact c2 = item.getContact(); Contact c2 = item.getContact();
if (!c1.getLocalAuthorId() if (!c1.getLocalAuthorId()
.equals(c2.getLocalAuthorId())) { .equals(c2.getLocalAuthorId())) {
@@ -113,15 +111,14 @@ public class ContactChooserFragment extends BaseFragment {
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
loadContacts(); loadContacts();
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
adapter.clear(); adapter.clear();
list.showProgressBar(); list.showProgressBar();
} }
@@ -145,7 +142,7 @@ public class ContactChooserFragment extends BaseFragment {
AuthorId localAuthorId = AuthorId localAuthorId =
identityManager.getLocalAuthor().getId(); identityManager.getLocalAuthor().getId();
for (Contact c : contactManager.getActiveContacts()) { for (Contact c : contactManager.getActiveContacts()) {
if (c.getId().getInt() == contactId) { if (c.getId().equals(contactId)) {
c1 = c; c1 = c;
} else { } else {
ContactId id = c.getId(); ContactId id = c.getId();
@@ -176,7 +173,7 @@ public class ContactChooserFragment extends BaseFragment {
@Override @Override
public void run() { public void run() {
adapter.setLocalAuthor(localAuthorId); adapter.setLocalAuthor(localAuthorId);
if (contacts.size() == 0) list.showData(); if (contacts.isEmpty()) list.showData();
else adapter.addAll(contacts); else adapter.addAll(contacts);
} }
}); });

View File

@@ -14,6 +14,7 @@ import org.briarproject.android.ActivityComponent;
import org.briarproject.android.BriarActivity; import org.briarproject.android.BriarActivity;
import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.fragment.BaseFragment;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId;
// TODO extend the BriarFragmentActivity ? // TODO extend the BriarFragmentActivity ?
public class IntroductionActivity extends BriarActivity implements 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"; public static final String CONTACT_ID = "briar.CONTACT_ID";
private int contactId; private ContactId contactId;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Intent intent = getIntent(); Intent intent = getIntent();
contactId = intent.getIntExtra(CONTACT_ID, -1); int id = intent.getIntExtra(CONTACT_ID, -1);
if (contactId == -1) if (id == -1) throw new IllegalStateException("No ContactId");
throw new IllegalArgumentException("Wrong ContactId"); contactId = new ContactId(id);
setContentView(R.layout.activity_fragment_container); setContentView(R.layout.activity_fragment_container);
@@ -75,7 +76,7 @@ public class IntroductionActivity extends BriarActivity implements
} }
} }
int getContactId() { ContactId getContactId() {
return contactId; return contactId;
} }

View File

@@ -37,9 +37,13 @@ public class AddContactActivity extends BriarActivity
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AddContactActivity.class.getName()); Logger.getLogger(AddContactActivity.class.getName());
@Inject protected CryptoComponent crypto; @Inject
@Inject protected InvitationTaskFactory invitationTaskFactory; CryptoComponent crypto;
@Inject protected ReferenceManager referenceManager; @Inject
InvitationTaskFactory invitationTaskFactory;
@Inject
ReferenceManager referenceManager;
private AddContactView view = null; private AddContactView view = null;
private InvitationTask task = null; private InvitationTask task = null;
private long taskHandle = -1; private long taskHandle = -1;
@@ -52,7 +56,8 @@ public class AddContactActivity extends BriarActivity
private String contactName = null; private String contactName = null;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject protected volatile IdentityManager identityManager; @Inject
volatile IdentityManager identityManager;
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
@@ -150,8 +155,8 @@ public class AddContactActivity extends BriarActivity
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
view.populate(); view.populate();
} }

View File

@@ -40,15 +40,13 @@ public class KeyAgreementActivity extends BriarFragmentActivity implements
private static final int STEP_QR = 2; private static final int STEP_QR = 2;
@Inject @Inject
protected EventBus eventBus; EventBus eventBus;
private Toolbar toolbar;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
protected volatile ContactExchangeTask contactExchangeTask; volatile ContactExchangeTask contactExchangeTask;
@Inject @Inject
protected volatile IdentityManager identityManager; volatile IdentityManager identityManager;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
@@ -61,7 +59,7 @@ public class KeyAgreementActivity extends BriarFragmentActivity implements
super.onCreate(state); super.onCreate(state);
setContentView(R.layout.activity_plain); setContentView(R.layout.activity_plain);
toolbar = (Toolbar) findViewById(R.id.toolbar); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -83,14 +81,14 @@ public class KeyAgreementActivity extends BriarFragmentActivity implements
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
eventBus.addListener(this); eventBus.addListener(this);
} }
@Override @Override
protected void onPause() { protected void onStop() {
super.onPause(); super.onStop();
eventBus.removeListener(this); eventBus.removeListener(this);
} }

View File

@@ -162,25 +162,16 @@ public class ShowQrCodeFragment extends BaseEventFragment
} else { } else {
startListening(); startListening();
} }
}
@Override
public void onResume() {
super.onResume();
openCamera(); openCamera();
} }
@Override
public void onPause() {
super.onPause();
releaseCamera();
}
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
stopListening(); stopListening();
if (receiver != null) getActivity().unregisterReceiver(receiver); if (receiver != null) getActivity().unregisterReceiver(receiver);
releaseCamera();
} }
@UiThread @UiThread

View File

@@ -132,16 +132,16 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
getPreferenceScreen().getSharedPreferences() getPreferenceScreen().getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this); .registerOnSharedPreferenceChangeListener(this);
showPanicApp(PanicResponder.getTriggerPackageName(getActivity())); showPanicApp(PanicResponder.getTriggerPackageName(getActivity()));
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
getPreferenceScreen().getSharedPreferences() getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this); .unregisterOnSharedPreferenceChangeListener(this);
} }

View File

@@ -27,8 +27,8 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
public class GroupControllerImpl public class GroupControllerImpl extends
extends ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage> ThreadListControllerImpl<PrivateGroup, GroupMessageItem, GroupMessageHeader, GroupMessage>
implements GroupController { implements GroupController {
private static final Logger LOG = private static final Logger LOG =
@@ -48,8 +48,8 @@ public class GroupControllerImpl
} }
@Override @Override
public void onActivityResume() { public void onActivityStart() {
super.onActivityResume(); super.onActivityStart();
// TODO: Add new notification manager methods for private groups // TODO: Add new notification manager methods for private groups
} }
@@ -101,9 +101,8 @@ public class GroupControllerImpl
@Override @Override
protected GroupMessage createLocalMessage(String body, long timestamp, protected GroupMessage createLocalMessage(String body, long timestamp,
@Nullable MessageId parentId, LocalAuthor author) { @Nullable MessageId parentId, LocalAuthor author) {
return privateGroupManager return privateGroupManager.createLocalMessage(getGroupId(), body,
.createLocalMessage(getGroupId(), body, timestamp, parentId, timestamp, parentId, author);
author);
} }
@Override @Override

View File

@@ -1,56 +1,51 @@
package org.briarproject.android.privategroup.list; package org.briarproject.android.privategroup.list;
import org.briarproject.api.clients.MessageTracker.GroupCount;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.nullsafety.NotNullByDefault;
import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroup;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import org.jetbrains.annotations.NotNull;
// This class is not thread-safe // This class is not thread-safe
@NotNullByDefault
class GroupItem { class GroupItem {
private final PrivateGroup privateGroup; private final PrivateGroup privateGroup;
private int messageCount; private int messageCount, unreadCount;
private long lastUpdate; private long timestamp;
private int unreadCount;
private boolean dissolved; private boolean dissolved;
GroupItem(@NotNull PrivateGroup privateGroup, int messageCount, GroupItem(PrivateGroup privateGroup, GroupCount count, boolean dissolved) {
long lastUpdate, int unreadCount, boolean dissolved) {
this.privateGroup = privateGroup; this.privateGroup = privateGroup;
this.messageCount = messageCount; this.messageCount = count.getMsgCount();
this.lastUpdate = lastUpdate; this.unreadCount = count.getUnreadCount();
this.unreadCount = unreadCount; this.timestamp = count.getLatestMsgTime();
this.dissolved = dissolved; this.dissolved = dissolved;
} }
void addMessageHeader(GroupMessageHeader header) { void addMessageHeader(GroupMessageHeader header) {
messageCount++; messageCount++;
if (header.getTimestamp() > lastUpdate) { if (header.getTimestamp() > timestamp) {
lastUpdate = header.getTimestamp(); timestamp = header.getTimestamp();
} }
if (!header.isRead()) { if (!header.isRead()) {
unreadCount++; unreadCount++;
} }
} }
@NotNull
PrivateGroup getPrivateGroup() { PrivateGroup getPrivateGroup() {
return privateGroup; return privateGroup;
} }
@NotNull
GroupId getId() { GroupId getId() {
return privateGroup.getId(); return privateGroup.getId();
} }
@NotNull
Author getCreator() { Author getCreator() {
return privateGroup.getAuthor(); return privateGroup.getAuthor();
} }
@NotNull
String getName() { String getName() {
return privateGroup.getName(); return privateGroup.getName();
} }
@@ -63,8 +58,8 @@ class GroupItem {
return messageCount; return messageCount;
} }
long getLastUpdate() { long getTimestamp() {
return lastUpdate; return timestamp;
} }
int getUnreadCount() { int getUnreadCount() {

View File

@@ -38,7 +38,7 @@ class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> {
public int compare(GroupItem a, GroupItem b) { public int compare(GroupItem a, GroupItem b) {
if (a == b) return 0; if (a == b) return 0;
// The group with the latest message comes first // 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;
if (aTime < bTime) return 1; if (aTime < bTime) return 1;
// Break ties by group name // Break ties by group name
@@ -50,7 +50,7 @@ class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> {
@Override @Override
public boolean areContentsTheSame(GroupItem a, GroupItem b) { public boolean areContentsTheSame(GroupItem a, GroupItem b) {
return a.getMessageCount() == b.getMessageCount() && return a.getMessageCount() == b.getMessageCount() &&
a.getLastUpdate() == b.getLastUpdate() && a.getTimestamp() == b.getTimestamp() &&
a.getUnreadCount() == b.getUnreadCount() && a.getUnreadCount() == b.getUnreadCount() &&
a.isDissolved() == b.isDissolved(); a.isDissolved() == b.isDissolved();
} }

View File

@@ -31,6 +31,7 @@ public interface GroupListController extends DbController {
ResultExceptionHandler<Void, DbException> result); ResultExceptionHandler<Void, DbException> result);
interface GroupListListener extends DestroyableContext { interface GroupListListener extends DestroyableContext {
@UiThread @UiThread
void onGroupMessageAdded(GroupMessageHeader header); void onGroupMessageAdded(GroupMessageHeader header);

View File

@@ -8,6 +8,7 @@ import org.briarproject.android.controller.handler.ResultExceptionHandler;
import org.briarproject.api.clients.MessageTracker.GroupCount; import org.briarproject.api.clients.MessageTracker.GroupCount;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener; 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.event.GroupRemovedEvent;
import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.lifecycle.LifecycleManager; import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.privategroup.PrivateGroup; import org.briarproject.api.privategroup.PrivateGroup;
import org.briarproject.api.privategroup.PrivateGroupManager; import org.briarproject.api.privategroup.PrivateGroupManager;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
@@ -29,6 +31,7 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
public class GroupListControllerImpl extends DbControllerImpl public class GroupListControllerImpl extends DbControllerImpl
@@ -81,59 +84,77 @@ public class GroupListControllerImpl extends DbControllerImpl
@CallSuper @CallSuper
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof GroupMessageAddedEvent) { if (e instanceof GroupMessageAddedEvent) {
final GroupMessageAddedEvent m = (GroupMessageAddedEvent) e; GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
LOG.info("New group message added"); LOG.info("Private group message added");
listener.runOnUiThreadUnlessDestroyed(new Runnable() { onGroupMessageAdded(g.getHeader());
@Override
public void run() {
listener.onGroupMessageAdded(m.getHeader());
}
});
} else if (e instanceof GroupAddedEvent) { } else if (e instanceof GroupAddedEvent) {
final GroupAddedEvent gae = (GroupAddedEvent) e; GroupAddedEvent g = (GroupAddedEvent) e;
ClientId id = gae.getGroup().getClientId(); ClientId id = g.getGroup().getClientId();
if (id.equals(groupManager.getClientId())) { if (id.equals(groupManager.getClientId())) {
LOG.info("Private group added"); LOG.info("Private group added");
listener.runOnUiThreadUnlessDestroyed(new Runnable() { onGroupAdded(g.getGroup().getId());
@Override
public void run() {
listener.onGroupAdded(gae.getGroup().getId());
}
});
} }
} else if (e instanceof GroupRemovedEvent) { } else if (e instanceof GroupRemovedEvent) {
final GroupRemovedEvent gre = (GroupRemovedEvent) e; GroupRemovedEvent g = (GroupRemovedEvent) e;
ClientId id = gre.getGroup().getClientId(); ClientId id = g.getGroup().getClientId();
if (id.equals(groupManager.getClientId())) { if (id.equals(groupManager.getClientId())) {
LOG.info("Private group removed"); LOG.info("Private group removed");
listener.runOnUiThreadUnlessDestroyed(new Runnable() { onGroupRemoved(g.getGroup().getId());
@Override
public void run() {
listener.onGroupRemoved(gre.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 @Override
public void loadGroups( public void loadGroups(
final ResultExceptionHandler<Collection<GroupItem>, DbException> handler) { final ResultExceptionHandler<Collection<GroupItem>, DbException> handler) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
LOG.info("Loading groups from database...");
try { try {
long now = System.currentTimeMillis();
Collection<PrivateGroup> groups = Collection<PrivateGroup> groups =
groupManager.getPrivateGroups(); groupManager.getPrivateGroups();
List<GroupItem> items = new ArrayList<>(groups.size()); List<GroupItem> items = new ArrayList<>(groups.size());
for (PrivateGroup g : groups) { for (PrivateGroup g : groups) {
GroupCount c = groupManager.getGroupCount(g.getId()); try {
boolean dissolved = groupManager.isDissolved(g.getId()); GroupId id = g.getId();
items.add(new GroupItem(g, c.getMsgCount(), GroupCount count = groupManager.getGroupCount(id);
c.getLatestMsgTime(), c.getUnreadCount(), boolean dissolved = groupManager.isDissolved(id);
dissolved)); 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); handler.onResult(items);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -150,9 +171,13 @@ public class GroupListControllerImpl extends DbControllerImpl
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
LOG.info("Removing group from database...");
try { try {
long now = System.currentTimeMillis();
groupManager.removePrivateGroup(g); 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) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);

View File

@@ -1,6 +1,5 @@
package org.briarproject.android.privategroup.list; package org.briarproject.android.privategroup.list;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
@@ -16,7 +15,6 @@ import org.briarproject.R;
import org.briarproject.android.ActivityComponent; import org.briarproject.android.ActivityComponent;
import org.briarproject.android.controller.handler.UiResultExceptionHandler; import org.briarproject.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.android.fragment.BaseFragment; 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.GroupListController.GroupListListener;
import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; import org.briarproject.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
import org.briarproject.android.view.BriarRecyclerView; import org.briarproject.android.view.BriarRecyclerView;
@@ -25,6 +23,7 @@ import org.briarproject.api.privategroup.GroupMessageHeader;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -32,6 +31,7 @@ public class GroupListFragment extends BaseFragment implements
GroupListListener, OnGroupRemoveClickListener { GroupListListener, OnGroupRemoveClickListener {
public final static String TAG = GroupListFragment.class.getName(); public final static String TAG = GroupListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
public static GroupListFragment newInstance() { public static GroupListFragment newInstance() {
return new GroupListFragment(); return new GroupListFragment();
@@ -114,7 +114,6 @@ public class GroupListFragment extends BaseFragment implements
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
// TODO handle error // TODO handle error
finish();
} }
}); });
} }
@@ -122,6 +121,7 @@ public class GroupListFragment extends BaseFragment implements
@UiThread @UiThread
@Override @Override
public void onGroupMessageAdded(GroupMessageHeader header) { public void onGroupMessageAdded(GroupMessageHeader header) {
adapter.incrementRevision();
int position = adapter.findItemPosition(header.getGroupId()); int position = adapter.findItemPosition(header.getGroupId());
GroupItem item = adapter.getItemAt(position); GroupItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {
@@ -139,6 +139,7 @@ public class GroupListFragment extends BaseFragment implements
@UiThread @UiThread
@Override @Override
public void onGroupRemoved(GroupId groupId) { public void onGroupRemoved(GroupId groupId) {
adapter.incrementRevision();
adapter.removeItem(groupId); adapter.removeItem(groupId);
} }
@@ -148,22 +149,25 @@ public class GroupListFragment extends BaseFragment implements
} }
private void loadGroups() { private void loadGroups() {
final int revision = adapter.getRevision();
controller.loadGroups( controller.loadGroups(
new UiResultExceptionHandler<Collection<GroupItem>, DbException>( new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
listener) { listener) {
@Override @Override
public void onResultUi(Collection<GroupItem> result) { public void onResultUi(Collection<GroupItem> groups) {
if (result.isEmpty()) { if (revision == adapter.getRevision()) {
list.showData(); adapter.incrementRevision();
if (groups.isEmpty()) list.showData();
else adapter.addAll(groups);
} else { } else {
adapter.addAll(result); LOG.info("Concurrent update, reloading");
loadGroups();
} }
} }
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
// TODO handle this error // TODO handle this error
finish();
} }
}); });
} }

View File

@@ -88,7 +88,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
postCount.setTextColor( postCount.setTextColor(
getColor(ctx, R.color.briar_text_secondary)); getColor(ctx, R.color.briar_text_secondary));
long lastUpdate = group.getLastUpdate(); long lastUpdate = group.getTimestamp();
date.setText(AndroidUtils.formatDate(ctx, lastUpdate)); date.setText(AndroidUtils.formatDate(ctx, lastUpdate));
date.setVisibility(VISIBLE); date.setVisibility(VISIBLE);
avatar.setProblem(false); avatar.setProblem(false);

View File

@@ -163,8 +163,8 @@ public class DevReportActivity extends BaseCrashReportDialog
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
if (chevron.isSelected()) refresh(); if (chevron.isSelected()) refresh();
} }

View File

@@ -30,7 +30,6 @@ import org.briarproject.api.sync.GroupId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.logging.Logger; 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 // Fields that are accessed from background threads must be volatile
@Inject @Inject
protected volatile ContactManager contactManager; volatile ContactManager contactManager;
@Inject @Inject
protected volatile IdentityManager identityManager; volatile IdentityManager identityManager;
@Inject @Inject
protected volatile ForumSharingManager forumSharingManager; volatile ForumSharingManager forumSharingManager;
private volatile GroupId groupId; private volatile GroupId groupId;
@@ -91,8 +90,9 @@ public class ContactSelectorFragment extends BaseFragment implements
setHasOptionsMenu(true); setHasOptionsMenu(true);
Bundle args = getArguments(); Bundle args = getArguments();
groupId = new GroupId(args.getByteArray(GROUP_ID)); byte[] b = args.getByteArray(GROUP_ID);
if (groupId == null) throw new IllegalStateException("No GroupId"); if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b);
} }
@Override @Override
@@ -125,12 +125,16 @@ public class ContactSelectorFragment extends BaseFragment implements
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
loadContacts(selectedContacts);
}
if (selectedContacts != null) @Override
loadContacts(Collections.unmodifiableCollection(selectedContacts)); public void onStop() {
else loadContacts(null); super.onStop();
adapter.clear();
list.showProgressBar();
} }
@Override @Override
@@ -202,9 +206,8 @@ public class ContactSelectorFragment extends BaseFragment implements
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
displayContacts(Collections.unmodifiableList(contacts)); displayContacts(contacts);
} catch (DbException e) { } catch (DbException e) {
displayContacts(Collections.<ContactListItem>emptyList());
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} }
@@ -216,8 +219,8 @@ public class ContactSelectorFragment extends BaseFragment implements
shareActivity.runOnUiThreadUnlessDestroyed(new Runnable() { shareActivity.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (!contacts.isEmpty()) adapter.addAll(contacts); if (contacts.isEmpty()) list.showData();
else list.showData(); else adapter.addAll(contacts);
updateMenuItem(); updateMenuItem();
} }
}); });

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.sharing;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.widget.Toast; import android.widget.Toast;
@@ -28,7 +29,7 @@ abstract class InvitationsActivity extends BriarActivity
protected static final Logger LOG = protected static final Logger LOG =
Logger.getLogger(InvitationsActivity.class.getName()); Logger.getLogger(InvitationsActivity.class.getName());
private InvitationAdapter adapter; protected InvitationAdapter adapter;
private BriarRecyclerView list; private BriarRecyclerView list;
@Inject @Inject
@@ -42,7 +43,6 @@ abstract class InvitationsActivity extends BriarActivity
adapter = getAdapter(this, this); adapter = getAdapter(this, this);
list = (BriarRecyclerView) findViewById(R.id.list); list = (BriarRecyclerView) findViewById(R.id.list);
if (list != null) { if (list != null) {
list.setLayoutManager(new LinearLayoutManager(this)); list.setLayoutManager(new LinearLayoutManager(this));
@@ -51,21 +51,22 @@ abstract class InvitationsActivity extends BriarActivity
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
eventBus.addListener(this); eventBus.addListener(this);
loadInvitations(false); loadInvitations(false);
} }
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
eventBus.removeListener(this); eventBus.removeListener(this);
adapter.clear(); adapter.clear();
list.showProgressBar(); list.showProgressBar();
} }
@Override @Override
@CallSuper
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) { if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, reloading..."); LOG.info("Contact removed, reloading...");
@@ -83,6 +84,7 @@ abstract class InvitationsActivity extends BriarActivity
Toast.makeText(this, res, LENGTH_SHORT).show(); Toast.makeText(this, res, LENGTH_SHORT).show();
// remove item and finish if it was the last // remove item and finish if it was the last
adapter.incrementRevision();
adapter.remove(item); adapter.remove(item);
if (adapter.getItemCount() == 0) { if (adapter.getItemCount() == 0) {
supportFinishAfterTransition(); supportFinishAfterTransition();
@@ -101,7 +103,7 @@ abstract class InvitationsActivity extends BriarActivity
abstract protected int getDeclineRes(); abstract protected int getDeclineRes();
protected void displayInvitations( protected void displayInvitations(final int revision,
final Collection<InvitationItem> invitations, final boolean clear) { final Collection<InvitationItem> invitations, final boolean clear) {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
@@ -109,9 +111,13 @@ abstract class InvitationsActivity extends BriarActivity
if (invitations.isEmpty()) { if (invitations.isEmpty()) {
LOG.info("No more invitations available, finishing"); LOG.info("No more invitations available, finishing");
finish(); finish();
} else if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (clear) adapter.setItems(invitations);
else adapter.addAll(invitations);
} else { } else {
if (clear) adapter.clear(); LOG.info("Concurrent update, reloading");
adapter.addAll(invitations); loadInvitations(clear);
} }
} }
}); });

View File

@@ -29,9 +29,9 @@ public class InvitationsBlogActivity extends InvitationsActivity {
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
protected volatile BlogManager blogManager; volatile BlogManager blogManager;
@Inject @Inject
protected volatile BlogSharingManager blogSharingManager; volatile BlogSharingManager blogSharingManager;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
@@ -62,31 +62,35 @@ public class InvitationsBlogActivity extends InvitationsActivity {
} }
} }
@Override
protected InvitationAdapter getAdapter(Context ctx, protected InvitationAdapter getAdapter(Context ctx,
AvailableForumClickListener listener) { AvailableForumClickListener listener) {
return new BlogInvitationAdapter(ctx, listener); return new BlogInvitationAdapter(ctx, listener);
} }
@Override
protected void loadInvitations(final boolean clear) { protected void loadInvitations(final boolean clear) {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Collection<InvitationItem> invitations = new ArrayList<>();
try { try {
Collection<InvitationItem> invitations = new ArrayList<>();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
invitations.addAll(blogSharingManager.getInvitations()); invitations.addAll(blogSharingManager.getInvitations());
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
displayInvitations(revision, invitations, clear);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} }
displayInvitations(invitations, clear);
} }
}); });
} }
@Override
protected void respondToInvitation(final InvitationItem item, protected void respondToInvitation(final InvitationItem item,
final boolean accept) { final boolean accept) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@@ -95,6 +99,7 @@ public class InvitationsBlogActivity extends InvitationsActivity {
try { try {
Blog b = (Blog) item.getShareable(); Blog b = (Blog) item.getShareable();
for (Contact c : item.getNewSharers()) { for (Contact c : item.getNewSharers()) {
// TODO: What happens if a contact has been removed?
blogSharingManager.respondToInvitation(b, c, accept); blogSharingManager.respondToInvitation(b, c, accept);
} }
} catch (DbException e) { } catch (DbException e) {
@@ -105,10 +110,12 @@ public class InvitationsBlogActivity extends InvitationsActivity {
}); });
} }
@Override
protected int getAcceptRes() { protected int getAcceptRes() {
return R.string.blogs_sharing_joined_toast; return R.string.blogs_sharing_joined_toast;
} }
@Override
protected int getDeclineRes() { protected int getDeclineRes() {
return R.string.blogs_sharing_declined_toast; return R.string.blogs_sharing_declined_toast;
} }

View File

@@ -29,9 +29,9 @@ public class InvitationsForumActivity extends InvitationsActivity {
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
protected volatile ForumManager forumManager; volatile ForumManager forumManager;
@Inject @Inject
protected volatile ForumSharingManager forumSharingManager; volatile ForumSharingManager forumSharingManager;
@Override @Override
public void injectActivity(ActivityComponent component) { public void injectActivity(ActivityComponent component) {
@@ -62,31 +62,35 @@ public class InvitationsForumActivity extends InvitationsActivity {
} }
} }
@Override
protected InvitationAdapter getAdapter(Context ctx, protected InvitationAdapter getAdapter(Context ctx,
AvailableForumClickListener listener) { AvailableForumClickListener listener) {
return new ForumInvitationAdapter(ctx, listener); return new ForumInvitationAdapter(ctx, listener);
} }
@Override
protected void loadInvitations(final boolean clear) { protected void loadInvitations(final boolean clear) {
final int revision = adapter.getRevision();
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
Collection<InvitationItem> invitations = new ArrayList<>();
try { try {
Collection<InvitationItem> invitations = new ArrayList<>();
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
invitations.addAll(forumSharingManager.getInvitations()); invitations.addAll(forumSharingManager.getInvitations());
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Load took " + duration + " ms"); LOG.info("Load took " + duration + " ms");
displayInvitations(revision, invitations, clear);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} }
displayInvitations(invitations, clear);
} }
}); });
} }
@Override
protected void respondToInvitation(final InvitationItem item, protected void respondToInvitation(final InvitationItem item,
final boolean accept) { final boolean accept) {
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@@ -95,6 +99,7 @@ public class InvitationsForumActivity extends InvitationsActivity {
try { try {
Forum f = (Forum) item.getShareable(); Forum f = (Forum) item.getShareable();
for (Contact c : item.getNewSharers()) { for (Contact c : item.getNewSharers()) {
// TODO: What happens if a contact has been removed?
forumSharingManager.respondToInvitation(f, c, accept); forumSharingManager.respondToInvitation(f, c, accept);
} }
} catch (DbException e) { } catch (DbException e) {
@@ -105,10 +110,12 @@ public class InvitationsForumActivity extends InvitationsActivity {
}); });
} }
@Override
protected int getAcceptRes() { protected int getAcceptRes() {
return R.string.forum_joined_toast; return R.string.forum_joined_toast;
} }
@Override
protected int getDeclineRes() { protected int getDeclineRes() {
return R.string.forum_declined_toast; return R.string.forum_declined_toast;
} }

View File

@@ -63,13 +63,21 @@ abstract class SharingStatusActivity extends BriarActivity {
} }
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
loadSharedBy(); loadSharedBy();
loadSharedWith(); loadSharedWith();
} }
@Override
public void onStop() {
super.onStop();
sharedByAdapter.clear();
sharedByList.showProgressBar();
sharedWithAdapter.clear();
sharedWithList.showProgressBar();
}
@Override @Override
public boolean onOptionsItemSelected(final MenuItem item) { public boolean onOptionsItemSelected(final MenuItem item) {
// Handle presses on the action bar items // Handle presses on the action bar items
@@ -97,11 +105,11 @@ abstract class SharingStatusActivity extends BriarActivity {
} }
private void loadSharedBy() { private void loadSharedBy() {
dbController.runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try { try {
List<ContactListItem> contactItems = new ArrayList<>();
for (Contact c : getSharedBy()) { for (Contact c : getSharedBy()) {
LocalAuthor localAuthor = identityManager LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId()); .getLocalAuthor(c.getLocalAuthorId());
@@ -110,11 +118,11 @@ abstract class SharingStatusActivity extends BriarActivity {
groupId, new GroupCount(0, 0, 0)); groupId, new GroupCount(0, 0, 0));
contactItems.add(item); contactItems.add(item);
} }
displaySharedBy(contactItems);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} }
displaySharedBy(contactItems);
} }
}); });
} }
@@ -123,21 +131,18 @@ abstract class SharingStatusActivity extends BriarActivity {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (contacts.isEmpty()) { if (contacts.isEmpty()) sharedByList.showData();
sharedByList.showData(); else sharedByAdapter.addAll(contacts);
} else {
sharedByAdapter.addAll(contacts);
}
} }
}); });
} }
private void loadSharedWith() { private void loadSharedWith() {
dbController.runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
List<ContactListItem> contactItems = new ArrayList<>();
try { try {
List<ContactListItem> contactItems = new ArrayList<>();
for (Contact c : getSharedWith()) { for (Contact c : getSharedWith()) {
LocalAuthor localAuthor = identityManager LocalAuthor localAuthor = identityManager
.getLocalAuthor(c.getLocalAuthorId()); .getLocalAuthor(c.getLocalAuthorId());
@@ -146,11 +151,11 @@ abstract class SharingStatusActivity extends BriarActivity {
groupId, new GroupCount(0, 0, 0)); groupId, new GroupCount(0, 0, 0));
contactItems.add(item); contactItems.add(item);
} }
displaySharedWith(contactItems);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
} }
displaySharedWith(contactItems);
} }
}); });
} }
@@ -159,11 +164,8 @@ abstract class SharingStatusActivity extends BriarActivity {
runOnUiThreadUnlessDestroyed(new Runnable() { runOnUiThreadUnlessDestroyed(new Runnable() {
@Override @Override
public void run() { public void run() {
if (contacts.isEmpty()) { if (contacts.isEmpty()) sharedWithList.showData();
sharedWithList.showData(); else sharedWithAdapter.addAll(contacts);
} else {
sharedWithAdapter.addAll(contacts);
}
} }
}); });
} }

View File

@@ -6,6 +6,7 @@ import android.support.annotation.UiThread;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import org.briarproject.android.util.VersionedAdapter;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
import java.util.ArrayList; import java.util.ArrayList;
@@ -16,21 +17,23 @@ import java.util.Map;
import static android.support.v7.widget.RecyclerView.NO_POSITION; import static android.support.v7.widget.RecyclerView.NO_POSITION;
@UiThread
public abstract class ThreadItemAdapter<I extends ThreadItem> public abstract class ThreadItemAdapter<I extends ThreadItem>
extends RecyclerView.Adapter<ThreadItemViewHolder<I>> { extends RecyclerView.Adapter<ThreadItemViewHolder<I>>
implements VersionedAdapter {
static final int UNDEFINED = -1; static final int UNDEFINED = -1;
private final NestedTreeList<I> items = new NestedTreeList<>(); private final NestedTreeList<I> items = new NestedTreeList<>();
private final Map<I, ValueAnimator> animatingItems = new HashMap<>(); private final Map<I, ValueAnimator> animatingItems = new HashMap<>();
private final ThreadItemListener<I> listener;
private final LinearLayoutManager layoutManager;
// highlight not dependant on time // highlight not dependant on time
private I replyItem; private I replyItem;
// temporary highlight // temporary highlight
private I addedItem; private I addedItem;
private final ThreadItemListener<I> listener; private volatile int revision = 0;
private final LinearLayoutManager layoutManager;
public ThreadItemAdapter(ThreadItemListener<I> listener, public ThreadItemAdapter(ThreadItemListener<I> listener,
LinearLayoutManager layoutManager) { LinearLayoutManager layoutManager) {
@@ -290,6 +293,17 @@ public abstract class ThreadItemAdapter<I extends ThreadItem>
animatingItems.remove(item); animatingItems.remove(item);
} }
@Override
public int getRevision() {
return revision;
}
@UiThread
@Override
public void incrementRevision() {
revision++;
}
protected interface ThreadItemListener<I> { protected interface ThreadItemListener<I> {
void onItemVisible(I item); void onItemVisible(I item);

View File

@@ -29,6 +29,7 @@ import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils; import org.briarproject.util.StringUtils;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Logger;
import static android.support.design.widget.Snackbar.make; import static android.support.design.widget.Snackbar.make;
import static android.view.View.GONE; 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_INPUT_VISIBILITY = "inputVisibility";
protected static final String KEY_REPLY_ID = "replyId"; protected static final String KEY_REPLY_ID = "replyId";
private static final Logger LOG =
Logger.getLogger(ThreadListActivity.class.getName());
protected A adapter; protected A adapter;
protected BriarRecyclerView list; protected BriarRecyclerView list;
protected TextInputView textInput; protected TextInputView textInput;
@@ -75,7 +79,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
if (state != null) { if (state != null) {
byte[] replyIdBytes = state.getByteArray(KEY_REPLY_ID); byte[] replyIdBytes = state.getByteArray(KEY_REPLY_ID);
if(replyIdBytes != null) replyId = new MessageId(replyIdBytes); if (replyIdBytes != null) replyId = new MessageId(replyIdBytes);
} }
loadItems(); loadItems();
@@ -106,18 +110,24 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
protected abstract void onNamedGroupLoaded(G groupItem); protected abstract void onNamedGroupLoaded(G groupItem);
private void loadItems() { private void loadItems() {
final int revision = adapter.getRevision();
getController().loadItems( getController().loadItems(
new UiResultExceptionHandler<Collection<I>, DbException>( new UiResultExceptionHandler<Collection<I>, DbException>(this) {
this) {
@Override @Override
public void onResultUi(Collection<I> items) { public void onResultUi(Collection<I> items) {
if (items.isEmpty()) { if (revision == adapter.getRevision()) {
list.showData(); adapter.incrementRevision();
if (items.isEmpty()) {
list.showData();
} else {
adapter.setItems(items);
list.showData();
if (replyId != null)
adapter.setReplyItemById(replyId);
}
} else { } else {
adapter.setItems(items); LOG.info("Concurrent update, reloading");
list.showData(); loadItems();
if (replyId != null)
adapter.setReplyItemById(replyId);
} }
} }
@@ -131,35 +141,33 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
@CallSuper @CallSuper
@Override @Override
public void onResume() { public void onStart() {
super.onResume(); super.onStart();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
@CallSuper @CallSuper
@Override @Override
public void onPause() { public void onStop() {
super.onPause(); super.onStop();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
} }
@Override @Override
protected void onRestoreInstanceState(Bundle savedInstanceState) { protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState); super.onRestoreInstanceState(savedInstanceState);
textInput.setVisibility( boolean visible = savedInstanceState.getBoolean(KEY_INPUT_VISIBILITY);
savedInstanceState.getBoolean(KEY_INPUT_VISIBILITY) ? textInput.setVisibility(visible ? VISIBLE : GONE);
VISIBLE : GONE);
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putBoolean(KEY_INPUT_VISIBILITY, boolean visible = textInput.getVisibility() == VISIBLE;
textInput.getVisibility() == VISIBLE); outState.putBoolean(KEY_INPUT_VISIBILITY, visible);
ThreadItem replyItem = adapter.getReplyItem(); ThreadItem replyItem = adapter.getReplyItem();
if (replyItem != null) { if (replyItem != null) {
outState.putByteArray(KEY_REPLY_ID, outState.putByteArray(KEY_REPLY_ID, replyItem.getId().getBytes());
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) { protected void addItem(final I item, boolean isLocal) {
adapter.incrementRevision();
adapter.add(item); adapter.add(item);
if (isLocal && adapter.isVisible(item)) { if (isLocal && adapter.isVisible(item)) {
displaySnackbarShort(getItemPostedString()); displaySnackbarShort(getItemPostedString());

View File

@@ -49,8 +49,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
private final EventBus eventBus; private final EventBus eventBus;
private final Clock clock; private final Clock clock;
private final Map<MessageId, String> bodyCache = private final Map<MessageId, String> bodyCache = new ConcurrentHashMap<>();
new ConcurrentHashMap<>();
private volatile GroupId groupId; private volatile GroupId groupId;
@@ -82,14 +81,14 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@CallSuper @CallSuper
@Override @Override
public void onActivityResume() { public void onActivityStart() {
notificationManager.blockNotification(getGroupId()); notificationManager.blockNotification(getGroupId());
eventBus.addListener(this); eventBus.addListener(this);
} }
@CallSuper @CallSuper
@Override @Override
public void onActivityPause() { public void onActivityStop() {
notificationManager.unblockNotification(getGroupId()); notificationManager.unblockNotification(getGroupId());
eventBus.removeListener(this); eventBus.removeListener(this);
} }
@@ -127,8 +126,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
G groupItem = loadNamedGroup(); G groupItem = loadNamedGroup();
long duration = System.currentTimeMillis() - now; long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info( LOG.info("Loading group took " + duration + " ms");
"Loading named group took " + duration + " ms");
handler.onResult(groupItem); handler.onResult(groupItem);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -149,7 +147,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
LOG.info("Loading items...");
try { try {
// Load headers // Load headers
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@@ -193,8 +190,8 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
runOnDbThread(new Runnable() { runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
LOG.info("Loading item...");
try { try {
long now = System.currentTimeMillis();
String body; String body;
if (!bodyCache.containsKey(header.getId())) { if (!bodyCache.containsKey(header.getId())) {
body = loadMessageBody(header.getId()); body = loadMessageBody(header.getId());
@@ -202,6 +199,9 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
} else { } else {
body = bodyCache.get(header.getId()); 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); I item = buildItem(header, body);
handler.onResult(item); handler.onResult(item);
} catch (DbException e) { } catch (DbException e) {
@@ -250,12 +250,16 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@Override @Override
public void run() { public void run() {
try { try {
long now = System.currentTimeMillis();
LocalAuthor author = identityManager.getLocalAuthor(); LocalAuthor author = identityManager.getLocalAuthor();
long timestamp = getLatestTimestamp(); long timestamp = getLatestTimestamp();
timestamp = timestamp = Math.max(timestamp, clock.currentTimeMillis());
Math.max(timestamp, clock.currentTimeMillis()); long duration = System.currentTimeMillis() - now;
createMessage(body, timestamp, parentId, author, if (LOG.isLoggable(INFO)) {
handler); LOG.info("Loading identity and timestamp took " +
duration + " ms");
}
createMessage(body, timestamp, parentId, author, handler);
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);
@@ -274,8 +278,11 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
cryptoExecutor.execute(new Runnable() { cryptoExecutor.execute(new Runnable() {
@Override @Override
public void run() { public void run() {
LOG.info("Creating message..."); long now = System.currentTimeMillis();
M msg = createLocalMessage(body, timestamp, parentId, author); 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); storePost(msg, body, handler);
} }
}); });
@@ -291,7 +298,6 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
@Override @Override
public void run() { public void run() {
try { try {
LOG.info("Store message...");
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
H header = addLocalMessage(msg); H header = addLocalMessage(msg);
bodyCache.put(msg.getMessage().getId(), body); bodyCache.put(msg.getMessage().getId(), body);
@@ -354,10 +360,7 @@ public abstract class ThreadListControllerImpl<G extends NamedGroup, I extends T
} }
private void checkGroupId() { private void checkGroupId() {
if (groupId == null) { if (groupId == null) throw new IllegalStateException();
throw new IllegalStateException(
"You must set the GroupId before the controller is started.");
}
} }
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.android.util;
import android.content.Context; import android.content.Context;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.util.SortedList; import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView.Adapter; import android.support.v7.widget.RecyclerView.Adapter;
import android.support.v7.widget.RecyclerView.ViewHolder; import android.support.v7.widget.RecyclerView.ViewHolder;
@@ -11,11 +12,13 @@ import java.util.Collection;
import static android.support.v7.util.SortedList.INVALID_POSITION; import static android.support.v7.util.SortedList.INVALID_POSITION;
public abstract class BriarAdapter<T, V extends ViewHolder> public abstract class BriarAdapter<T, V extends ViewHolder>
extends Adapter<V> { extends Adapter<V> implements VersionedAdapter {
protected final Context ctx; protected final Context ctx;
protected final SortedList<T> items; protected final SortedList<T> items;
private volatile int revision = 0;
public BriarAdapter(Context ctx, Class<T> c) { public BriarAdapter(Context ctx, Class<T> c) {
this.ctx = ctx; this.ctx = ctx;
this.items = new SortedList<>(c, new SortedList.Callback<T>() { 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); this.items.addAll(items);
} }
public void setItems(Collection<T> items) {
this.items.beginBatchedUpdates();
this.items.clear();
this.items.addAll(items);
this.items.endBatchedUpdates();
}
@Nullable @Nullable
public T getItemAt(int position) { public T getItemAt(int position) {
if (position == INVALID_POSITION || position >= items.size()) { if (position == INVALID_POSITION || position >= items.size()) {
@@ -103,4 +113,14 @@ public abstract class BriarAdapter<T, V extends ViewHolder>
return items.size() == 0; return items.size() == 0;
} }
@Override
public int getRevision() {
return revision;
}
@UiThread
@Override
public void incrementRevision() {
revision++;
}
} }

View File

@@ -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();
}

View File

@@ -1,17 +1,25 @@
package org.briarproject.api.crypto; 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.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import javax.inject.Qualifier; 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 @Qualifier
@Target({FIELD, METHOD, PARAMETER}) @Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME) @Retention(RUNTIME)
public @interface CryptoExecutor {} public @interface CryptoExecutor {
}

View File

@@ -1,21 +1,23 @@
package org.briarproject.api.db; 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.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import javax.inject.Qualifier; 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> * <p>
* The contract of this executor is that tasks are executed in the order * The contract of this executor is that tasks are run in the order they're
* they're submitted, tasks are not executed concurrently, and submitting a * submitted, tasks are not run concurrently, and submitting a task will never
* task will never block. * block. Tasks must not run indefinitely. Tasks submitted during shutdown are
* discarded.
*/ */
@Qualifier @Qualifier
@Target({ FIELD, METHOD, PARAMETER }) @Target({ FIELD, METHOD, PARAMETER })

View File

@@ -1,5 +1,6 @@
package org.briarproject.api.event; package org.briarproject.api.event;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.GroupId;
@@ -9,11 +10,13 @@ import org.briarproject.api.sync.GroupId;
public class PrivateMessageReceivedEvent extends Event { public class PrivateMessageReceivedEvent extends Event {
private final PrivateMessageHeader messageHeader; private final PrivateMessageHeader messageHeader;
private final ContactId contactId;
private final GroupId groupId; private final GroupId groupId;
public PrivateMessageReceivedEvent(PrivateMessageHeader messageHeader, public PrivateMessageReceivedEvent(PrivateMessageHeader messageHeader,
GroupId groupId) { ContactId contactId, GroupId groupId) {
this.messageHeader = messageHeader; this.messageHeader = messageHeader;
this.contactId = contactId;
this.groupId = groupId; this.groupId = groupId;
} }
@@ -21,6 +24,10 @@ public class PrivateMessageReceivedEvent extends Event {
return messageHeader; return messageHeader;
} }
public ContactId getContactId() {
return contactId;
}
public GroupId getGroupId() { public GroupId getGroupId() {
return groupId; return groupId;
} }

View File

@@ -1,17 +1,25 @@
package org.briarproject.api.lifecycle; 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.Retention;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import javax.inject.Qualifier; 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 @Qualifier
@Target({ FIELD, METHOD, PARAMETER }) @Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME) @Retention(RUNTIME)
public @interface IoExecutor {} public @interface IoExecutor {
}

View File

@@ -102,10 +102,11 @@ class MessagingManagerImpl extends ConversationClientImpl
boolean local = meta.getBoolean("local"); boolean local = meta.getBoolean("local");
boolean read = meta.getBoolean(MSG_KEY_READ); boolean read = meta.getBoolean(MSG_KEY_READ);
PrivateMessageHeader header = new PrivateMessageHeader( PrivateMessageHeader header = new PrivateMessageHeader(
m.getId(), m.getGroupId(), timestamp, contentType, local, read, m.getId(), groupId, timestamp, contentType, local, read,
false, false); false, false);
ContactId contactId = getContactId(txn, groupId);
PrivateMessageReceivedEvent event = new PrivateMessageReceivedEvent( PrivateMessageReceivedEvent event = new PrivateMessageReceivedEvent(
header, groupId); header, contactId, groupId);
txn.attach(event); txn.attach(event);
trackIncomingMessage(txn, m); 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 @Override
public ContactId getContactId(GroupId g) throws DbException { public ContactId getContactId(GroupId g) throws DbException {
try { try {