diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java index ad9f08b4612b60cc71d5ecfcf590a2a8b4692b70..b012ac39379c457ae28f1a168d8ef5dd48065023 100644 --- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java +++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java @@ -4,13 +4,8 @@ import static android.view.Gravity.CENTER_HORIZONTAL; import static android.widget.LinearLayout.VERTICAL; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; -import static net.sf.briar.api.Rating.UNRATED; import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -21,7 +16,6 @@ import net.sf.briar.android.BriarService; import net.sf.briar.android.BriarService.BriarServiceConnection; import net.sf.briar.android.widgets.CommonLayoutParams; import net.sf.briar.android.widgets.HorizontalBorder; -import net.sf.briar.api.Rating; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseExecutor; import net.sf.briar.api.db.DbException; @@ -34,10 +28,7 @@ import net.sf.briar.api.db.event.MessageExpiredEvent; import net.sf.briar.api.db.event.RatingChangedEvent; import net.sf.briar.api.db.event.SubscriptionRemovedEvent; import net.sf.briar.api.messaging.Author; -import net.sf.briar.api.messaging.AuthorId; import net.sf.briar.api.messaging.GroupId; -import net.sf.briar.api.messaging.Message; -import net.sf.briar.api.messaging.MessageId; import android.content.Intent; import android.os.Bundle; import android.view.View; @@ -58,11 +49,7 @@ OnClickListener, OnItemClickListener { private final BriarServiceConnection serviceConnection = new BriarServiceConnection(); - private final Map<AuthorId, Rating> ratingCache = - new ConcurrentHashMap<AuthorId, Rating>(); - // The following fields must only be accessed from the UI thread - private final Set<MessageId> messageIds = new HashSet<MessageId>(); private String groupName = null; private GroupAdapter adapter = null; private ListView list = null; @@ -107,8 +94,6 @@ OnClickListener, OnItemClickListener { setContentView(layout); - // Listen for messages and groups being added or removed - db.addListener(this); // Bind to the service so we can wait for the DB to be opened bindService(new Intent(BriarService.class.getName()), serviceConnection, 0); @@ -117,6 +102,7 @@ OnClickListener, OnItemClickListener { @Override public void onResume() { super.onResume(); + db.addListener(this); loadHeaders(); } @@ -129,8 +115,6 @@ OnClickListener, OnItemClickListener { // Load the headers from the database Collection<GroupMessageHeader> headers = db.getMessageHeaders(groupId); - if(LOG.isLoggable(INFO)) - LOG.info("Loaded " + headers.size() + " headers"); // Display the headers in the UI displayHeaders(headers); } catch(NoSuchSubscriptionException e) { @@ -151,15 +135,8 @@ OnClickListener, OnItemClickListener { private void displayHeaders(final Collection<GroupMessageHeader> headers) { runOnUiThread(new Runnable() { public void run() { - ratingCache.clear(); - messageIds.clear(); adapter.clear(); - for(GroupMessageHeader h : headers) { - Author a = h.getAuthor(); - if(a != null) ratingCache.put(a.getId(), h.getRating()); - messageIds.add(h.getId()); - adapter.add(h); - } + for(GroupMessageHeader h : headers) adapter.add(h); adapter.sort(AscendingHeaderComparator.INSTANCE); selectFirstUnread(); } @@ -191,27 +168,27 @@ OnClickListener, OnItemClickListener { } } + @Override + public void onPause() { + db.removeListener(this); + } + @Override public void onDestroy() { super.onDestroy(); - db.removeListener(this); unbindService(serviceConnection); } + // FIXME: Load operations may overlap, resulting in an inconsistent view public void eventOccurred(DatabaseEvent e) { if(e instanceof GroupMessageAddedEvent) { GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; - Message m = g.getMessage(); - if(m.getGroup().getId().equals(groupId)) - loadRatingOrAddToGroup(m, g.isIncoming()); + if(g.getMessage().getGroup().getId().equals(groupId)) + loadHeaders(); } else if(e instanceof MessageExpiredEvent) { - if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading"); - loadHeaders(); // FIXME: Don't reload unnecessarily + loadHeaders(); // FIXME: Don't reload everything } else if(e instanceof RatingChangedEvent) { - RatingChangedEvent r = (RatingChangedEvent) e; - AuthorId a = r.getAuthorId(); - ratingCache.remove(a); - updateRating(a, r.getRating()); + loadHeaders(); } else if(e instanceof SubscriptionRemovedEvent) { if(((SubscriptionRemovedEvent) e).getGroupId().equals(groupId)) { if(LOG.isLoggable(INFO)) LOG.info("Subscription removed"); @@ -220,81 +197,6 @@ OnClickListener, OnItemClickListener { } } - private void loadRatingOrAddToGroup(Message m, boolean incoming) { - Author a = m.getAuthor(); - if(a == null) { - addToGroup(m, UNRATED, incoming); - } else { - Rating r = ratingCache.get(a.getId()); - if(r == null) loadRating(m, incoming); - else addToGroup(m, r, incoming); - } - } - - private void addToGroup(final Message m, final Rating r, - final boolean incoming) { - runOnUiThread(new Runnable() { - public void run() { - if(messageIds.add(m.getId())) { - adapter.add(new GroupMessageHeader(m.getId(), m.getParent(), - m.getContentType(), m.getSubject(), - m.getTimestamp(),!incoming, false, - m.getGroup().getId(), m.getAuthor(), r)); - adapter.sort(AscendingHeaderComparator.INSTANCE); - selectFirstUnread(); - } - } - }); - } - - private void loadRating(final Message m, final boolean incoming) { - dbExecutor.execute(new Runnable() { - public void run() { - try { - // Wait for the service to be bound and started - serviceConnection.waitForStartup(); - // Load the rating from the database - AuthorId a = m.getAuthor().getId(); - Rating r = db.getRating(a); - // Cache the rating - ratingCache.put(a, r); - // Display the message - addToGroup(m, r, incoming); - } catch(DbException e) { - if(LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } catch(InterruptedException e) { - if(LOG.isLoggable(INFO)) - LOG.info("Interrupted while waiting for service"); - Thread.currentThread().interrupt(); - } - } - }); - } - - private void updateRating(final AuthorId a, final Rating r) { - runOnUiThread(new Runnable() { - public void run() { - boolean affected = false; - int count = adapter.getCount(); - for(int i = 0; i < count; i++) { - GroupMessageHeader h = adapter.getItem(i); - Author author = h.getAuthor(); - if(author != null && author.getId().equals(a)) { - adapter.remove(h); - adapter.insert(new GroupMessageHeader(h.getId(), - h.getParent(), h.getContentType(), - h.getSubject(), h.getTimestamp(), h.isRead(), - h.isStarred(), h.getGroupId(), h.getAuthor(), - r), i); - affected = true; - } - } - if(affected) list.invalidate(); - } - }); - } - public void onClick(View view) { Intent i = new Intent(this, WriteGroupMessageActivity.class); i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes()); @@ -313,10 +215,7 @@ OnClickListener, OnItemClickListener { i.putExtra("net.sf.briar.GROUP_NAME", groupName); i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes()); Author author = item.getAuthor(); - if(author == null) { - i.putExtra("net.sf.briar.ANONYMOUS", true); - } else { - i.putExtra("net.sf.briar.ANONYMOUS", false); + if(author != null) { i.putExtra("net.sf.briar.AUTHOR_ID", author.getId().getBytes()); i.putExtra("net.sf.briar.AUTHOR_NAME", author.getName()); i.putExtra("net.sf.briar.RATING", item.getRating().toString()); diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java index 48a977e842bd755c47632c92271cba2e94a7b50e..e6ac1d6e72716c03ee5f21d9efa7cb2cd2bc9c8c 100644 --- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java +++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java @@ -14,7 +14,6 @@ import java.security.PrivateKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Executor; @@ -24,7 +23,6 @@ import net.sf.briar.R; import net.sf.briar.android.BriarActivity; import net.sf.briar.android.BriarService; import net.sf.briar.android.BriarService.BriarServiceConnection; -import net.sf.briar.android.DescendingHeaderComparator; import net.sf.briar.android.widgets.CommonLayoutParams; import net.sf.briar.android.widgets.HorizontalBorder; import net.sf.briar.api.ContactId; @@ -102,8 +100,6 @@ implements OnClickListener, DatabaseListener { setContentView(layout); - // Listen for messages and groups being added or removed - db.addListener(this); // Bind to the service so we can wait for the DB to be opened bindService(new Intent(BriarService.class.getName()), serviceConnection, 0); @@ -203,37 +199,31 @@ implements OnClickListener, DatabaseListener { @Override public void onResume() { super.onResume(); - loadGroups(); + db.addListener(this); + loadHeaders(); } - private void loadGroups() { + private void loadHeaders() { dbExecutor.execute(new Runnable() { public void run() { try { // Wait for the service to be bound and started serviceConnection.waitForStartup(); // Load the subscribed groups from the DB - Collection<Group> groups = db.getSubscriptions(); - if(LOG.isLoggable(INFO)) - LOG.info("Loaded " + groups.size() + " groups"); - List<GroupListItem> items = new ArrayList<GroupListItem>(); - for(Group g : groups) { + for(Group g : db.getSubscriptions()) { // Filter out restricted groups if(g.getPublicKey() != null) continue; - // Load the message headers - Collection<GroupMessageHeader> headers; try { - headers = db.getMessageHeaders(g.getId()); + // Load the headers from the database + Collection<GroupMessageHeader> headers = + db.getMessageHeaders(g.getId()); + // Display the headers in the UI + displayHeaders(g, headers); } catch(NoSuchSubscriptionException e) { - continue; // Unsubscribed since getSubscriptions() + if(LOG.isLoggable(INFO)) + LOG.info("Subscription removed"); } - if(LOG.isLoggable(INFO)) - LOG.info("Loaded " + headers.size() + " headers"); - if(!headers.isEmpty()) - items.add(createItem(g, headers)); } - // Display the groups in the UI - displayGroups(Collections.unmodifiableList(items)); } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -246,25 +236,34 @@ implements OnClickListener, DatabaseListener { }); } - private GroupListItem createItem(Group group, - Collection<GroupMessageHeader> headers) { - List<GroupMessageHeader> sort = - new ArrayList<GroupMessageHeader>(headers); - Collections.sort(sort, DescendingHeaderComparator.INSTANCE); - return new GroupListItem(group, sort); - } - - private void displayGroups(final Collection<GroupListItem> items) { + private void displayHeaders(final Group g, + final Collection<GroupMessageHeader> headers) { runOnUiThread(new Runnable() { public void run() { - adapter.clear(); - for(GroupListItem i : items) adapter.add(i); - adapter.sort(GroupComparator.INSTANCE); + // Remove the old item, if any + GroupListItem item = findGroup(g.getId()); + if(item != null) adapter.remove(item); + // Add a new item if there are any headers to display + if(!headers.isEmpty()) { + List<GroupMessageHeader> headerList = + new ArrayList<GroupMessageHeader>(headers); + adapter.add(new GroupListItem(g, headerList)); + adapter.sort(GroupComparator.INSTANCE); + } selectFirstUnread(); } }); } + private GroupListItem findGroup(GroupId g) { + int count = adapter.getCount(); + for(int i = 0; i < count; i++) { + GroupListItem item = adapter.getItem(i); + if(item.getGroupId().equals(g)) return item; + } + return null; // Not found + } + private void selectFirstUnread() { int firstUnread = -1, count = adapter.getCount(); for(int i = 0; i < count; i++) { @@ -277,10 +276,14 @@ implements OnClickListener, DatabaseListener { else list.setSelection(firstUnread); } + @Override + public void onPause() { + db.removeListener(this); + } + @Override public void onDestroy() { super.onDestroy(); - db.removeListener(this); unbindService(serviceConnection); } @@ -288,52 +291,24 @@ implements OnClickListener, DatabaseListener { startActivity(new Intent(this, WriteGroupMessageActivity.class)); } + // FIXME: Load operations may overlap, resulting in an inconsistent view public void eventOccurred(DatabaseEvent e) { if(e instanceof GroupMessageAddedEvent) { GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; - addToGroup(g.getMessage(), g.isIncoming()); + loadHeaders(g.getMessage().getGroup().getId()); } else if(e instanceof MessageExpiredEvent) { - if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading"); - loadGroups(); // FIXME: Don't reload unnecessarily + loadHeaders(); // FIXME: Don't reload everything } else if(e instanceof SubscriptionRemovedEvent) { removeGroup(((SubscriptionRemovedEvent) e).getGroupId()); } } - private void addToGroup(final Message m, final boolean incoming) { - runOnUiThread(new Runnable() { - public void run() { - GroupId g = m.getGroup().getId(); - GroupListItem item = findGroup(g); - if(item == null) { - loadGroup(g, m, incoming); - } else if(item.add(m, incoming)) { - adapter.sort(GroupComparator.INSTANCE); - selectFirstUnread(); - list.invalidate(); - } - } - }); - } - - private GroupListItem findGroup(GroupId g) { - int count = adapter.getCount(); - for(int i = 0; i < count; i++) { - GroupListItem item = adapter.getItem(i); - if(item.getGroupId().equals(g)) return item; - } - return null; // Not found - } - - private void loadGroup(final GroupId g, final Message m, - final boolean incoming) { + private void loadHeaders(final GroupId g) { dbExecutor.execute(new Runnable() { public void run() { try { - // Wait for the service to be bound and started serviceConnection.waitForStartup(); - // Load the group from the DB and display it in the UI - displayGroup(db.getGroup(g), m, incoming); + displayHeaders(db.getGroup(g), db.getMessageHeaders(g)); } catch(NoSuchSubscriptionException e) { if(LOG.isLoggable(INFO)) LOG.info("Subscription removed"); } catch(DbException e) { @@ -348,25 +323,6 @@ implements OnClickListener, DatabaseListener { }); } - private void displayGroup(final Group g, final Message m, - final boolean incoming) { - runOnUiThread(new Runnable() { - public void run() { - // The item may have been added since loadGroup() was called - GroupListItem item = findGroup(g.getId()); - if(item == null) { - adapter.add(new GroupListItem(g, m, incoming)); - adapter.sort(GroupComparator.INSTANCE); - selectFirstUnread(); - } else if(item.add(m, incoming)) { - adapter.sort(GroupComparator.INSTANCE); - selectFirstUnread(); - list.invalidate(); - } - } - }); - } - private void removeGroup(final GroupId g) { runOnUiThread(new Runnable() { public void run() { diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListItem.java b/briar-android/src/net/sf/briar/android/groups/GroupListItem.java index 918e272d4851e8f7c8a845c974944c0a30734513..a7d86263eff0c609c64d32abcbf06d2058c5eb9c 100644 --- a/briar-android/src/net/sf/briar/android/groups/GroupListItem.java +++ b/briar-android/src/net/sf/briar/android/groups/GroupListItem.java @@ -1,26 +1,20 @@ package net.sf.briar.android.groups; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; import net.sf.briar.android.DescendingHeaderComparator; import net.sf.briar.api.db.GroupMessageHeader; import net.sf.briar.api.messaging.Author; import net.sf.briar.api.messaging.Group; import net.sf.briar.api.messaging.GroupId; -import net.sf.briar.api.messaging.Message; -import net.sf.briar.api.messaging.MessageId; -// This class is not thread-safe class GroupListItem { - private final Set<MessageId> messageIds = new HashSet<MessageId>(); private final Group group; - private String authorName, subject; - private long timestamp; - private int unread; + private final String authorName, subject; + private final long timestamp; + private final int unread; GroupListItem(Group group, List<GroupMessageHeader> headers) { if(headers.isEmpty()) throw new IllegalArgumentException(); @@ -32,36 +26,9 @@ class GroupListItem { else authorName = a.getName(); subject = newest.getSubject(); timestamp = newest.getTimestamp(); - unread = 0; - for(GroupMessageHeader h : headers) { - if(!h.isRead()) unread++; - if(!messageIds.add(h.getId())) throw new IllegalArgumentException(); - } - } - - GroupListItem(Group group, Message first, boolean incoming) { - this.group = group; - Author a = first.getAuthor(); - if(a == null) authorName = null; - else authorName = a.getName(); - subject = first.getSubject(); - timestamp = first.getTimestamp(); - unread = incoming ? 1 : 0; - messageIds.add(first.getId()); - } - - boolean add(Message m, boolean incoming) { - if(!messageIds.add(m.getId())) return false; - if(m.getTimestamp() > timestamp) { - // The added message is the newest - Author a = m.getAuthor(); - if(a == null) authorName = null; - else authorName = a.getName(); - subject = m.getSubject(); - timestamp = m.getTimestamp(); - } - if(incoming) unread++; - return true; + int unread = 0; + for(GroupMessageHeader h : headers) if(!h.isRead()) unread++; + this.unread = unread; } GroupId getGroupId() { diff --git a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java index 845c7b9b1e7e3fb80922d8740331f00d93f527e5..4c03773933a5ef9ab8cf5aed775f98d53990b0b9 100644 --- a/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java +++ b/briar-android/src/net/sf/briar/android/groups/ReadGroupMessageActivity.java @@ -90,11 +90,9 @@ implements OnClickListener { id = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID"); if(id == null) throw new IllegalStateException(); messageId = new MessageId(id); - boolean anonymous = i.getBooleanExtra("net.sf.briar.ANONYMOUS", false); String authorName = null; - if(!anonymous) { - id = i.getByteArrayExtra("net.sf.briar.AUTHOR_ID"); - if(id == null) throw new IllegalStateException(); + id = i.getByteArrayExtra("net.sf.briar.AUTHOR_ID"); + if(id != null) { authorId = new AuthorId(id); authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME"); if(authorName == null) throw new IllegalStateException(); diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java index 6ac5fb32faa39b16c652a1bec7b85b46c188d791..156c0f7c1441c94ec1ed3c2998c13424a860994e 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationActivity.java @@ -6,8 +6,6 @@ import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import java.util.Collection; -import java.util.HashSet; -import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -29,8 +27,6 @@ import net.sf.briar.api.db.event.DatabaseEvent; import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.MessageExpiredEvent; import net.sf.briar.api.db.event.PrivateMessageAddedEvent; -import net.sf.briar.api.messaging.Message; -import net.sf.briar.api.messaging.MessageId; import android.content.Intent; import android.os.Bundle; import android.view.View; @@ -52,8 +48,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { private final BriarServiceConnection serviceConnection = new BriarServiceConnection(); - // The following fields must only be accessed from the UI thread - private Set<MessageId> messageIds = new HashSet<MessageId>(); private String contactName = null; private ConversationAdapter adapter = null; private ListView list = null; @@ -98,8 +92,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { setContentView(layout); - // Listen for messages being added or removed - db.addListener(this); // Bind to the service so we can wait for the DB to be opened bindService(new Intent(BriarService.class.getName()), serviceConnection, 0); @@ -108,6 +100,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { @Override public void onResume() { super.onResume(); + db.addListener(this); loadHeaders(); } @@ -120,8 +113,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { // Load the headers from the database Collection<PrivateMessageHeader> headers = db.getPrivateMessageHeaders(contactId); - if(LOG.isLoggable(INFO)) - LOG.info("Loaded " + headers.size() + " headers"); // Display the headers in the UI displayHeaders(headers); } catch(NoSuchContactException e) { @@ -143,12 +134,8 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { final Collection<PrivateMessageHeader> headers) { runOnUiThread(new Runnable() { public void run() { - messageIds.clear(); adapter.clear(); - for(PrivateMessageHeader h : headers) { - messageIds.add(h.getId()); - adapter.add(h); - } + for(PrivateMessageHeader h : headers) adapter.add(h); adapter.sort(AscendingHeaderComparator.INSTANCE); selectFirstUnread(); } @@ -180,13 +167,18 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { } } + @Override + public void onPause() { + db.removeListener(this); + } + @Override public void onDestroy() { super.onDestroy(); - db.removeListener(this); unbindService(serviceConnection); } + // FIXME: Load operations may overlap, resulting in an inconsistent view public void eventOccurred(DatabaseEvent e) { if(e instanceof ContactRemovedEvent) { ContactRemovedEvent c = (ContactRemovedEvent) e; @@ -195,30 +187,13 @@ implements DatabaseListener, OnClickListener, OnItemClickListener { finishOnUiThread(); } } else if(e instanceof MessageExpiredEvent) { - if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading"); - loadHeaders(); // FIXME: Don't reload unnecessarily + loadHeaders(); // FIXME: Don't reload everything } else if(e instanceof PrivateMessageAddedEvent) { - PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e; - if(p.getContactId().equals(contactId)) - addToConversation(p.getMessage(), p.isIncoming()); + if(((PrivateMessageAddedEvent) e).getContactId().equals(contactId)) + loadHeaders(); } } - private void addToConversation(final Message m, final boolean incoming) { - runOnUiThread(new Runnable() { - public void run() { - if(messageIds.add(m.getId())) { - adapter.add(new PrivateMessageHeader(m.getId(), - m.getParent(), m.getContentType(), m.getSubject(), - m.getTimestamp(), !incoming, false, contactId, - incoming)); - adapter.sort(AscendingHeaderComparator.INSTANCE); - selectFirstUnread(); - } - } - }); - } - public void onClick(View view) { Intent i = new Intent(this, WritePrivateMessageActivity.class); i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt()); diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java index 4734728148051e43fb0193c8ff5e4cb3d3308159..c7ab7d8a350f2721be18cdc76755519eb7b13aa3 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationListActivity.java @@ -10,9 +10,7 @@ import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -89,8 +87,6 @@ implements OnClickListener, DatabaseListener { setContentView(layout); - // Listen for messages being added or removed - db.addListener(this); // Bind to the service so we can wait for the DB to be opened bindService(new Intent(BriarService.class.getName()), serviceConnection, 0); @@ -163,6 +159,7 @@ implements OnClickListener, DatabaseListener { @Override public void onResume() { super.onResume(); + db.addListener(this); loadHeaders(); } @@ -173,16 +170,18 @@ implements OnClickListener, DatabaseListener { // Wait for the service to be bound and started serviceConnection.waitForStartup(); // Load the contact list from the database - Collection<Contact> contacts = db.getContacts(); - if(LOG.isLoggable(INFO)) - LOG.info("Loaded " + contacts.size() + " contacts"); - // Load the headers from the database - Collection<PrivateMessageHeader> headers = - db.getPrivateMessageHeaders(); - if(LOG.isLoggable(INFO)) - LOG.info("Loaded " + headers.size() + " headers"); - // Display the headers in the UI - displayHeaders(contacts, headers); + for(Contact c : db.getContacts()) { + try { + // Load the headers from the database + Collection<PrivateMessageHeader> headers = + db.getPrivateMessageHeaders(c.getId()); + // Display the headers in the UI + displayHeaders(c, headers); + } catch(NoSuchContactException e) { + if(LOG.isLoggable(INFO)) + LOG.info("Contact removed"); + } + } } catch(DbException e) { if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); @@ -195,40 +194,32 @@ implements OnClickListener, DatabaseListener { }); } - private void displayHeaders(final Collection<Contact> contacts, + private void displayHeaders(final Contact c, final Collection<PrivateMessageHeader> headers) { runOnUiThread(new Runnable() { public void run() { - adapter.clear(); - for(ConversationListItem i : sortHeaders(contacts, headers)) - adapter.add(i); - adapter.sort(ConversationComparator.INSTANCE); + // Remove the old item, if any + ConversationListItem item = findConversation(c.getId()); + if(item != null) adapter.remove(item); + // Add a new item if there are any headers to display + if(!headers.isEmpty()) { + List<PrivateMessageHeader> headerList = + new ArrayList<PrivateMessageHeader>(headers); + adapter.add(new ConversationListItem(c, headerList)); + adapter.sort(ConversationComparator.INSTANCE); + } selectFirstUnread(); } }); } - private List<ConversationListItem> sortHeaders(Collection<Contact> contacts, - Collection<PrivateMessageHeader> headers) { - // Group the headers into conversations, one per contact - Map<ContactId, List<PrivateMessageHeader>> map = - new HashMap<ContactId, List<PrivateMessageHeader>>(); - for(Contact c : contacts) - map.put(c.getId(), new ArrayList<PrivateMessageHeader>()); - for(PrivateMessageHeader h : headers) { - ContactId id = h.getContactId(); - List<PrivateMessageHeader> conversation = map.get(id); - // Ignore header if the contact was added after db.getContacts() - if(conversation != null) conversation.add(h); - } - // Create a list item for each non-empty conversation - List<ConversationListItem> list = new ArrayList<ConversationListItem>(); - for(Contact c : contacts) { - List<PrivateMessageHeader> conversation = map.get(c.getId()); - if(!conversation.isEmpty()) - list.add(new ConversationListItem(c, conversation)); + private ConversationListItem findConversation(ContactId c) { + int count = adapter.getCount(); + for(int i = 0; i < count; i++) { + ConversationListItem item = adapter.getItem(i); + if(item.getContactId().equals(c)) return item; } - return list; + return null; // Not found } private void selectFirstUnread() { @@ -243,10 +234,14 @@ implements OnClickListener, DatabaseListener { else list.setSelection(firstUnread); } + @Override + public void onPause() { + db.removeListener(this); + } + @Override public void onDestroy() { super.onDestroy(); - db.removeListener(this); unbindService(serviceConnection); } @@ -254,19 +249,18 @@ implements OnClickListener, DatabaseListener { startActivity(new Intent(this, WritePrivateMessageActivity.class)); } + // FIXME: Load operations may overlap, resulting in an inconsistent view public void eventOccurred(DatabaseEvent e) { if(e instanceof ContactRemovedEvent) { - removeContact(((ContactRemovedEvent) e).getContactId()); + removeConversation(((ContactRemovedEvent) e).getContactId()); } else if(e instanceof MessageExpiredEvent) { - if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading"); - loadHeaders(); // FIXME: Don't reload unnecessarily + loadHeaders(); // FIXME: Don't reload everything } else if(e instanceof PrivateMessageAddedEvent) { - PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e; - addToConversation(p.getContactId(), p.getMessage(), p.isIncoming()); + loadHeaders(((PrivateMessageAddedEvent) e).getContactId()); } } - private void removeContact(final ContactId c) { + private void removeConversation(final ContactId c) { runOnUiThread(new Runnable() { public void run() { ConversationListItem item = findConversation(c); @@ -278,40 +272,13 @@ implements OnClickListener, DatabaseListener { }); } - private ConversationListItem findConversation(ContactId c) { - int count = adapter.getCount(); - for(int i = 0; i < count; i++) { - ConversationListItem item = adapter.getItem(i); - if(item.getContactId().equals(c)) return item; - } - return null; // Not found - } - - private void addToConversation(final ContactId c, final Message m, - final boolean incoming) { - runOnUiThread(new Runnable() { - public void run() { - ConversationListItem item = findConversation(c); - if(item == null) { - loadContact(c, m, incoming); - } else if(item.add(m, incoming)) { - adapter.sort(ConversationComparator.INSTANCE); - selectFirstUnread(); - list.invalidate(); - } - } - }); - } - - private void loadContact(final ContactId c, final Message m, - final boolean incoming) { + private void loadHeaders(final ContactId c) { dbExecutor.execute(new Runnable() { public void run() { try { - // Wait for the service to be bound and started serviceConnection.waitForStartup(); - // Load the contact from the DB and display it in the UI - displayContact(db.getContact(c), m, incoming); + Contact contact = db.getContact(c); + displayHeaders(contact, db.getPrivateMessageHeaders(c)); } catch(NoSuchContactException e) { if(LOG.isLoggable(INFO)) LOG.info("Contact removed"); } catch(DbException e) { @@ -326,25 +293,6 @@ implements OnClickListener, DatabaseListener { }); } - private void displayContact(final Contact c, final Message m, - final boolean incoming) { - runOnUiThread(new Runnable() { - public void run() { - // The item may have been added since loadContact() was called - ConversationListItem item = findConversation(c.getId()); - if(item == null) { - adapter.add(new ConversationListItem(c, m, incoming)); - adapter.sort(ConversationComparator.INSTANCE); - selectFirstUnread(); - } else if(item.add(m, incoming)) { - adapter.sort(ConversationComparator.INSTANCE); - selectFirstUnread(); - list.invalidate(); - } - } - }); - } - private static class ConversationComparator implements Comparator<ConversationListItem> { diff --git a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java index edec2e49b360cfd2a0b1e4190f460a8cb0528f43..eec30a91c80947d7cd4142db16e009f7c4c50991 100644 --- a/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java +++ b/briar-android/src/net/sf/briar/android/messages/ConversationListItem.java @@ -1,25 +1,19 @@ package net.sf.briar.android.messages; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; import net.sf.briar.android.DescendingHeaderComparator; import net.sf.briar.api.Contact; import net.sf.briar.api.ContactId; import net.sf.briar.api.db.PrivateMessageHeader; -import net.sf.briar.api.messaging.Message; -import net.sf.briar.api.messaging.MessageId; -// This class is not thread-safe class ConversationListItem { - private final Set<MessageId> messageIds = new HashSet<MessageId>(); private final Contact contact; - private String subject; - private long timestamp; - private int unread; + private final String subject; + private final long timestamp; + private final int unread; ConversationListItem(Contact contact, List<PrivateMessageHeader> headers) { if(headers.isEmpty()) throw new IllegalArgumentException(); @@ -27,30 +21,9 @@ class ConversationListItem { Collections.sort(headers, DescendingHeaderComparator.INSTANCE); subject = headers.get(0).getSubject(); timestamp = headers.get(0).getTimestamp(); - unread = 0; - for(PrivateMessageHeader h : headers) { - if(!h.isRead()) unread++; - if(!messageIds.add(h.getId())) throw new IllegalArgumentException(); - } - } - - ConversationListItem(Contact contact, Message first, boolean incoming) { - this.contact = contact; - subject = first.getSubject(); - timestamp = first.getTimestamp(); - unread = incoming ? 1 : 0; - messageIds.add(first.getId()); - } - - boolean add(Message m, boolean incoming) { - if(!messageIds.add(m.getId())) return false; - if(m.getTimestamp() > timestamp) { - // The added message is the newest - subject = m.getSubject(); - timestamp = m.getTimestamp(); - } - if(incoming) unread++; - return true; + int unread = 0; + for(PrivateMessageHeader h : headers) if(!h.isRead()) unread++; + this.unread = unread; } ContactId getContactId() {