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

View File

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

View File

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

View File

@@ -12,8 +12,6 @@ import org.briarproject.api.blogs.BlogManager;
import org.briarproject.api.blogs.BlogPostHeader;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.BlogPostAddedEvent;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.identity.IdentityManager;
@@ -47,7 +45,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
private final Map<MessageId, BlogPostHeader> headerCache =
new ConcurrentHashMap<>();
protected volatile OnBlogPostAddedListener listener;
private volatile OnBlogPostAddedListener listener;
BaseControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus,
@@ -63,9 +61,7 @@ abstract class BaseControllerImpl extends DbControllerImpl
@Override
@CallSuper
public void onStart() {
if (listener == null)
throw new IllegalStateException(
"OnBlogPostAddedListener needs to be attached");
if (listener == null) throw new IllegalStateException();
eventBus.addListener(this);
}
@@ -75,26 +71,30 @@ abstract class BaseControllerImpl extends DbControllerImpl
eventBus.removeListener(this);
}
@Override
@CallSuper
public void eventOccurred(Event e) {
if (e instanceof BlogPostAddedEvent) {
final BlogPostAddedEvent b = (BlogPostAddedEvent) e;
LOG.info("New blog post added");
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
listener.onBlogPostAdded(b.getHeader(), b.isLocal());
}
});
}
}
@Override
public void setOnBlogPostAddedListener(OnBlogPostAddedListener listener) {
this.listener = listener;
}
void onBlogPostAdded(final BlogPostHeader h, final boolean local) {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
listener.onBlogPostAdded(h, local);
}
});
}
void onBlogRemoved() {
listener.runOnUiThreadUnlessDestroyed(new Runnable() {
@Override
public void run() {
listener.onBlogRemoved();
}
});
}
@Override
public void loadBlogPosts(final GroupId groupId,
final ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,7 +13,6 @@ import org.briarproject.R;
import org.briarproject.android.util.BriarAdapter;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.identity.Author;
import org.briarproject.api.sync.GroupId;
import org.briarproject.util.StringUtils;
import im.delight.android.identicons.IdenticonDrawable;
@@ -90,17 +89,6 @@ public abstract class BaseContactListAdapter<VH extends BaseContactListAdapter.B
return INVALID_POSITION; // Not found
}
int findItemPosition(GroupId g) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
ContactListItem item = getItemAt(i);
if (item != null && item.getGroupId().equals(g)) {
return i;
}
}
return INVALID_POSITION; // Not found
}
public static class BaseContactHolder extends RecyclerView.ViewHolder {
public final ViewGroup layout;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/** Annotation for injecting the executor for long-running crypto tasks. */
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the executor for long-running crypto tasks. Also
* used for annotating methods that should run on the crypto executor.
* <p>
* The contract of this executor is that tasks may be run concurrently, and
* submitting a task will never block. Tasks must not run indefinitely. Tasks
* submitted during shutdown are discarded.
*/
@Qualifier
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
public @interface CryptoExecutor {}
public @interface CryptoExecutor {
}

View File

@@ -1,21 +1,23 @@
package org.briarproject.api.db;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the executor for database tasks.
* Annotation for injecting the executor for database tasks. Also used for
* annotating methods that should run on the database executor.
* <p>
* The contract of this executor is that tasks are executed in the order
* they're submitted, tasks are not executed concurrently, and submitting a
* task will never block.
* The contract of this executor is that tasks are run in the order they're
* submitted, tasks are not run concurrently, and submitting a task will never
* block. Tasks must not run indefinitely. Tasks submitted during shutdown are
* discarded.
*/
@Qualifier
@Target({ FIELD, METHOD, PARAMETER })

View File

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

View File

@@ -1,17 +1,25 @@
package org.briarproject.api.lifecycle;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
/** Annotation for injecting the executor used by long-lived IO tasks. */
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Annotation for injecting the executor for long-running IO tasks. Also used
* for annotating methods that should run on the UI executor.
* <p>
* The contract of this executor is that tasks may be run concurrently, and
* submitting a task will never block. Tasks may run indefinitely. Tasks
* submitted during shutdown are discarded.
*/
@Qualifier
@Target({ FIELD, METHOD, PARAMETER })
@Target({FIELD, METHOD, PARAMETER})
@Retention(RUNTIME)
public @interface IoExecutor {}
public @interface IoExecutor {
}

View File

@@ -102,10 +102,11 @@ class MessagingManagerImpl extends ConversationClientImpl
boolean local = meta.getBoolean("local");
boolean read = meta.getBoolean(MSG_KEY_READ);
PrivateMessageHeader header = new PrivateMessageHeader(
m.getId(), m.getGroupId(), timestamp, contentType, local, read,
m.getId(), groupId, timestamp, contentType, local, read,
false, false);
ContactId contactId = getContactId(txn, groupId);
PrivateMessageReceivedEvent event = new PrivateMessageReceivedEvent(
header, groupId);
header, contactId, groupId);
txn.attach(event);
trackIncomingMessage(txn, m);
@@ -133,6 +134,17 @@ class MessagingManagerImpl extends ConversationClientImpl
}
}
private ContactId getContactId(Transaction txn, GroupId g)
throws DbException {
try {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g);
return new ContactId(meta.getLong("contactId").intValue());
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public ContactId getContactId(GroupId g) throws DbException {
try {