diff --git a/briar-android/res/drawable-hdpi/message_delivered.png b/briar-android/res/drawable-hdpi/message_delivered.png index 6edef05a9..efade6391 100644 Binary files a/briar-android/res/drawable-hdpi/message_delivered.png and b/briar-android/res/drawable-hdpi/message_delivered.png differ diff --git a/briar-android/res/drawable-hdpi/message_sent.png b/briar-android/res/drawable-hdpi/message_sent.png new file mode 100644 index 000000000..6edef05a9 Binary files /dev/null and b/briar-android/res/drawable-hdpi/message_sent.png differ diff --git a/briar-android/res/drawable-hdpi/message_stored.png b/briar-android/res/drawable-hdpi/message_stored.png new file mode 100644 index 000000000..8bf1ee459 Binary files /dev/null and b/briar-android/res/drawable-hdpi/message_stored.png differ diff --git a/briar-android/res/drawable-mdpi/message_delivered.png b/briar-android/res/drawable-mdpi/message_delivered.png index 1f3807209..938c50d90 100644 Binary files a/briar-android/res/drawable-mdpi/message_delivered.png and b/briar-android/res/drawable-mdpi/message_delivered.png differ diff --git a/briar-android/res/drawable-mdpi/message_sent.png b/briar-android/res/drawable-mdpi/message_sent.png new file mode 100644 index 000000000..1f3807209 Binary files /dev/null and b/briar-android/res/drawable-mdpi/message_sent.png differ diff --git a/briar-android/res/drawable-mdpi/message_stored.png b/briar-android/res/drawable-mdpi/message_stored.png new file mode 100644 index 000000000..01858ffc6 Binary files /dev/null and b/briar-android/res/drawable-mdpi/message_stored.png differ diff --git a/briar-android/res/drawable-xhdpi/message_delivered.png b/briar-android/res/drawable-xhdpi/message_delivered.png index a40d4d94c..991091136 100644 Binary files a/briar-android/res/drawable-xhdpi/message_delivered.png and b/briar-android/res/drawable-xhdpi/message_delivered.png differ diff --git a/briar-android/res/drawable-xhdpi/message_sent.png b/briar-android/res/drawable-xhdpi/message_sent.png new file mode 100644 index 000000000..a40d4d94c Binary files /dev/null and b/briar-android/res/drawable-xhdpi/message_sent.png differ diff --git a/briar-android/res/drawable-xhdpi/message_stored.png b/briar-android/res/drawable-xhdpi/message_stored.png new file mode 100644 index 000000000..b8eb1384a Binary files /dev/null and b/briar-android/res/drawable-xhdpi/message_stored.png differ diff --git a/briar-android/res/drawable-xxhdpi/message_stored.png b/briar-android/res/drawable-xxhdpi/message_stored.png new file mode 100644 index 000000000..153650a1f Binary files /dev/null and b/briar-android/res/drawable-xxhdpi/message_stored.png differ diff --git a/briar-android/res/drawable-xxxhdpi/message_stored.png b/briar-android/res/drawable-xxxhdpi/message_stored.png new file mode 100644 index 000000000..b3833bc3e Binary files /dev/null and b/briar-android/res/drawable-xxxhdpi/message_stored.png differ diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index 2375da4a8..828ef3b9f 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -8,7 +8,6 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static android.widget.LinearLayout.HORIZONTAL; import static android.widget.LinearLayout.VERTICAL; -import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static org.briarproject.android.contact.ReadPrivateMessageActivity.RESULT_PREV_NEXT; @@ -45,6 +44,7 @@ import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.MessageHeader; +import org.briarproject.api.db.MessageHeader.State; import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.NoSuchSubscriptionException; @@ -55,6 +55,7 @@ import org.briarproject.api.event.EventListener; import org.briarproject.api.event.MessageAddedEvent; import org.briarproject.api.event.MessageExpiredEvent; import org.briarproject.api.event.MessagesAckedEvent; +import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.messaging.Group; import org.briarproject.api.messaging.GroupId; import org.briarproject.api.messaging.Message; @@ -76,7 +77,6 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; -import android.widget.Toast; public class ConversationActivity extends BriarActivity implements EventListener, OnClickListener, OnItemClickListener { @@ -384,25 +384,31 @@ implements EventListener, OnClickListener, OnItemClickListener { } else if (e instanceof MessageExpiredEvent) { LOG.info("Message expired, reloading"); loadHeaders(); + } else if (e instanceof MessagesSentEvent) { + MessagesSentEvent m = (MessagesSentEvent) e; + if (m.getContactId().equals(contactId)) { + LOG.info("Messages sent"); + markMessages(m.getMessageIds(), State.SENT); + } } else if (e instanceof MessagesAckedEvent) { MessagesAckedEvent m = (MessagesAckedEvent) e; if (m.getContactId().equals(contactId)) { LOG.info("Messages acked"); - markMessagesDelivered(m.getMessageIds()); + markMessages(m.getMessageIds(), State.DELIVERED); } } } - private void markMessagesDelivered(final Collection acked) { + private void markMessages(final Collection messageIds, final State state) { runOnUiThread(new Runnable() { public void run() { - Set ackedSet = new HashSet(acked); + Set messages = new HashSet(messageIds); boolean changed = false; int count = adapter.getCount(); for (int i = 0; i < count; i++) { ConversationItem item = adapter.getItem(i); - if (ackedSet.contains(item.getHeader().getId())) { - item.setDelivered(true); + if (messages.contains(item.getHeader().getId())) { + item.setStatus(state); changed = true; } } @@ -417,7 +423,6 @@ implements EventListener, OnClickListener, OnItemClickListener { long timestamp = System.currentTimeMillis(); timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); createMessage(StringUtils.toUtf8(message), timestamp); - Toast.makeText(this, R.string.message_sent_toast, LENGTH_SHORT).show(); content.setText(""); hideSoftKeyboard(); } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java index 14699ab72..f78f4bb68 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java @@ -13,6 +13,7 @@ import org.briarproject.R; import org.briarproject.android.util.ElasticHorizontalSpace; import org.briarproject.android.util.LayoutUtils; import org.briarproject.api.db.MessageHeader; +import org.briarproject.api.db.MessageHeader.State; import org.briarproject.util.StringUtils; import android.content.Context; @@ -79,11 +80,16 @@ class ConversationAdapter extends ArrayAdapter { footer.addView(new ElasticHorizontalSpace(ctx)); - ImageView delivered = new ImageView(ctx); - delivered.setPadding(0, 0, pad, 0); - delivered.setImageResource(R.drawable.message_delivered); - if (!item.isDelivered()) delivered.setVisibility(INVISIBLE); - footer.addView(delivered); + ImageView status = new ImageView(ctx); + status.setPadding(0, 0, pad, 0); + if (item.getStatus() == State.DELIVERED) { + status.setImageResource(R.drawable.message_delivered); + } else if (item.getStatus() == State.SENT) { + status.setImageResource(R.drawable.message_sent); + } else { + status.setImageResource(R.drawable.message_stored); + } + footer.addView(status); TextView date = new TextView(ctx); date.setTextColor(res.getColor(R.color.private_message_date)); diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationItem.java index c1959f0c5..ac5637e16 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationItem.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationItem.java @@ -1,18 +1,19 @@ package org.briarproject.android.contact; import org.briarproject.api.db.MessageHeader; +import org.briarproject.api.db.MessageHeader.State; // This class is not thread-safe class ConversationItem { private final MessageHeader header; private byte[] body; - private boolean delivered; + private State status; ConversationItem(MessageHeader header) { this.header = header; body = null; - delivered = header.isDelivered(); + status = header.getStatus(); } MessageHeader getHeader() { @@ -27,11 +28,11 @@ class ConversationItem { this.body = body; } - boolean isDelivered() { - return delivered; + State getStatus() { + return status; } - void setDelivered(boolean delivered) { - this.delivered = delivered; + void setStatus(State state) { + this.status = state; } } diff --git a/briar-api/src/org/briarproject/api/db/MessageHeader.java b/briar-api/src/org/briarproject/api/db/MessageHeader.java index 779e4cfa0..c500971eb 100644 --- a/briar-api/src/org/briarproject/api/db/MessageHeader.java +++ b/briar-api/src/org/briarproject/api/db/MessageHeader.java @@ -6,17 +6,20 @@ import org.briarproject.api.messaging.MessageId; public class MessageHeader { + public enum State { STORED, SENT, DELIVERED }; + private final MessageId id, parent; private final GroupId groupId; private final Author author; private final Author.Status authorStatus; private final String contentType; private final long timestamp; - private final boolean local, read, delivered; + private final boolean local, read; + private final State status; public MessageHeader(MessageId id, MessageId parent, GroupId groupId, Author author, Author.Status authorStatus, String contentType, - long timestamp, boolean local, boolean read, boolean delivered) { + long timestamp, boolean local, boolean read, State status) { this.id = id; this.parent = parent; this.groupId = groupId; @@ -26,7 +29,7 @@ public class MessageHeader { this.timestamp = timestamp; this.local = local; this.read = read; - this.delivered = delivered; + this.status = status; } /** Returns the message's unique identifier. */ @@ -82,10 +85,9 @@ public class MessageHeader { } /** - * Returns true if the message has been delivered. (This only applies to - * locally generated private messages.) + * Returns message status. (This only applies to locally generated private messages.) */ - public boolean isDelivered() { - return delivered; + public State getStatus() { + return status; } } diff --git a/briar-api/src/org/briarproject/api/event/MessagesSentEvent.java b/briar-api/src/org/briarproject/api/event/MessagesSentEvent.java new file mode 100644 index 000000000..a70b7a872 --- /dev/null +++ b/briar-api/src/org/briarproject/api/event/MessagesSentEvent.java @@ -0,0 +1,27 @@ +package org.briarproject.api.event; + +import java.util.Collection; + +import org.briarproject.api.ContactId; +import org.briarproject.api.messaging.MessageId; + +/** An event that is broadcast when messages are sent to a contact. */ +public class MessagesSentEvent extends Event { + + private final ContactId contactId; + private final Collection messageIds; + + public MessagesSentEvent(ContactId contactId, + Collection messageIds) { + this.contactId = contactId; + this.messageIds = messageIds; + } + + public ContactId getContactId() { + return contactId; + } + + public Collection getMessageIds() { + return messageIds; + } +} diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java index 32467764e..fbf81fe6b 100644 --- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java +++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java @@ -52,6 +52,7 @@ import org.briarproject.api.event.MessageRequestedEvent; import org.briarproject.api.event.MessageToAckEvent; import org.briarproject.api.event.MessageToRequestEvent; import org.briarproject.api.event.MessagesAckedEvent; +import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.event.RemoteRetentionTimeUpdatedEvent; import org.briarproject.api.event.RemoteSubscriptionsUpdatedEvent; import org.briarproject.api.event.RemoteTransportsUpdatedEvent; @@ -380,6 +381,7 @@ DatabaseCleaner.Callback { lock.writeLock().unlock(); } if (messages.isEmpty()) return null; + if (!ids.isEmpty()) eventBus.broadcast(new MessagesSentEvent(c, ids)); return Collections.unmodifiableList(messages); } @@ -455,6 +457,7 @@ DatabaseCleaner.Callback { lock.writeLock().unlock(); } if (messages.isEmpty()) return null; + if (!ids.isEmpty()) eventBus.broadcast(new MessagesSentEvent(c, ids)); return Collections.unmodifiableList(messages); } diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java index 61add5a15..4ac27fd6a 100644 --- a/briar-core/src/org/briarproject/db/JdbcDatabase.java +++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java @@ -44,6 +44,7 @@ import org.briarproject.api.TransportProperties; import org.briarproject.api.db.DbClosedException; import org.briarproject.api.db.DbException; import org.briarproject.api.db.MessageHeader; +import org.briarproject.api.db.MessageHeader.State; import org.briarproject.api.messaging.Group; import org.briarproject.api.messaging.GroupId; import org.briarproject.api.messaging.Message; @@ -1452,7 +1453,7 @@ abstract class JdbcDatabase implements Database { if (rs.next()) throw new DbException(); // Get the message headers sql = "SELECT m.messageId, parentId, m.groupId, contentType," - + " timestamp, local, read, seen" + + " timestamp, local, read, seen, s.txCount" + " FROM messages AS m" + " JOIN groups AS g" + " ON m.groupId = g.groupId" @@ -1478,8 +1479,15 @@ abstract class JdbcDatabase implements Database { boolean read = rs.getBoolean(7); boolean seen = rs.getBoolean(8); Author author = local ? localAuthor : remoteAuthor; + + // initialize message status + State status; + if (seen) status = State.DELIVERED; + else if (rs.getInt(9) > 0) status = State.SENT; + else status = State.STORED; + headers.add(new MessageHeader(id, parent, groupId, author, - VERIFIED, contentType, timestamp, local, read, seen)); + VERIFIED, contentType, timestamp, local, read, status)); } rs.close(); ps.close(); @@ -1631,6 +1639,10 @@ abstract class JdbcDatabase implements Database { } } + /** + * This method is used to get group messages. + * The message status won't be used. + */ public Collection getMessageHeaders(Connection txn, GroupId g) throws DbException { PreparedStatement ps = null; @@ -1669,12 +1681,14 @@ abstract class JdbcDatabase implements Database { boolean read = rs.getBoolean(9); boolean isSelf = rs.getBoolean(10); boolean isContact = rs.getBoolean(11); + Author.Status status; if (author == null) status = ANONYMOUS; else if (isSelf || isContact) status = VERIFIED; else status = UNKNOWN; + headers.add(new MessageHeader(id, parent, g, author, status, - contentType, timestamp, local, read, false)); + contentType, timestamp, local, read, State.STORED)); } rs.close(); ps.close();