mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-18 05:39:53 +01:00
Removed complex premature optimisations from DB/UI interaction.
This commit is contained in:
@@ -4,13 +4,8 @@ import static android.view.Gravity.CENTER_HORIZONTAL;
|
|||||||
import static android.widget.LinearLayout.VERTICAL;
|
import static android.widget.LinearLayout.VERTICAL;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
import static net.sf.briar.api.Rating.UNRATED;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
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.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
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.BriarService.BriarServiceConnection;
|
||||||
import net.sf.briar.android.widgets.CommonLayoutParams;
|
import net.sf.briar.android.widgets.CommonLayoutParams;
|
||||||
import net.sf.briar.android.widgets.HorizontalBorder;
|
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.DatabaseComponent;
|
||||||
import net.sf.briar.api.db.DatabaseExecutor;
|
import net.sf.briar.api.db.DatabaseExecutor;
|
||||||
import net.sf.briar.api.db.DbException;
|
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.RatingChangedEvent;
|
||||||
import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
|
import net.sf.briar.api.db.event.SubscriptionRemovedEvent;
|
||||||
import net.sf.briar.api.messaging.Author;
|
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.GroupId;
|
||||||
import net.sf.briar.api.messaging.Message;
|
|
||||||
import net.sf.briar.api.messaging.MessageId;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -58,11 +49,7 @@ OnClickListener, OnItemClickListener {
|
|||||||
|
|
||||||
private final BriarServiceConnection serviceConnection =
|
private final BriarServiceConnection serviceConnection =
|
||||||
new BriarServiceConnection();
|
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 String groupName = null;
|
||||||
private GroupAdapter adapter = null;
|
private GroupAdapter adapter = null;
|
||||||
private ListView list = null;
|
private ListView list = null;
|
||||||
@@ -107,8 +94,6 @@ OnClickListener, OnItemClickListener {
|
|||||||
|
|
||||||
setContentView(layout);
|
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
|
// Bind to the service so we can wait for the DB to be opened
|
||||||
bindService(new Intent(BriarService.class.getName()),
|
bindService(new Intent(BriarService.class.getName()),
|
||||||
serviceConnection, 0);
|
serviceConnection, 0);
|
||||||
@@ -117,6 +102,7 @@ OnClickListener, OnItemClickListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
db.addListener(this);
|
||||||
loadHeaders();
|
loadHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +115,6 @@ OnClickListener, OnItemClickListener {
|
|||||||
// Load the headers from the database
|
// Load the headers from the database
|
||||||
Collection<GroupMessageHeader> headers =
|
Collection<GroupMessageHeader> headers =
|
||||||
db.getMessageHeaders(groupId);
|
db.getMessageHeaders(groupId);
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Loaded " + headers.size() + " headers");
|
|
||||||
// Display the headers in the UI
|
// Display the headers in the UI
|
||||||
displayHeaders(headers);
|
displayHeaders(headers);
|
||||||
} catch(NoSuchSubscriptionException e) {
|
} catch(NoSuchSubscriptionException e) {
|
||||||
@@ -151,15 +135,8 @@ OnClickListener, OnItemClickListener {
|
|||||||
private void displayHeaders(final Collection<GroupMessageHeader> headers) {
|
private void displayHeaders(final Collection<GroupMessageHeader> headers) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
ratingCache.clear();
|
|
||||||
messageIds.clear();
|
|
||||||
adapter.clear();
|
adapter.clear();
|
||||||
for(GroupMessageHeader h : headers) {
|
for(GroupMessageHeader h : headers) adapter.add(h);
|
||||||
Author a = h.getAuthor();
|
|
||||||
if(a != null) ratingCache.put(a.getId(), h.getRating());
|
|
||||||
messageIds.add(h.getId());
|
|
||||||
adapter.add(h);
|
|
||||||
}
|
|
||||||
adapter.sort(AscendingHeaderComparator.INSTANCE);
|
adapter.sort(AscendingHeaderComparator.INSTANCE);
|
||||||
selectFirstUnread();
|
selectFirstUnread();
|
||||||
}
|
}
|
||||||
@@ -191,27 +168,27 @@ OnClickListener, OnItemClickListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
db.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
db.removeListener(this);
|
|
||||||
unbindService(serviceConnection);
|
unbindService(serviceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Load operations may overlap, resulting in an inconsistent view
|
||||||
public void eventOccurred(DatabaseEvent e) {
|
public void eventOccurred(DatabaseEvent e) {
|
||||||
if(e instanceof GroupMessageAddedEvent) {
|
if(e instanceof GroupMessageAddedEvent) {
|
||||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||||
Message m = g.getMessage();
|
if(g.getMessage().getGroup().getId().equals(groupId))
|
||||||
if(m.getGroup().getId().equals(groupId))
|
loadHeaders();
|
||||||
loadRatingOrAddToGroup(m, g.isIncoming());
|
|
||||||
} else if(e instanceof MessageExpiredEvent) {
|
} else if(e instanceof MessageExpiredEvent) {
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
|
loadHeaders(); // FIXME: Don't reload everything
|
||||||
loadHeaders(); // FIXME: Don't reload unnecessarily
|
|
||||||
} else if(e instanceof RatingChangedEvent) {
|
} else if(e instanceof RatingChangedEvent) {
|
||||||
RatingChangedEvent r = (RatingChangedEvent) e;
|
loadHeaders();
|
||||||
AuthorId a = r.getAuthorId();
|
|
||||||
ratingCache.remove(a);
|
|
||||||
updateRating(a, r.getRating());
|
|
||||||
} else if(e instanceof SubscriptionRemovedEvent) {
|
} else if(e instanceof SubscriptionRemovedEvent) {
|
||||||
if(((SubscriptionRemovedEvent) e).getGroupId().equals(groupId)) {
|
if(((SubscriptionRemovedEvent) e).getGroupId().equals(groupId)) {
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
|
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) {
|
public void onClick(View view) {
|
||||||
Intent i = new Intent(this, WriteGroupMessageActivity.class);
|
Intent i = new Intent(this, WriteGroupMessageActivity.class);
|
||||||
i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
|
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.GROUP_NAME", groupName);
|
||||||
i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes());
|
i.putExtra("net.sf.briar.MESSAGE_ID", item.getId().getBytes());
|
||||||
Author author = item.getAuthor();
|
Author author = item.getAuthor();
|
||||||
if(author == null) {
|
if(author != null) {
|
||||||
i.putExtra("net.sf.briar.ANONYMOUS", true);
|
|
||||||
} else {
|
|
||||||
i.putExtra("net.sf.briar.ANONYMOUS", false);
|
|
||||||
i.putExtra("net.sf.briar.AUTHOR_ID", author.getId().getBytes());
|
i.putExtra("net.sf.briar.AUTHOR_ID", author.getId().getBytes());
|
||||||
i.putExtra("net.sf.briar.AUTHOR_NAME", author.getName());
|
i.putExtra("net.sf.briar.AUTHOR_NAME", author.getName());
|
||||||
i.putExtra("net.sf.briar.RATING", item.getRating().toString());
|
i.putExtra("net.sf.briar.RATING", item.getRating().toString());
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import java.security.PrivateKey;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
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.BriarActivity;
|
||||||
import net.sf.briar.android.BriarService;
|
import net.sf.briar.android.BriarService;
|
||||||
import net.sf.briar.android.BriarService.BriarServiceConnection;
|
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.CommonLayoutParams;
|
||||||
import net.sf.briar.android.widgets.HorizontalBorder;
|
import net.sf.briar.android.widgets.HorizontalBorder;
|
||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
@@ -102,8 +100,6 @@ implements OnClickListener, DatabaseListener {
|
|||||||
|
|
||||||
setContentView(layout);
|
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
|
// Bind to the service so we can wait for the DB to be opened
|
||||||
bindService(new Intent(BriarService.class.getName()),
|
bindService(new Intent(BriarService.class.getName()),
|
||||||
serviceConnection, 0);
|
serviceConnection, 0);
|
||||||
@@ -203,37 +199,31 @@ implements OnClickListener, DatabaseListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadGroups();
|
db.addListener(this);
|
||||||
|
loadHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadGroups() {
|
private void loadHeaders() {
|
||||||
dbExecutor.execute(new Runnable() {
|
dbExecutor.execute(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
// Wait for the service to be bound and started
|
// Wait for the service to be bound and started
|
||||||
serviceConnection.waitForStartup();
|
serviceConnection.waitForStartup();
|
||||||
// Load the subscribed groups from the DB
|
// Load the subscribed groups from the DB
|
||||||
Collection<Group> groups = db.getSubscriptions();
|
for(Group g : db.getSubscriptions()) {
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Loaded " + groups.size() + " groups");
|
|
||||||
List<GroupListItem> items = new ArrayList<GroupListItem>();
|
|
||||||
for(Group g : groups) {
|
|
||||||
// Filter out restricted groups
|
// Filter out restricted groups
|
||||||
if(g.getPublicKey() != null) continue;
|
if(g.getPublicKey() != null) continue;
|
||||||
// Load the message headers
|
|
||||||
Collection<GroupMessageHeader> headers;
|
|
||||||
try {
|
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) {
|
} 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) {
|
} catch(DbException e) {
|
||||||
if(LOG.isLoggable(WARNING))
|
if(LOG.isLoggable(WARNING))
|
||||||
LOG.log(WARNING, e.toString(), e);
|
LOG.log(WARNING, e.toString(), e);
|
||||||
@@ -246,25 +236,34 @@ implements OnClickListener, DatabaseListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupListItem createItem(Group group,
|
private void displayHeaders(final Group g,
|
||||||
Collection<GroupMessageHeader> headers) {
|
final 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) {
|
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
adapter.clear();
|
// Remove the old item, if any
|
||||||
for(GroupListItem i : items) adapter.add(i);
|
GroupListItem item = findGroup(g.getId());
|
||||||
adapter.sort(GroupComparator.INSTANCE);
|
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();
|
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() {
|
private void selectFirstUnread() {
|
||||||
int firstUnread = -1, count = adapter.getCount();
|
int firstUnread = -1, count = adapter.getCount();
|
||||||
for(int i = 0; i < count; i++) {
|
for(int i = 0; i < count; i++) {
|
||||||
@@ -277,10 +276,14 @@ implements OnClickListener, DatabaseListener {
|
|||||||
else list.setSelection(firstUnread);
|
else list.setSelection(firstUnread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
db.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
db.removeListener(this);
|
|
||||||
unbindService(serviceConnection);
|
unbindService(serviceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,52 +291,24 @@ implements OnClickListener, DatabaseListener {
|
|||||||
startActivity(new Intent(this, WriteGroupMessageActivity.class));
|
startActivity(new Intent(this, WriteGroupMessageActivity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Load operations may overlap, resulting in an inconsistent view
|
||||||
public void eventOccurred(DatabaseEvent e) {
|
public void eventOccurred(DatabaseEvent e) {
|
||||||
if(e instanceof GroupMessageAddedEvent) {
|
if(e instanceof GroupMessageAddedEvent) {
|
||||||
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
|
||||||
addToGroup(g.getMessage(), g.isIncoming());
|
loadHeaders(g.getMessage().getGroup().getId());
|
||||||
} else if(e instanceof MessageExpiredEvent) {
|
} else if(e instanceof MessageExpiredEvent) {
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
|
loadHeaders(); // FIXME: Don't reload everything
|
||||||
loadGroups(); // FIXME: Don't reload unnecessarily
|
|
||||||
} else if(e instanceof SubscriptionRemovedEvent) {
|
} else if(e instanceof SubscriptionRemovedEvent) {
|
||||||
removeGroup(((SubscriptionRemovedEvent) e).getGroupId());
|
removeGroup(((SubscriptionRemovedEvent) e).getGroupId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToGroup(final Message m, final boolean incoming) {
|
private void loadHeaders(final GroupId g) {
|
||||||
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) {
|
|
||||||
dbExecutor.execute(new Runnable() {
|
dbExecutor.execute(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
// Wait for the service to be bound and started
|
|
||||||
serviceConnection.waitForStartup();
|
serviceConnection.waitForStartup();
|
||||||
// Load the group from the DB and display it in the UI
|
displayHeaders(db.getGroup(g), db.getMessageHeaders(g));
|
||||||
displayGroup(db.getGroup(g), m, incoming);
|
|
||||||
} catch(NoSuchSubscriptionException e) {
|
} catch(NoSuchSubscriptionException e) {
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
|
if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
|
||||||
} catch(DbException e) {
|
} 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) {
|
private void removeGroup(final GroupId g) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|||||||
@@ -1,26 +1,20 @@
|
|||||||
package net.sf.briar.android.groups;
|
package net.sf.briar.android.groups;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import net.sf.briar.android.DescendingHeaderComparator;
|
import net.sf.briar.android.DescendingHeaderComparator;
|
||||||
import net.sf.briar.api.db.GroupMessageHeader;
|
import net.sf.briar.api.db.GroupMessageHeader;
|
||||||
import net.sf.briar.api.messaging.Author;
|
import net.sf.briar.api.messaging.Author;
|
||||||
import net.sf.briar.api.messaging.Group;
|
import net.sf.briar.api.messaging.Group;
|
||||||
import net.sf.briar.api.messaging.GroupId;
|
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 {
|
class GroupListItem {
|
||||||
|
|
||||||
private final Set<MessageId> messageIds = new HashSet<MessageId>();
|
|
||||||
private final Group group;
|
private final Group group;
|
||||||
private String authorName, subject;
|
private final String authorName, subject;
|
||||||
private long timestamp;
|
private final long timestamp;
|
||||||
private int unread;
|
private final int unread;
|
||||||
|
|
||||||
GroupListItem(Group group, List<GroupMessageHeader> headers) {
|
GroupListItem(Group group, List<GroupMessageHeader> headers) {
|
||||||
if(headers.isEmpty()) throw new IllegalArgumentException();
|
if(headers.isEmpty()) throw new IllegalArgumentException();
|
||||||
@@ -32,36 +26,9 @@ class GroupListItem {
|
|||||||
else authorName = a.getName();
|
else authorName = a.getName();
|
||||||
subject = newest.getSubject();
|
subject = newest.getSubject();
|
||||||
timestamp = newest.getTimestamp();
|
timestamp = newest.getTimestamp();
|
||||||
unread = 0;
|
int unread = 0;
|
||||||
for(GroupMessageHeader h : headers) {
|
for(GroupMessageHeader h : headers) if(!h.isRead()) unread++;
|
||||||
if(!h.isRead()) unread++;
|
this.unread = 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GroupId getGroupId() {
|
GroupId getGroupId() {
|
||||||
|
|||||||
@@ -90,11 +90,9 @@ implements OnClickListener {
|
|||||||
id = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
|
id = i.getByteArrayExtra("net.sf.briar.MESSAGE_ID");
|
||||||
if(id == null) throw new IllegalStateException();
|
if(id == null) throw new IllegalStateException();
|
||||||
messageId = new MessageId(id);
|
messageId = new MessageId(id);
|
||||||
boolean anonymous = i.getBooleanExtra("net.sf.briar.ANONYMOUS", false);
|
|
||||||
String authorName = null;
|
String authorName = null;
|
||||||
if(!anonymous) {
|
id = i.getByteArrayExtra("net.sf.briar.AUTHOR_ID");
|
||||||
id = i.getByteArrayExtra("net.sf.briar.AUTHOR_ID");
|
if(id != null) {
|
||||||
if(id == null) throw new IllegalStateException();
|
|
||||||
authorId = new AuthorId(id);
|
authorId = new AuthorId(id);
|
||||||
authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME");
|
authorName = i.getStringExtra("net.sf.briar.AUTHOR_NAME");
|
||||||
if(authorName == null) throw new IllegalStateException();
|
if(authorName == null) throw new IllegalStateException();
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
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.DatabaseListener;
|
||||||
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
import net.sf.briar.api.db.event.MessageExpiredEvent;
|
||||||
import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
|
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.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -52,8 +48,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
|||||||
private final BriarServiceConnection serviceConnection =
|
private final BriarServiceConnection serviceConnection =
|
||||||
new BriarServiceConnection();
|
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 String contactName = null;
|
||||||
private ConversationAdapter adapter = null;
|
private ConversationAdapter adapter = null;
|
||||||
private ListView list = null;
|
private ListView list = null;
|
||||||
@@ -98,8 +92,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
|||||||
|
|
||||||
setContentView(layout);
|
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
|
// Bind to the service so we can wait for the DB to be opened
|
||||||
bindService(new Intent(BriarService.class.getName()),
|
bindService(new Intent(BriarService.class.getName()),
|
||||||
serviceConnection, 0);
|
serviceConnection, 0);
|
||||||
@@ -108,6 +100,7 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
db.addListener(this);
|
||||||
loadHeaders();
|
loadHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +113,6 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
|||||||
// Load the headers from the database
|
// Load the headers from the database
|
||||||
Collection<PrivateMessageHeader> headers =
|
Collection<PrivateMessageHeader> headers =
|
||||||
db.getPrivateMessageHeaders(contactId);
|
db.getPrivateMessageHeaders(contactId);
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Loaded " + headers.size() + " headers");
|
|
||||||
// Display the headers in the UI
|
// Display the headers in the UI
|
||||||
displayHeaders(headers);
|
displayHeaders(headers);
|
||||||
} catch(NoSuchContactException e) {
|
} catch(NoSuchContactException e) {
|
||||||
@@ -143,12 +134,8 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
|||||||
final Collection<PrivateMessageHeader> headers) {
|
final Collection<PrivateMessageHeader> headers) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
messageIds.clear();
|
|
||||||
adapter.clear();
|
adapter.clear();
|
||||||
for(PrivateMessageHeader h : headers) {
|
for(PrivateMessageHeader h : headers) adapter.add(h);
|
||||||
messageIds.add(h.getId());
|
|
||||||
adapter.add(h);
|
|
||||||
}
|
|
||||||
adapter.sort(AscendingHeaderComparator.INSTANCE);
|
adapter.sort(AscendingHeaderComparator.INSTANCE);
|
||||||
selectFirstUnread();
|
selectFirstUnread();
|
||||||
}
|
}
|
||||||
@@ -180,13 +167,18 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
db.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
db.removeListener(this);
|
|
||||||
unbindService(serviceConnection);
|
unbindService(serviceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Load operations may overlap, resulting in an inconsistent view
|
||||||
public void eventOccurred(DatabaseEvent e) {
|
public void eventOccurred(DatabaseEvent e) {
|
||||||
if(e instanceof ContactRemovedEvent) {
|
if(e instanceof ContactRemovedEvent) {
|
||||||
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
ContactRemovedEvent c = (ContactRemovedEvent) e;
|
||||||
@@ -195,30 +187,13 @@ implements DatabaseListener, OnClickListener, OnItemClickListener {
|
|||||||
finishOnUiThread();
|
finishOnUiThread();
|
||||||
}
|
}
|
||||||
} else if(e instanceof MessageExpiredEvent) {
|
} else if(e instanceof MessageExpiredEvent) {
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
|
loadHeaders(); // FIXME: Don't reload everything
|
||||||
loadHeaders(); // FIXME: Don't reload unnecessarily
|
|
||||||
} else if(e instanceof PrivateMessageAddedEvent) {
|
} else if(e instanceof PrivateMessageAddedEvent) {
|
||||||
PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e;
|
if(((PrivateMessageAddedEvent) e).getContactId().equals(contactId))
|
||||||
if(p.getContactId().equals(contactId))
|
loadHeaders();
|
||||||
addToConversation(p.getMessage(), p.isIncoming());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
public void onClick(View view) {
|
||||||
Intent i = new Intent(this, WritePrivateMessageActivity.class);
|
Intent i = new Intent(this, WritePrivateMessageActivity.class);
|
||||||
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
|
i.putExtra("net.sf.briar.CONTACT_ID", contactId.getInt());
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import java.security.GeneralSecurityException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -89,8 +87,6 @@ implements OnClickListener, DatabaseListener {
|
|||||||
|
|
||||||
setContentView(layout);
|
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
|
// Bind to the service so we can wait for the DB to be opened
|
||||||
bindService(new Intent(BriarService.class.getName()),
|
bindService(new Intent(BriarService.class.getName()),
|
||||||
serviceConnection, 0);
|
serviceConnection, 0);
|
||||||
@@ -163,6 +159,7 @@ implements OnClickListener, DatabaseListener {
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
db.addListener(this);
|
||||||
loadHeaders();
|
loadHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,16 +170,18 @@ implements OnClickListener, DatabaseListener {
|
|||||||
// Wait for the service to be bound and started
|
// Wait for the service to be bound and started
|
||||||
serviceConnection.waitForStartup();
|
serviceConnection.waitForStartup();
|
||||||
// Load the contact list from the database
|
// Load the contact list from the database
|
||||||
Collection<Contact> contacts = db.getContacts();
|
for(Contact c : db.getContacts()) {
|
||||||
if(LOG.isLoggable(INFO))
|
try {
|
||||||
LOG.info("Loaded " + contacts.size() + " contacts");
|
// Load the headers from the database
|
||||||
// Load the headers from the database
|
Collection<PrivateMessageHeader> headers =
|
||||||
Collection<PrivateMessageHeader> headers =
|
db.getPrivateMessageHeaders(c.getId());
|
||||||
db.getPrivateMessageHeaders();
|
// Display the headers in the UI
|
||||||
if(LOG.isLoggable(INFO))
|
displayHeaders(c, headers);
|
||||||
LOG.info("Loaded " + headers.size() + " headers");
|
} catch(NoSuchContactException e) {
|
||||||
// Display the headers in the UI
|
if(LOG.isLoggable(INFO))
|
||||||
displayHeaders(contacts, headers);
|
LOG.info("Contact removed");
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch(DbException e) {
|
} catch(DbException e) {
|
||||||
if(LOG.isLoggable(WARNING))
|
if(LOG.isLoggable(WARNING))
|
||||||
LOG.log(WARNING, e.toString(), e);
|
LOG.log(WARNING, e.toString(), e);
|
||||||
@@ -195,40 +194,32 @@ implements OnClickListener, DatabaseListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayHeaders(final Collection<Contact> contacts,
|
private void displayHeaders(final Contact c,
|
||||||
final Collection<PrivateMessageHeader> headers) {
|
final Collection<PrivateMessageHeader> headers) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
adapter.clear();
|
// Remove the old item, if any
|
||||||
for(ConversationListItem i : sortHeaders(contacts, headers))
|
ConversationListItem item = findConversation(c.getId());
|
||||||
adapter.add(i);
|
if(item != null) adapter.remove(item);
|
||||||
adapter.sort(ConversationComparator.INSTANCE);
|
// 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();
|
selectFirstUnread();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ConversationListItem> sortHeaders(Collection<Contact> contacts,
|
private ConversationListItem findConversation(ContactId c) {
|
||||||
Collection<PrivateMessageHeader> headers) {
|
int count = adapter.getCount();
|
||||||
// Group the headers into conversations, one per contact
|
for(int i = 0; i < count; i++) {
|
||||||
Map<ContactId, List<PrivateMessageHeader>> map =
|
ConversationListItem item = adapter.getItem(i);
|
||||||
new HashMap<ContactId, List<PrivateMessageHeader>>();
|
if(item.getContactId().equals(c)) return item;
|
||||||
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
|
return null; // Not found
|
||||||
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));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectFirstUnread() {
|
private void selectFirstUnread() {
|
||||||
@@ -243,10 +234,14 @@ implements OnClickListener, DatabaseListener {
|
|||||||
else list.setSelection(firstUnread);
|
else list.setSelection(firstUnread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
db.removeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
db.removeListener(this);
|
|
||||||
unbindService(serviceConnection);
|
unbindService(serviceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,19 +249,18 @@ implements OnClickListener, DatabaseListener {
|
|||||||
startActivity(new Intent(this, WritePrivateMessageActivity.class));
|
startActivity(new Intent(this, WritePrivateMessageActivity.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Load operations may overlap, resulting in an inconsistent view
|
||||||
public void eventOccurred(DatabaseEvent e) {
|
public void eventOccurred(DatabaseEvent e) {
|
||||||
if(e instanceof ContactRemovedEvent) {
|
if(e instanceof ContactRemovedEvent) {
|
||||||
removeContact(((ContactRemovedEvent) e).getContactId());
|
removeConversation(((ContactRemovedEvent) e).getContactId());
|
||||||
} else if(e instanceof MessageExpiredEvent) {
|
} else if(e instanceof MessageExpiredEvent) {
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Message removed, reloading");
|
loadHeaders(); // FIXME: Don't reload everything
|
||||||
loadHeaders(); // FIXME: Don't reload unnecessarily
|
|
||||||
} else if(e instanceof PrivateMessageAddedEvent) {
|
} else if(e instanceof PrivateMessageAddedEvent) {
|
||||||
PrivateMessageAddedEvent p = (PrivateMessageAddedEvent) e;
|
loadHeaders(((PrivateMessageAddedEvent) e).getContactId());
|
||||||
addToConversation(p.getContactId(), p.getMessage(), p.isIncoming());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeContact(final ContactId c) {
|
private void removeConversation(final ContactId c) {
|
||||||
runOnUiThread(new Runnable() {
|
runOnUiThread(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
ConversationListItem item = findConversation(c);
|
ConversationListItem item = findConversation(c);
|
||||||
@@ -278,40 +272,13 @@ implements OnClickListener, DatabaseListener {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConversationListItem findConversation(ContactId c) {
|
private void loadHeaders(final 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) {
|
|
||||||
dbExecutor.execute(new Runnable() {
|
dbExecutor.execute(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
// Wait for the service to be bound and started
|
|
||||||
serviceConnection.waitForStartup();
|
serviceConnection.waitForStartup();
|
||||||
// Load the contact from the DB and display it in the UI
|
Contact contact = db.getContact(c);
|
||||||
displayContact(db.getContact(c), m, incoming);
|
displayHeaders(contact, db.getPrivateMessageHeaders(c));
|
||||||
} catch(NoSuchContactException e) {
|
} catch(NoSuchContactException e) {
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
|
if(LOG.isLoggable(INFO)) LOG.info("Contact removed");
|
||||||
} catch(DbException e) {
|
} 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
|
private static class ConversationComparator
|
||||||
implements Comparator<ConversationListItem> {
|
implements Comparator<ConversationListItem> {
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
package net.sf.briar.android.messages;
|
package net.sf.briar.android.messages;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import net.sf.briar.android.DescendingHeaderComparator;
|
import net.sf.briar.android.DescendingHeaderComparator;
|
||||||
import net.sf.briar.api.Contact;
|
import net.sf.briar.api.Contact;
|
||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
import net.sf.briar.api.db.PrivateMessageHeader;
|
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 {
|
class ConversationListItem {
|
||||||
|
|
||||||
private final Set<MessageId> messageIds = new HashSet<MessageId>();
|
|
||||||
private final Contact contact;
|
private final Contact contact;
|
||||||
private String subject;
|
private final String subject;
|
||||||
private long timestamp;
|
private final long timestamp;
|
||||||
private int unread;
|
private final int unread;
|
||||||
|
|
||||||
ConversationListItem(Contact contact, List<PrivateMessageHeader> headers) {
|
ConversationListItem(Contact contact, List<PrivateMessageHeader> headers) {
|
||||||
if(headers.isEmpty()) throw new IllegalArgumentException();
|
if(headers.isEmpty()) throw new IllegalArgumentException();
|
||||||
@@ -27,30 +21,9 @@ class ConversationListItem {
|
|||||||
Collections.sort(headers, DescendingHeaderComparator.INSTANCE);
|
Collections.sort(headers, DescendingHeaderComparator.INSTANCE);
|
||||||
subject = headers.get(0).getSubject();
|
subject = headers.get(0).getSubject();
|
||||||
timestamp = headers.get(0).getTimestamp();
|
timestamp = headers.get(0).getTimestamp();
|
||||||
unread = 0;
|
int unread = 0;
|
||||||
for(PrivateMessageHeader h : headers) {
|
for(PrivateMessageHeader h : headers) if(!h.isRead()) unread++;
|
||||||
if(!h.isRead()) unread++;
|
this.unread = 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContactId getContactId() {
|
ContactId getContactId() {
|
||||||
|
|||||||
Reference in New Issue
Block a user