diff --git a/briar-android/src/org/briarproject/android/AndroidComponent.java b/briar-android/src/org/briarproject/android/AndroidComponent.java index 5bc21c1cc..d5b9a8393 100644 --- a/briar-android/src/org/briarproject/android/AndroidComponent.java +++ b/briar-android/src/org/briarproject/android/AndroidComponent.java @@ -9,6 +9,7 @@ import org.briarproject.android.forum.ForumPersistentData; import org.briarproject.android.report.BriarReportSender; import org.briarproject.api.contact.ContactExchangeTask; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.conversation.ConversationManager; import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.crypto.PasswordStrengthEstimator; @@ -84,6 +85,8 @@ public interface AndroidComponent extends CoreEagerSingletons { ContactManager contactManager(); + ConversationManager conversationManager(); + MessagingManager messagingManager(); PrivateMessageFactory privateMessageFactory(); diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index 08e2d2650..b27b4d0ff 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -23,6 +23,11 @@ import org.briarproject.android.util.BriarRecyclerView; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.conversation.ConversationForumInvitationItem; +import org.briarproject.api.conversation.ConversationIntroductionRequestItem; +import org.briarproject.api.conversation.ConversationIntroductionResponseItem; +import org.briarproject.api.conversation.ConversationItem; +import org.briarproject.api.conversation.ConversationMessageItem; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.event.ContactAddedEvent; @@ -41,6 +46,8 @@ import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.introduction.IntroductionManager; import org.briarproject.api.introduction.IntroductionMessage; +import org.briarproject.api.introduction.IntroductionRequest; +import org.briarproject.api.introduction.IntroductionResponse; import org.briarproject.api.messaging.MessagingManager; import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.plugins.ConnectionRegistry; @@ -257,7 +264,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { LOG.info("Message received, update contact"); PrivateMessageReceivedEvent p = (PrivateMessageReceivedEvent) e; PrivateMessageHeader h = p.getMessageHeader(); - updateItem(p.getGroupId(), ConversationItem.from(h)); + updateItem(p.getGroupId(), ConversationMessageItem.from(h)); } else if (e instanceof MessageStateChangedEvent) { MessageStateChangedEvent m = (MessageStateChangedEvent) e; ClientId c = m.getClientId(); @@ -354,7 +361,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { Collection headers = messagingManager.getMessageHeaders(id); for (PrivateMessageHeader h : headers) { - messages.add(ConversationItem.from(h)); + messages.add(ConversationMessageItem.from(h)); } long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) @@ -365,7 +372,12 @@ public class ContactListFragment extends BaseFragment implements EventListener { introductionManager .getIntroductionMessages(id); for (IntroductionMessage m : introductions) { - messages.add(ConversationItem.from(m)); + if (m instanceof IntroductionRequest) + messages.add(ConversationIntroductionRequestItem + .from((IntroductionRequest) m)); + else + messages.add(ConversationIntroductionResponseItem + .from((IntroductionResponse) m)); } duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) @@ -375,7 +387,7 @@ public class ContactListFragment extends BaseFragment implements EventListener { Collection invitations = forumSharingManager.getInvitationMessages(id); for (ForumInvitationMessage i : invitations) { - messages.add(ConversationItem.from(i)); + messages.add(ConversationForumInvitationItem.from(i)); } duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) diff --git a/briar-android/src/org/briarproject/android/contact/ContactListItem.java b/briar-android/src/org/briarproject/android/contact/ContactListItem.java index e436d4a9b..b880bc889 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListItem.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListItem.java @@ -1,13 +1,12 @@ package org.briarproject.android.contact; import org.briarproject.api.contact.Contact; +import org.briarproject.api.conversation.ConversationItem; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.GroupId; import java.util.Collection; -import static org.briarproject.android.contact.ConversationItem.IncomingItem; - // This class is not thread-safe public class ContactListItem { @@ -44,8 +43,8 @@ public class ContactListItem { empty = empty && message == null; if (message != null) { if (message.getTime() > timestamp) timestamp = message.getTime(); - if (message instanceof IncomingItem && - !((IncomingItem) message).isRead()) + if (message instanceof ConversationItem.IncomingItem && + !((ConversationItem.IncomingItem) message).isRead()) unread++; } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index c85e84d8c..a34ac9d42 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -30,14 +30,18 @@ import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.introduction.IntroductionActivity; import org.briarproject.android.util.BriarRecyclerView; import org.briarproject.api.FormatException; -import org.briarproject.api.clients.SessionId; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.conversation.ConversationIntroductionRequestItem; +import org.briarproject.api.conversation.ConversationIntroductionResponseItem; +import org.briarproject.api.conversation.ConversationItem; +import org.briarproject.api.conversation.ConversationItem.IncomingItem; +import org.briarproject.api.conversation.ConversationManager; +import org.briarproject.api.conversation.ConversationMessageItem; import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchContactException; -import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.event.ContactConnectedEvent; import org.briarproject.api.event.ContactDisconnectedEvent; import org.briarproject.api.event.ContactRemovedEvent; @@ -50,13 +54,8 @@ import org.briarproject.api.event.IntroductionResponseReceivedEvent; import org.briarproject.api.event.MessagesAckedEvent; import org.briarproject.api.event.MessagesSentEvent; import org.briarproject.api.event.PrivateMessageReceivedEvent; -import org.briarproject.api.forum.ForumInvitationMessage; -import org.briarproject.api.forum.ForumSharingManager; -import org.briarproject.api.introduction.IntroductionManager; -import org.briarproject.api.introduction.IntroductionMessage; import org.briarproject.api.introduction.IntroductionRequest; import org.briarproject.api.introduction.IntroductionResponse; -import org.briarproject.api.messaging.MessagingManager; import org.briarproject.api.messaging.PrivateMessage; import org.briarproject.api.messaging.PrivateMessageFactory; import org.briarproject.api.messaging.PrivateMessageHeader; @@ -68,10 +67,8 @@ import org.briarproject.util.StringUtils; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.logging.Logger; @@ -85,12 +82,11 @@ import static android.support.v4.app.ActivityOptionsCompat.makeCustomAnimation; 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.ConversationItem.IncomingItem; -import static org.briarproject.android.contact.ConversationItem.OutgoingItem; public class ConversationActivity extends BriarActivity implements EventListener, OnClickListener, - ConversationAdapter.IntroductionHandler { + ConversationAdapter.ConversationHandler, + ConversationAdapter.MessageUpdatedHandler { private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); @@ -116,22 +112,17 @@ public class ConversationActivity extends BriarActivity @Inject protected volatile ContactManager contactManager; @Inject - protected volatile MessagingManager messagingManager; + protected volatile ConversationManager conversationManager; @Inject protected volatile EventBus eventBus; @Inject volatile PrivateMessageFactory privateMessageFactory; - @Inject - protected volatile IntroductionManager introductionManager; - @Inject - protected volatile ForumSharingManager forumSharingManager; private volatile GroupId groupId = null; private volatile ContactId contactId = null; private volatile String contactName = null; private volatile byte[] contactIdenticonKey = null; private volatile boolean connected = false; - private volatile Map bodyCache = new HashMap<>(); @Override public void onCreate(Bundle state) { @@ -165,7 +156,7 @@ public class ConversationActivity extends BriarActivity ViewCompat.setTransitionName(toolbarAvatar, "avatar" + hexGroupId); ViewCompat.setTransitionName(toolbarStatus, "bulb" + hexGroupId); - adapter = new ConversationAdapter(this, this); + adapter = new ConversationAdapter(this, this, this); list = (BriarRecyclerView) findViewById(R.id.conversationView); list.setLayoutManager(new LinearLayoutManager(this)); list.setAdapter(adapter); @@ -267,7 +258,7 @@ public class ConversationActivity extends BriarActivity try { long now = System.currentTimeMillis(); if (contactId == null) - contactId = messagingManager.getContactId(groupId); + contactId = conversationManager.getContactId(groupId); if (contactName == null || contactIdenticonKey == null) { Contact contact = contactManager.getContact(contactId); contactName = contact.getAuthor().getName(); @@ -324,19 +315,13 @@ public class ConversationActivity extends BriarActivity try { long now = System.currentTimeMillis(); if (contactId == null) - contactId = messagingManager.getContactId(groupId); - Collection headers = - messagingManager.getMessageHeaders(contactId); - Collection introductions = - introductionManager - .getIntroductionMessages(contactId); - Collection invitations = - forumSharingManager - .getInvitationMessages(contactId); + contactId = conversationManager.getContactId(groupId); + List items = + conversationManager.getMessages(contactId); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading headers took " + duration + " ms"); - displayMessages(headers, introductions, invitations); + displayMessages(items); } catch (NoSuchContactException e) { finishOnUiThread(); } catch (DbException e) { @@ -347,46 +332,16 @@ public class ConversationActivity extends BriarActivity }); } - private void displayMessages(final Collection headers, - final Collection introductions, - final Collection invitations) { + private void displayMessages(final List items) { runOnUiThread(new Runnable() { @Override public void run() { sendButton.setEnabled(true); - if (headers.isEmpty() && introductions.isEmpty() && - invitations.isEmpty()) { + if (items.isEmpty()) { // we have no messages, // so let the list know to hide progress bar list.showData(); } else { - List items = new ArrayList<>(); - for (PrivateMessageHeader h : headers) { - ConversationMessageItem item = - (ConversationMessageItem) ConversationItem - .from(h); - byte[] body = bodyCache.get(h.getId()); - if (body == null) loadMessageBody(h); - else item.setBody(body); - items.add(item); - } - for (IntroductionMessage m : introductions) { - ConversationItem item; - if (m instanceof IntroductionRequest) { - item = ConversationItem - .from((IntroductionRequest) m); - } else { - item = ConversationItem - .from(ConversationActivity.this, - contactName, - (IntroductionResponse) m); - } - items.add(item); - } - for (ForumInvitationMessage i : invitations) { - ConversationItem item = ConversationItem.from(i); - items.add(item); - } adapter.addAll(items); // Scroll to the bottom list.scrollToPosition(adapter.getItemCount() - 1); @@ -395,47 +350,6 @@ public class ConversationActivity extends BriarActivity }); } - private void loadMessageBody(final PrivateMessageHeader h) { - runOnDbThread(new Runnable() { - @Override - public void run() { - try { - long now = System.currentTimeMillis(); - byte[] body = messagingManager.getMessageBody(h.getId()); - long duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Loading message took " + duration + " ms"); - displayMessageBody(h.getId(), body); - } catch (NoSuchMessageException e) { - // The item will be removed when we get the event - } catch (DbException e) { - if (LOG.isLoggable(WARNING)) - LOG.log(WARNING, e.toString(), e); - } - } - }); - } - - private void displayMessageBody(final MessageId m, final byte[] body) { - runOnUiThread(new Runnable() { - @Override - public void run() { - bodyCache.put(m, body); - SparseArray messages = - adapter.getPrivateMessages(); - for (int i = 0; i < messages.size(); i++) { - ConversationMessageItem item = messages.valueAt(i); - if (item.getId().equals(m)) { - item.setBody(body); - adapter.notifyItemChanged(messages.keyAt(i)); - list.scrollToPosition(adapter.getItemCount() - 1); - return; - } - } - } - }); - } - private void addConversationItem(final ConversationItem item) { runOnUiThread(new Runnable() { @Override @@ -448,11 +362,11 @@ public class ConversationActivity extends BriarActivity } private void markMessagesRead() { - List unread = new ArrayList<>(); + List unread = new ArrayList<>(); SparseArray list = adapter.getIncomingMessages(); for (int i = 0; i < list.size(); i++) { IncomingItem item = list.valueAt(i); - if (!item.isRead()) unread.add(item.getId()); + if (!item.isRead()) unread.add((ConversationItem) item); } if (unread.isEmpty()) return; if (LOG.isLoggable(INFO)) @@ -460,16 +374,14 @@ public class ConversationActivity extends BriarActivity markMessagesRead(Collections.unmodifiableList(unread)); } - private void markMessagesRead(final Collection unread) { + private void markMessagesRead(final Collection unread) { runOnDbThread(new Runnable() { @Override public void run() { try { long now = System.currentTimeMillis(); - for (MessageId m : unread) - // not really clean, but the messaging manager can - // handle introduction messages as well - messagingManager.setReadFlag(m, true); + for (ConversationItem item : unread) + conversationManager.setReadFlag(item, true); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Marking read took " + duration + " ms"); @@ -494,9 +406,10 @@ public class ConversationActivity extends BriarActivity if (p.getGroupId().equals(groupId)) { LOG.info("Message received, adding"); PrivateMessageHeader h = p.getMessageHeader(); - addConversationItem(ConversationItem.from(h)); - loadMessageBody(h); - markMessageReadIfNew(h); + ConversationMessageItem m = ConversationMessageItem.from(h); + conversationManager.loadMessageContent(m); + addConversationItem(m); + markMessageReadIfNew(m); } } else if (e instanceof MessagesSentEvent) { MessagesSentEvent m = (MessagesSentEvent) e; @@ -529,7 +442,8 @@ public class ConversationActivity extends BriarActivity (IntroductionRequestReceivedEvent) e; if (event.getContactId().equals(contactId)) { IntroductionRequest ir = event.getIntroductionRequest(); - ConversationItem item = new ConversationIntroductionInItem(ir); + ConversationItem item = + ConversationIntroductionRequestItem.from(ir); addConversationItem(item); } } else if (e instanceof IntroductionResponseReceivedEvent) { @@ -538,7 +452,7 @@ public class ConversationActivity extends BriarActivity if (event.getContactId().equals(contactId)) { IntroductionResponse ir = event.getIntroductionResponse(); ConversationItem item = - ConversationItem.from(this, contactName, ir); + ConversationIntroductionResponseItem.from(ir); addConversationItem(item); } } else if (e instanceof ForumInvitationReceivedEvent) { @@ -550,31 +464,31 @@ public class ConversationActivity extends BriarActivity } } - private void markMessageReadIfNew(final PrivateMessageHeader h) { + private void markMessageReadIfNew(final ConversationItem item) { runOnUiThread(new Runnable() { @Override public void run() { - ConversationItem item = adapter.getLastItem(); - if (item != null) { + ConversationItem last = adapter.getLastItem(); + if (last != null) { // Mark the message read if it's the newest message - long lastMsgTime = item.getTime(); - long newMsgTime = h.getTimestamp(); - if (newMsgTime > lastMsgTime) markNewMessageRead(h.getId()); + long lastMsgTime = last.getTime(); + long newMsgTime = item.getTime(); + if (newMsgTime > lastMsgTime) markNewMessageRead(item); else loadMessages(); } else { // mark the message as read as well if it is the first one - markNewMessageRead(h.getId()); + markNewMessageRead(item); } } }); } - private void markNewMessageRead(final MessageId m) { + private void markNewMessageRead(final ConversationItem item) { runOnDbThread(new Runnable() { @Override public void run() { try { - messagingManager.setReadFlag(m, true); + conversationManager.setReadFlag(item, true); loadMessages(); } catch (DbException e) { if (LOG.isLoggable(WARNING)) @@ -590,9 +504,10 @@ public class ConversationActivity extends BriarActivity @Override public void run() { Set messages = new HashSet<>(messageIds); - SparseArray list = adapter.getOutgoingMessages(); + SparseArray list = + adapter.getOutgoingMessages(); for (int i = 0; i < list.size(); i++) { - OutgoingItem item = list.valueAt(i); + ConversationItem.OutgoingItem item = list.valueAt(i); if (messages.contains(item.getId())) { item.setSent(sent); item.setSeen(seen); @@ -641,19 +556,10 @@ public class ConversationActivity extends BriarActivity public void run() { try { long now = System.currentTimeMillis(); - messagingManager.addLocalMessage(m); + ConversationItem item = conversationManager.addLocalMessage(m, body); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Storing message took " + duration + " ms"); - - PrivateMessageHeader h = new PrivateMessageHeader( - m.getMessage().getId(), - m.getMessage().getTimestamp(), m.getContentType(), - true, false, false, false); - ConversationMessageItem item = - (ConversationMessageItem) ConversationItem.from(h); - item.setBody(body); - bodyCache.put(m.getMessage().getId(), body); addConversationItem(item); } catch (DbException e) { if (LOG.isLoggable(WARNING)) @@ -688,7 +594,7 @@ public class ConversationActivity extends BriarActivity try { // make sure contactId is initialised if (contactId == null) - contactId = messagingManager.getContactId(groupId); + contactId = conversationManager.getContactId(groupId); // remove contact with that ID contactManager.removeContact(contactId); } catch (DbException e) { @@ -739,7 +645,7 @@ public class ConversationActivity extends BriarActivity } @Override - public void respondToIntroduction(final SessionId sessionId, + public void respondToItem(final ConversationItem item, final boolean accept) { runOnDbThread(new Runnable() { @Override @@ -747,22 +653,15 @@ public class ConversationActivity extends BriarActivity long timestamp = System.currentTimeMillis(); timestamp = Math.max(timestamp, getMinTimestampForNewMessage()); try { - if (accept) { - introductionManager - .acceptIntroduction(contactId, sessionId, - timestamp); - } else { - introductionManager - .declineIntroduction(contactId, sessionId, - timestamp); - } + conversationManager + .respondToItem(contactId, item, accept, timestamp); loadMessages(); } catch (DbException | FormatException e) { + // TODO decide how to make this type-agnostic introductionResponseError(); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } - } }); } @@ -778,4 +677,15 @@ public class ConversationActivity extends BriarActivity }); } + @Override + public void messageUpdated(final int position) { + runOnUiThread(new Runnable() { + @Override + public void run() { + adapter.notifyItemChanged(position); + // Scroll to the bottom + list.scrollToPosition(adapter.getItemCount() - 1); + } + }); + } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java index 2884350ea..554ed4e3d 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java @@ -15,27 +15,26 @@ import android.widget.TextView; import org.briarproject.R; import org.briarproject.android.forum.ForumInvitationsActivity; import org.briarproject.android.util.AndroidUtils; -import org.briarproject.api.clients.SessionId; +import org.briarproject.api.UniqueId; +import org.briarproject.api.conversation.ConversationForumInvitationItem; +import org.briarproject.api.conversation.ConversationIntroductionRequestItem; +import org.briarproject.api.conversation.ConversationIntroductionResponseItem; +import org.briarproject.api.conversation.ConversationItem; +import org.briarproject.api.conversation.ConversationItem.IncomingItem; +import org.briarproject.api.conversation.ConversationItem.OutgoingItem; +import org.briarproject.api.conversation.ConversationMessageItem; import org.briarproject.api.forum.ForumInvitationMessage; import org.briarproject.api.introduction.IntroductionRequest; +import org.briarproject.api.introduction.IntroductionResponse; import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.util.StringUtils; +import java.util.Arrays; import java.util.List; import static android.support.v7.util.SortedList.INVALID_POSITION; +import static android.support.v7.widget.RecyclerView.NO_ID; import static android.support.v7.widget.RecyclerView.ViewHolder; -import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_IN; -import static org.briarproject.android.contact.ConversationItem.FORUM_INVITATION_OUT; -import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_IN; -import static org.briarproject.android.contact.ConversationItem.INTRODUCTION_OUT; -import static org.briarproject.android.contact.ConversationItem.IncomingItem; -import static org.briarproject.android.contact.ConversationItem.MSG_IN; -import static org.briarproject.android.contact.ConversationItem.MSG_IN_UNREAD; -import static org.briarproject.android.contact.ConversationItem.MSG_OUT; -import static org.briarproject.android.contact.ConversationItem.NOTICE_IN; -import static org.briarproject.android.contact.ConversationItem.NOTICE_OUT; -import static org.briarproject.android.contact.ConversationItem.OutgoingItem; class ConversationAdapter extends RecyclerView.Adapter { @@ -43,13 +42,17 @@ class ConversationAdapter extends RecyclerView.Adapter { new SortedList<>(ConversationItem.class, new ListCallbacks()); private Context ctx; - private IntroductionHandler intro; + private ConversationHandler handler; + private MessageUpdatedHandler msgUpdated; private String contactName; public ConversationAdapter(Context context, - IntroductionHandler introductionHandler) { + ConversationHandler conversationHandler, + MessageUpdatedHandler messageUpdatedHandler) { ctx = context; - intro = introductionHandler; + handler = conversationHandler; + msgUpdated = messageUpdatedHandler; + setHasStableIds(true); } public void setContactName(String contactName) { @@ -59,82 +62,99 @@ class ConversationAdapter extends RecyclerView.Adapter { @Override public int getItemViewType(int position) { - return getItem(position).getType(); + ConversationItem m = getItem(position); + if (m instanceof IncomingItem) { + if (m instanceof ConversationMessageItem) { + return R.layout.list_item_msg_in; + } else if (m instanceof ConversationIntroductionRequestItem) { + return R.layout.list_item_introduction_in; + } else if (m instanceof ConversationIntroductionResponseItem) { + return R.layout.list_item_notice_in; + } else if (m instanceof ConversationForumInvitationItem) { + return R.layout.list_item_forum_invitation_in; + } + } else if (m instanceof OutgoingItem) { + if (m instanceof ConversationMessageItem) { + return R.layout.list_item_msg_out; + } else if (m instanceof ConversationIntroductionRequestItem) { + return R.layout.list_item_introduction_out; + } else if (m instanceof ConversationIntroductionResponseItem) { + return R.layout.list_item_notice_out; + } else if (m instanceof ConversationForumInvitationItem) { + return R.layout.list_item_forum_invitation_out; + } + } + throw new IllegalArgumentException("Unhandled Conversation Message"); } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) { - View v; + View v = LayoutInflater.from(viewGroup.getContext()).inflate( + type, viewGroup, false); - // outgoing message (local) - if (type == MSG_OUT) { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_msg_out, viewGroup, false); - return new MessageHolder(v, type); - } else if (type == INTRODUCTION_IN) { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_introduction_in, viewGroup, false); - return new IntroductionHolder(v, type); - } else if (type == INTRODUCTION_OUT) { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_introduction_out, viewGroup, false); - return new IntroductionHolder(v, type); - } else if (type == NOTICE_IN) { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_notice_in, viewGroup, false); - return new NoticeHolder(v, type); - } else if (type == NOTICE_OUT) { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_notice_out, viewGroup, false); - return new NoticeHolder(v, type); - } else if (type == FORUM_INVITATION_IN) { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_forum_invitation_in, viewGroup, false); - return new InvitationHolder(v, type); - } else if (type == FORUM_INVITATION_OUT) { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_forum_invitation_out, viewGroup, false); - return new InvitationHolder(v, type); - } - // incoming message (non-local) - else { - v = LayoutInflater.from(viewGroup.getContext()).inflate( - R.layout.list_item_msg_in, viewGroup, false); - return new MessageHolder(v, type); + switch (type) { + case R.layout.list_item_msg_in: + return new MessageHolder(v, false); + case R.layout.list_item_msg_out: + return new MessageHolder(v, true); + + case R.layout.list_item_introduction_in: + return new IntroductionHolder(v, false); + case R.layout.list_item_introduction_out: + return new IntroductionHolder(v, true); + + case R.layout.list_item_notice_in: + return new NoticeHolder(v, false); + case R.layout.list_item_notice_out: + return new NoticeHolder(v, true); + + case R.layout.list_item_forum_invitation_in: + return new InvitationHolder(v, false); + case R.layout.list_item_forum_invitation_out: + return new InvitationHolder(v, true); + + default: + throw new IllegalArgumentException( + "Unhandled Conversation Message"); } } @Override public void onBindViewHolder(ViewHolder ui, int position) { - ConversationItem item = getItem(position); - if (item instanceof ConversationMessageItem) { - bindMessage((MessageHolder) ui, (ConversationMessageItem) item); - } else if (item instanceof ConversationIntroductionOutItem) { - bindIntroduction((IntroductionHolder) ui, - (ConversationIntroductionOutItem) item, position); - } else if (item instanceof ConversationIntroductionInItem) { - bindIntroduction((IntroductionHolder) ui, - (ConversationIntroductionInItem) item, position); - } else if (item instanceof ConversationNoticeOutItem) { - bindNotice((NoticeHolder) ui, (ConversationNoticeOutItem) item); - } else if (item instanceof ConversationNoticeInItem) { - bindNotice((NoticeHolder) ui, (ConversationNoticeInItem) item); - } else if (item instanceof ConversationForumInvitationOutItem) { - bindInvitation((InvitationHolder) ui, - (ConversationForumInvitationOutItem) item); - } else if (item instanceof ConversationForumInvitationInItem) { - bindInvitation((InvitationHolder) ui, - (ConversationForumInvitationInItem) item); - } else { - throw new IllegalArgumentException("Unhandled Conversation Item"); + switch (ui.getItemViewType()) { + case R.layout.list_item_msg_in: + case R.layout.list_item_msg_out: + bindMessage((MessageHolder) ui, position); + break; + + case R.layout.list_item_introduction_in: + case R.layout.list_item_introduction_out: + bindIntroduction((IntroductionHolder) ui, position); + break; + + case R.layout.list_item_notice_in: + case R.layout.list_item_notice_out: + bindNotice((NoticeHolder) ui, position); + break; + + case R.layout.list_item_forum_invitation_in: + case R.layout.list_item_forum_invitation_out: + bindInvitation((InvitationHolder) ui, position); + break; + + default: + throw new IllegalArgumentException( + "Unhandled Conversation Message"); } } - private void bindMessage(MessageHolder ui, ConversationMessageItem item) { + private void bindMessage(final MessageHolder ui, int position) { + ConversationMessageItem item = + (ConversationMessageItem) getItem(position); PrivateMessageHeader header = item.getHeader(); - if (item instanceof ConversationItem.OutgoingItem) { + if (item instanceof OutgoingItem) { if (((OutgoingItem) item).isSeen()) { ui.status.setImageResource(R.drawable.message_delivered_white); } else if (((OutgoingItem) item).isSent()) { @@ -143,7 +163,7 @@ class ConversationAdapter extends RecyclerView.Adapter { ui.status.setImageResource(R.drawable.message_stored_white); } } else { - if (item.getType() == MSG_IN_UNREAD) { + if (!((IncomingItem) item).isRead()) { // TODO implement new unread message highlight according to #232 /* int left = ui.layout.getPaddingLeft(); int top = ui.layout.getPaddingTop(); @@ -162,6 +182,12 @@ class ConversationAdapter extends RecyclerView.Adapter { if (item.getBody() == null) { ui.body.setText("\u2026"); + item.setContentListener(new ConversationItem.ContentListener() { + @Override + public void contentReady() { + msgUpdated.messageUpdated(ui.getAdapterPosition()); + } + }); } else if (header.getContentType().equals("text/plain")) { ui.body.setText( StringUtils.trim(StringUtils.fromUtf8(item.getBody()))); @@ -173,9 +199,10 @@ class ConversationAdapter extends RecyclerView.Adapter { ui.date.setText(AndroidUtils.formatDate(ctx, timestamp)); } - private void bindIntroduction(IntroductionHolder ui, - final ConversationIntroductionItem item, final int position) { + private void bindIntroduction(IntroductionHolder ui, final int position) { + final ConversationIntroductionRequestItem item = + (ConversationIntroductionRequestItem) getItem(position); final IntroductionRequest ir = item.getIntroductionRequest(); String message = ir.getMessage(); @@ -189,11 +216,10 @@ class ConversationAdapter extends RecyclerView.Adapter { } // Outgoing Introduction Request - if (item instanceof ConversationIntroductionOutItem) { + if (item instanceof OutgoingItem) { ui.text.setText(ctx.getString(R.string.introduction_request_sent, contactName, ir.getName())); - ConversationIntroductionOutItem i = - (ConversationIntroductionOutItem) item; + OutgoingItem i = (OutgoingItem) item; if (i.isSeen()) { ui.status.setImageResource(R.drawable.message_delivered); ui.message.status.setImageResource( @@ -239,7 +265,7 @@ class ConversationAdapter extends RecyclerView.Adapter { ui.acceptButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - intro.respondToIntroduction(ir.getSessionId(), true); + handler.respondToItem(item, true); item.setAnswered(true); notifyItemChanged(position); } @@ -249,7 +275,7 @@ class ConversationAdapter extends RecyclerView.Adapter { ui.declineButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - intro.respondToIntroduction(ir.getSessionId(), false); + handler.respondToItem(item, false); item.setAnswered(true); notifyItemChanged(position); } @@ -258,13 +284,14 @@ class ConversationAdapter extends RecyclerView.Adapter { ui.date.setText(AndroidUtils.formatDate(ctx, item.getTime())); } - private void bindNotice(NoticeHolder ui, ConversationNoticeItem item) { + private void bindNotice(NoticeHolder ui, int position) { + ConversationItem item = getItem(position); - ui.text.setText(item.getText()); + ui.text.setText(getNoticeText(item)); ui.date.setText(AndroidUtils.formatDate(ctx, item.getTime())); - if (item instanceof ConversationNoticeOutItem) { - ConversationNoticeOutItem n = (ConversationNoticeOutItem) item; + if (item instanceof OutgoingItem) { + OutgoingItem n = (OutgoingItem) item; if (n.isSeen()) { ui.status.setImageResource(R.drawable.message_delivered); } else if (n.isSent()) { @@ -275,9 +302,49 @@ class ConversationAdapter extends RecyclerView.Adapter { } } - private void bindInvitation(InvitationHolder ui, - final ConversationForumInvitationItem item) { + private String getNoticeText(ConversationItem m) { + if (m instanceof ConversationIntroductionResponseItem) { + IntroductionResponse ir = + ((ConversationIntroductionResponseItem) m) + .getIntroductionResponse(); + if (ir.isLocal()) { + if (ir.wasAccepted()) { + return ctx.getString( + R.string.introduction_response_accepted_sent, + ir.getName()); + } else { + return ctx.getString( + R.string.introduction_response_declined_sent, + ir.getName()); + } + } else { + if (ir.wasAccepted()) { + return ctx.getString( + R.string.introduction_response_accepted_received, + contactName, ir.getName()); + } else { + if (ir.isIntroducer()) { + return ctx.getString( + R.string.introduction_response_declined_received, + contactName, ir.getName()); + } else { + return ctx.getString( + R.string.introduction_response_declined_received_by_introducee, + contactName, ir.getName()); + } + } + } + } else { + throw new IllegalArgumentException( + "Unhandled Conversation Message"); + } + } + + private void bindInvitation(InvitationHolder ui, int position) { + + ConversationForumInvitationItem item = + (ConversationForumInvitationItem) getItem(position); ForumInvitationMessage fim = item.getForumInvitationMessage(); String message = fim.getMessage(); @@ -291,11 +358,10 @@ class ConversationAdapter extends RecyclerView.Adapter { } // Outgoing Invitation - if (item instanceof ConversationForumInvitationOutItem) { + if (item instanceof OutgoingItem) { ui.text.setText(ctx.getString(R.string.forum_invitation_sent, fim.getForumName(), contactName)); - ConversationForumInvitationOutItem i = - (ConversationForumInvitationOutItem) item; + OutgoingItem i = (OutgoingItem) item; if (i.isSeen()) { ui.status.setImageResource(R.drawable.message_delivered); ui.message.status.setImageResource( @@ -338,6 +404,23 @@ class ConversationAdapter extends RecyclerView.Adapter { return items.size(); } + @Override + public long getItemId(int position) { + ConversationItem m = getItem(position); + if (m == null) { + return NO_ID; + } + byte[] b = m.getId().getBytes(); + // Technically this could result in collisions because hashCode is not + // guaranteed to be collision-resistant. We could instead use BLAKE2s + // with hash output set to 8 bytes, and then convert that to a long. + long id = Arrays.hashCode(Arrays.copyOf(b, UniqueId.LENGTH / 2)); + id <<= 32; + id |= Arrays.hashCode( + Arrays.copyOfRange(b, UniqueId.LENGTH / 2, UniqueId.LENGTH)); + return id; + } + public ConversationItem getItem(int position) { if (position == INVALID_POSITION || items.size() <= position) { return null; // Not found @@ -354,7 +437,8 @@ class ConversationAdapter extends RecyclerView.Adapter { } public SparseArray getIncomingMessages() { - SparseArray messages = new SparseArray<>(); + SparseArray messages = + new SparseArray<>(); for (int i = 0; i < items.size(); i++) { ConversationItem item = items.get(i); @@ -366,7 +450,8 @@ class ConversationAdapter extends RecyclerView.Adapter { } public SparseArray getOutgoingMessages() { - SparseArray messages = new SparseArray<>(); + SparseArray messages = + new SparseArray<>(); for (int i = 0; i < items.size(); i++) { ConversationItem item = items.get(i); @@ -377,18 +462,6 @@ class ConversationAdapter extends RecyclerView.Adapter { return messages; } - public SparseArray getPrivateMessages() { - SparseArray messages = new SparseArray<>(); - - for (int i = 0; i < items.size(); i++) { - ConversationItem item = items.get(i); - if (item instanceof ConversationMessageItem) { - messages.put(i, (ConversationMessageItem) item); - } - } - return messages; - } - public void add(final ConversationItem message) { this.items.add(message); } @@ -408,7 +481,7 @@ class ConversationAdapter extends RecyclerView.Adapter { public TextView date; public ImageView status; - public MessageHolder(View v, int type) { + public MessageHolder(View v, boolean outgoing) { super(v); layout = (ViewGroup) v.findViewById(R.id.msgLayout); @@ -416,7 +489,7 @@ class ConversationAdapter extends RecyclerView.Adapter { date = (TextView) v.findViewById(R.id.msgTime); // outgoing message (local) - if (type == MSG_OUT) { + if (outgoing) { status = (ImageView) v.findViewById(R.id.msgStatus); } } @@ -432,18 +505,17 @@ class ConversationAdapter extends RecyclerView.Adapter { private final TextView date; private final ImageView status; - public IntroductionHolder(View v, int type) { + public IntroductionHolder(View v, boolean outgoing) { super(v); messageLayout = v.findViewById(R.id.messageLayout); - message = new MessageHolder(messageLayout, - type == INTRODUCTION_IN ? MSG_IN : MSG_OUT); + message = new MessageHolder(messageLayout, outgoing); text = (TextView) v.findViewById(R.id.introductionText); acceptButton = (Button) v.findViewById(R.id.acceptButton); declineButton = (Button) v.findViewById(R.id.declineButton); date = (TextView) v.findViewById(R.id.introductionTime); - if (type == INTRODUCTION_OUT) { + if (outgoing) { status = (ImageView) v.findViewById(R.id.introductionStatus); } else { status = null; @@ -457,13 +529,13 @@ class ConversationAdapter extends RecyclerView.Adapter { private final TextView date; private final ImageView status; - public NoticeHolder(View v, int type) { + public NoticeHolder(View v, boolean outgoing) { super(v); text = (TextView) v.findViewById(R.id.noticeText); date = (TextView) v.findViewById(R.id.noticeTime); - if (type == NOTICE_OUT) { + if (outgoing) { status = (ImageView) v.findViewById(R.id.noticeStatus); } else { status = null; @@ -480,17 +552,16 @@ class ConversationAdapter extends RecyclerView.Adapter { private final TextView date; private final ImageView status; - public InvitationHolder(View v, int type) { + public InvitationHolder(View v, boolean outgoing) { super(v); messageLayout = v.findViewById(R.id.messageLayout); - message = new MessageHolder(messageLayout, - type == FORUM_INVITATION_IN ? MSG_IN : MSG_OUT); + message = new MessageHolder(messageLayout, outgoing); text = (TextView) v.findViewById(R.id.introductionText); showForumsButton = (Button) v.findViewById(R.id.showForumsButton); date = (TextView) v.findViewById(R.id.introductionTime); - if (type == FORUM_INVITATION_OUT) { + if (outgoing) { status = (ImageView) v.findViewById(R.id.introductionStatus); } else { status = null; @@ -498,7 +569,8 @@ class ConversationAdapter extends RecyclerView.Adapter { } } - private class ListCallbacks extends SortedList.Callback { + private class ListCallbacks + extends SortedList.Callback { @Override public void onInserted(int position, int count) { @@ -543,7 +615,11 @@ class ConversationAdapter extends RecyclerView.Adapter { } } - public interface IntroductionHandler { - void respondToIntroduction(SessionId sessionId, boolean accept); + public interface ConversationHandler { + void respondToItem(ConversationItem item, boolean accept); + } + + public interface MessageUpdatedHandler { + void messageUpdated(int position); } } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationInItem.java deleted file mode 100644 index b082fe4d6..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationInItem.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.forum.ForumInvitationMessage; - -// This class is not thread-safe -public class ConversationForumInvitationInItem - extends ConversationForumInvitationItem - implements ConversationItem.IncomingItem { - - private boolean read; - - public ConversationForumInvitationInItem(ForumInvitationMessage fim) { - super(fim); - - this.read = fim.isRead(); - } - - @Override - int getType() { - return FORUM_INVITATION_IN; - } - - @Override - public boolean isRead() { - return read; - } - - @Override - public void setRead(boolean read) { - this.read = read; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationItem.java deleted file mode 100644 index eb303d16b..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationItem.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.forum.ForumInvitationMessage; - -abstract class ConversationForumInvitationItem extends ConversationItem { - - private final ForumInvitationMessage fim; - - public ConversationForumInvitationItem(ForumInvitationMessage fim) { - super(fim.getId(), fim.getTimestamp()); - - this.fim = fim; - } - - public ForumInvitationMessage getForumInvitationMessage() { - return fim; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationOutItem.java deleted file mode 100644 index 202023dda..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationForumInvitationOutItem.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.forum.ForumInvitationMessage; - -/** - * This class is needed and can not be replaced by an ConversationNoticeOutItem, - * because it carries the optional invitation message - * to be displayed as a regular private message. - *

- * This class is not thread-safe - */ -public class ConversationForumInvitationOutItem - extends ConversationForumInvitationItem - implements ConversationItem.OutgoingItem { - - private boolean sent, seen; - - public ConversationForumInvitationOutItem(ForumInvitationMessage fim) { - super(fim); - this.sent = fim.isSent(); - this.seen = fim.isSeen(); - } - - @Override - int getType() { - return FORUM_INVITATION_OUT; - } - - @Override - public boolean isSent() { - return sent; - } - - @Override - public void setSent(boolean sent) { - this.sent = sent; - } - - @Override - public boolean isSeen() { - return seen; - } - - @Override - public void setSeen(boolean seen) { - this.seen = seen; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java deleted file mode 100644 index caefec6a6..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionInItem.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.introduction.IntroductionRequest; - -// This class is not thread-safe -public class ConversationIntroductionInItem extends ConversationIntroductionItem - implements ConversationItem.IncomingItem { - - private boolean read; - - public ConversationIntroductionInItem(IntroductionRequest ir) { - super(ir); - - this.read = ir.isRead(); - } - - @Override - int getType() { - return INTRODUCTION_IN; - } - - @Override - public boolean isRead() { - return read; - } - - @Override - public void setRead(boolean read) { - this.read = read; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java deleted file mode 100644 index 51a7c3ebb..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionItem.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.introduction.IntroductionRequest; - -// This class is not thread-safe -abstract class ConversationIntroductionItem extends ConversationItem { - - private final IntroductionRequest ir; - private boolean answered; - - public ConversationIntroductionItem(IntroductionRequest ir) { - super(ir.getMessageId(), ir.getTimestamp()); - - this.ir = ir; - this.answered = ir.wasAnswered(); - } - - public IntroductionRequest getIntroductionRequest() { - return ir; - } - - public boolean wasAnswered() { - return answered; - } - - public void setAnswered(boolean answered) { - this.answered = answered; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java deleted file mode 100644 index 7c7082a8d..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationIntroductionOutItem.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.introduction.IntroductionRequest; - -/** - * This class is needed and can not be replaced by an ConversationNoticeOutItem, - * because it carries the optional introduction message - * to be displayed as a regular private message. - * - * This class is not thread-safe - */ -public class ConversationIntroductionOutItem - extends ConversationIntroductionItem - implements ConversationItem.OutgoingItem { - - private boolean sent, seen; - - public ConversationIntroductionOutItem(IntroductionRequest ir) { - super(ir); - this.sent = ir.isSent(); - this.seen = ir.isSeen(); - } - - @Override - int getType() { - return INTRODUCTION_OUT; - } - - @Override - public boolean isSent() { - return sent; - } - - @Override - public void setSent(boolean sent) { - this.sent = sent; - } - - @Override - public boolean isSeen() { - return seen; - } - - @Override - public void setSeen(boolean seen) { - this.seen = seen; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationItem.java b/briar-android/src/org/briarproject/android/contact/ConversationItem.java deleted file mode 100644 index 7b75cb13a..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationItem.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.briarproject.android.contact; - -import android.content.Context; - -import org.briarproject.R; -import org.briarproject.api.forum.ForumInvitationMessage; -import org.briarproject.api.introduction.IntroductionMessage; -import org.briarproject.api.introduction.IntroductionRequest; -import org.briarproject.api.introduction.IntroductionResponse; -import org.briarproject.api.messaging.PrivateMessageHeader; -import org.briarproject.api.sync.MessageId; - -// This class is not thread-safe -public abstract class ConversationItem { - - // this is needed for RecyclerView adapter which requires an int type - final static int MSG_IN = 0; - final static int MSG_IN_UNREAD = 1; - final static int MSG_OUT = 2; - final static int INTRODUCTION_IN = 3; - final static int INTRODUCTION_OUT = 4; - final static int NOTICE_IN = 5; - final static int NOTICE_OUT = 6; - final static int FORUM_INVITATION_IN = 7; - final static int FORUM_INVITATION_OUT = 8; - - private MessageId id; - private long time; - - public ConversationItem(MessageId id, long time) { - this.id = id; - this.time = time; - } - - abstract int getType(); - - public MessageId getId() { - return id; - } - - long getTime() { - return time; - } - - public static ConversationItem from(PrivateMessageHeader h) { - if (h.isLocal()) - return new ConversationMessageOutItem(h); - else - return new ConversationMessageInItem(h); - } - - public static ConversationItem from(IntroductionRequest ir) { - if (ir.isLocal()) { - return new ConversationIntroductionOutItem(ir); - } else { - return new ConversationIntroductionInItem(ir); - } - } - - public static ConversationItem from(Context ctx, String contactName, - IntroductionResponse ir) { - - if (ir.isLocal()) { - String text; - if (ir.wasAccepted()) { - text = ctx.getString( - R.string.introduction_response_accepted_sent, - ir.getName()); - } else { - text = ctx.getString( - R.string.introduction_response_declined_sent, - ir.getName()); - } - return new ConversationNoticeOutItem(ir.getMessageId(), text, - ir.getTimestamp(), ir.isSent(), ir.isSeen()); - } else { - String text; - if (ir.wasAccepted()) { - text = ctx.getString( - R.string.introduction_response_accepted_received, - contactName, ir.getName()); - } else { - if (ir.isIntroducer()) { - text = ctx.getString( - R.string.introduction_response_declined_received, - contactName, ir.getName()); - } else { - text = ctx.getString( - R.string.introduction_response_declined_received_by_introducee, - contactName, ir.getName()); - } - } - return new ConversationNoticeInItem(ir.getMessageId(), text, - ir.getTimestamp(), ir.isRead()); - } - } - - public static ConversationItem from(ForumInvitationMessage fim) { - if (fim.isLocal()) { - return new ConversationForumInvitationOutItem(fim); - } else { - return new ConversationForumInvitationInItem(fim); - } - } - - /** - * This method should not be used to get user-facing objects, - * Its purpose is to provider data for the contact list. - */ - public static ConversationItem from(IntroductionMessage im) { - if (im.isLocal()) - return new ConversationNoticeOutItem(im.getMessageId(), "", - im.getTimestamp(), false, false); - return new ConversationNoticeInItem(im.getMessageId(), "", - im.getTimestamp(), im.isRead()); - } - - protected interface OutgoingItem { - - MessageId getId(); - - boolean isSent(); - - void setSent(boolean sent); - - boolean isSeen(); - - void setSeen(boolean seen); - } - - protected interface IncomingItem { - - MessageId getId(); - - boolean isRead(); - - void setRead(boolean read); - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationMessageInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationMessageInItem.java deleted file mode 100644 index 7969aebd7..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationMessageInItem.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.messaging.PrivateMessageHeader; - -// This class is not thread-safe -public class ConversationMessageInItem extends ConversationMessageItem - implements ConversationItem.IncomingItem { - - private boolean read; - - public ConversationMessageInItem(PrivateMessageHeader header) { - super(header); - - read = header.isRead(); - } - - @Override - int getType() { - return MSG_IN; - } - - @Override - public boolean isRead() { - return read; - } - - @Override - public void setRead(boolean read) { - this.read = read; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationMessageItem.java b/briar-android/src/org/briarproject/android/contact/ConversationMessageItem.java deleted file mode 100644 index f151cde17..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationMessageItem.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.messaging.PrivateMessageHeader; - -// This class is not thread-safe -abstract class ConversationMessageItem extends ConversationItem { - - private final PrivateMessageHeader header; - private byte[] body; - - public ConversationMessageItem(PrivateMessageHeader header) { - super(header.getId(), header.getTimestamp()); - - this.header = header; - body = null; - } - - PrivateMessageHeader getHeader() { - return header; - } - - byte[] getBody() { - return body; - } - - void setBody(byte[] body) { - this.body = body; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationMessageOutItem.java b/briar-android/src/org/briarproject/android/contact/ConversationMessageOutItem.java deleted file mode 100644 index 7fc602554..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationMessageOutItem.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.messaging.PrivateMessageHeader; - -// This class is not thread-safe -public class ConversationMessageOutItem extends ConversationMessageItem - implements ConversationItem.OutgoingItem { - - private boolean sent, seen; - - public ConversationMessageOutItem(PrivateMessageHeader header) { - super(header); - - sent = header.isSent(); - seen = header.isSeen(); - } - - @Override - int getType() { - return MSG_OUT; - } - - @Override - public boolean isSent() { - return sent; - } - - @Override - public void setSent(boolean sent) { - this.sent = sent; - } - - @Override - public boolean isSeen() { - return seen; - } - - @Override - public void setSeen(boolean seen) { - this.seen = seen; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java deleted file mode 100644 index 9b4acb556..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeInItem.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.sync.MessageId; - -// This class is not thread-safe -public class ConversationNoticeInItem extends ConversationNoticeItem - implements ConversationItem.IncomingItem { - - private boolean read; - - public ConversationNoticeInItem(MessageId id, String text, long time, - boolean read) { - super(id, text, time); - - this.read = read; - } - - @Override - int getType() { - return NOTICE_IN; - } - - @Override - public boolean isRead() { - return read; - } - - @Override - public void setRead(boolean read) { - this.read = read; - } -} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeItem.java b/briar-android/src/org/briarproject/android/contact/ConversationNoticeItem.java deleted file mode 100644 index 8245f7758..000000000 --- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeItem.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.briarproject.android.contact; - -import org.briarproject.api.sync.MessageId; - -abstract class ConversationNoticeItem extends ConversationItem { - - private final String text; - - public ConversationNoticeItem(MessageId id, String text, long time) { - super(id, time); - - this.text = text; - } - - public String getText() { - return text; - } -} diff --git a/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java b/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java index fa5ad2284..ece15fed7 100644 --- a/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java +++ b/briar-android/src/org/briarproject/android/forum/SelectableContactListItem.java @@ -1,8 +1,8 @@ package org.briarproject.android.forum; import org.briarproject.android.contact.ContactListItem; -import org.briarproject.android.contact.ConversationItem; import org.briarproject.api.contact.Contact; +import org.briarproject.api.conversation.ConversationItem; import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.sync.GroupId; diff --git a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java index 8bf810454..eadc37223 100644 --- a/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java +++ b/briar-android/src/org/briarproject/android/introduction/ContactChooserFragment.java @@ -15,20 +15,17 @@ import org.briarproject.R; import org.briarproject.android.ActivityComponent; import org.briarproject.android.contact.ContactListAdapter; import org.briarproject.android.contact.ContactListItem; -import org.briarproject.android.contact.ConversationItem; import org.briarproject.android.fragment.BaseFragment; import org.briarproject.android.util.BriarRecyclerView; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.conversation.ConversationManager; +import org.briarproject.api.conversation.ConversationItem; import org.briarproject.api.db.DbException; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.IdentityManager; import org.briarproject.api.identity.LocalAuthor; -import org.briarproject.api.introduction.IntroductionManager; -import org.briarproject.api.introduction.IntroductionMessage; -import org.briarproject.api.messaging.MessagingManager; -import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.plugins.ConnectionRegistry; import org.briarproject.api.sync.GroupId; @@ -61,9 +58,7 @@ public class ContactChooserFragment extends BaseFragment { @Inject protected volatile IdentityManager identityManager; @Inject - protected volatile MessagingManager messagingManager; - @Inject - protected volatile IntroductionManager introductionManager; + protected volatile ConversationManager conversationManager; @Inject protected volatile ConnectionRegistry connectionRegistry; @@ -166,7 +161,7 @@ public class ContactChooserFragment extends BaseFragment { } else { ContactId id = c.getId(); GroupId groupId = - messagingManager.getConversationId(id); + conversationManager.getConversationId(id); Collection messages = getMessages(id); boolean connected = @@ -227,28 +222,12 @@ public class ContactChooserFragment extends BaseFragment { long now = System.currentTimeMillis(); - Collection messages = new ArrayList<>(); - - Collection headers = - messagingManager.getMessageHeaders(id); - for (PrivateMessageHeader h : headers) { - messages.add(ConversationItem.from(h)); - } + Collection messages = + conversationManager.getMessages(id, false); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) LOG.info("Loading message headers took " + duration + " ms"); - now = System.currentTimeMillis(); - Collection introductions = - introductionManager - .getIntroductionMessages(id); - for (IntroductionMessage m : introductions) { - messages.add(ConversationItem.from(m)); - } - duration = System.currentTimeMillis() - now; - if (LOG.isLoggable(INFO)) - LOG.info("Loading introduction messages took " + duration + " ms"); - return messages; } } diff --git a/briar-api/src/org/briarproject/api/conversation/ConversationForumInvitationItem.java b/briar-api/src/org/briarproject/api/conversation/ConversationForumInvitationItem.java new file mode 100644 index 000000000..53950d07a --- /dev/null +++ b/briar-api/src/org/briarproject/api/conversation/ConversationForumInvitationItem.java @@ -0,0 +1,8 @@ +package org.briarproject.api.conversation; + +import org.briarproject.api.forum.ForumInvitationMessage; + +public interface ConversationForumInvitationItem extends ConversationItem { + + ForumInvitationMessage getForumInvitationMessage(); +} diff --git a/briar-api/src/org/briarproject/api/conversation/ConversationIntroductionRequestItem.java b/briar-api/src/org/briarproject/api/conversation/ConversationIntroductionRequestItem.java new file mode 100644 index 000000000..9565c82d0 --- /dev/null +++ b/briar-api/src/org/briarproject/api/conversation/ConversationIntroductionRequestItem.java @@ -0,0 +1,12 @@ +package org.briarproject.api.conversation; + +import org.briarproject.api.introduction.IntroductionRequest; + +public interface ConversationIntroductionRequestItem extends ConversationItem { + + IntroductionRequest getIntroductionRequest(); + + boolean wasAnswered(); + + void setAnswered(boolean answered); +} diff --git a/briar-api/src/org/briarproject/api/conversation/ConversationIntroductionResponseItem.java b/briar-api/src/org/briarproject/api/conversation/ConversationIntroductionResponseItem.java new file mode 100644 index 000000000..dcf225374 --- /dev/null +++ b/briar-api/src/org/briarproject/api/conversation/ConversationIntroductionResponseItem.java @@ -0,0 +1,8 @@ +package org.briarproject.api.conversation; + +import org.briarproject.api.introduction.IntroductionResponse; + +public interface ConversationIntroductionResponseItem extends ConversationItem { + + IntroductionResponse getIntroductionResponse(); +} diff --git a/briar-api/src/org/briarproject/api/conversation/ConversationItem.java b/briar-api/src/org/briarproject/api/conversation/ConversationItem.java new file mode 100644 index 000000000..ee3bfdd20 --- /dev/null +++ b/briar-api/src/org/briarproject/api/conversation/ConversationItem.java @@ -0,0 +1,40 @@ +package org.briarproject.api.conversation; + +import org.briarproject.api.sync.MessageId; + +public interface ConversationItem { + + MessageId getId(); + + long getTime(); + + interface ContentListener { + + void contentReady(); + } + + interface Partial extends ConversationItem { + + void setContent(byte[] content); + + void setContentListener(ContentListener listener); + } + + interface IncomingItem extends ConversationItem { + + boolean isRead(); + + void setRead(boolean read); + } + + interface OutgoingItem extends ConversationItem { + + boolean isSent(); + + void setSent(boolean sent); + + boolean isSeen(); + + void setSeen(boolean seen); + } +} diff --git a/briar-api/src/org/briarproject/api/conversation/ConversationManager.java b/briar-api/src/org/briarproject/api/conversation/ConversationManager.java new file mode 100644 index 000000000..d6119c6de --- /dev/null +++ b/briar-api/src/org/briarproject/api/conversation/ConversationManager.java @@ -0,0 +1,61 @@ +package org.briarproject.api.conversation; + +import org.briarproject.api.FormatException; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.db.DbException; +import org.briarproject.api.messaging.PrivateMessage; +import org.briarproject.api.sync.ClientId; +import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; + +import java.util.List; + +public interface ConversationManager { + + /** + * Returns the unique ID of the conversation client. + */ + ClientId getClientId(); + + /** + * Stores a local private message, and returns the corresponding item. + */ + ConversationItem addLocalMessage(PrivateMessage m, byte[] body) throws DbException; + + /** + * Returns the ID of the contact with the given private conversation. + */ + ContactId getContactId(GroupId g) throws DbException; + + /** + * Returns the ID of the private conversation with the given contact. + */ + GroupId getConversationId(ContactId c) throws DbException; + + /** + * Returns all messages in the given private conversation. + */ + List getMessages(ContactId c) throws DbException; + + /** + * Returns all messages in the given private conversation. + */ + List getMessages(ContactId c, boolean content) + throws DbException; + + /** + * Starts a background task to load the content of the given message. + */ + void loadMessageContent(ConversationItem.Partial m); + + /** + * Respond to a conversation item with accept/decline. + */ + void respondToItem(ContactId c, ConversationItem item, boolean accept, + long timestamp) throws DbException, FormatException; + + /** + * Marks a conversation item as read or unread. + */ + void setReadFlag(ConversationItem item, boolean read) throws DbException; +} diff --git a/briar-api/src/org/briarproject/api/conversation/ConversationMessageItem.java b/briar-api/src/org/briarproject/api/conversation/ConversationMessageItem.java new file mode 100644 index 000000000..35ec1f203 --- /dev/null +++ b/briar-api/src/org/briarproject/api/conversation/ConversationMessageItem.java @@ -0,0 +1,11 @@ +package org.briarproject.api.conversation; + +import org.briarproject.api.conversation.ConversationItem.Partial; +import org.briarproject.api.messaging.PrivateMessageHeader; + +public interface ConversationMessageItem extends Partial { + + PrivateMessageHeader getHeader(); + + byte[] getBody(); +} diff --git a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java index 365cf18e7..b4ed94152 100644 --- a/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java +++ b/briar-api/src/org/briarproject/api/forum/ForumSharingManager.java @@ -6,6 +6,7 @@ import org.briarproject.api.db.DbException; import org.briarproject.api.sharing.SharingManager; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; import java.util.Collection; @@ -46,4 +47,7 @@ public interface ForumSharingManager extends SharingManager getIntroductionMessages(ContactId contactId) throws DbException; + /** Marks an introduction message as read or unread. */ + void setReadFlag(MessageId m, boolean read) throws DbException; + } diff --git a/briar-api/src/org/briarproject/api/sharing/SharingManager.java b/briar-api/src/org/briarproject/api/sharing/SharingManager.java index 100ab5dc4..a2987ef80 100644 --- a/briar-api/src/org/briarproject/api/sharing/SharingManager.java +++ b/briar-api/src/org/briarproject/api/sharing/SharingManager.java @@ -5,6 +5,7 @@ import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DbException; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; +import org.briarproject.api.sync.MessageId; import java.util.Collection; @@ -45,4 +46,7 @@ public interface SharingManager bodyCache = + new ConcurrentHashMap(); + + @Inject + ConversationManagerImpl(@DatabaseExecutor Executor dbExecutor, + ForumSharingManager forumSharingManager, + IntroductionManager introductionManager, + MessagingManager messagingManager) { + this.dbExecutor = dbExecutor; + this.forumSharingManager = forumSharingManager; + this.introductionManager = introductionManager; + this.messagingManager = messagingManager; + } + + @Override + public ClientId getClientId() { + return CLIENT_ID; + } + + @Override + public ConversationItem addLocalMessage(PrivateMessage m, byte[] body) + throws DbException { + messagingManager.addLocalMessage(m); + bodyCache.put(m.getMessage().getId(), body); + + PrivateMessageHeader h = new PrivateMessageHeader( + m.getMessage().getId(), + m.getMessage().getTimestamp(), m.getContentType(), + true, false, false, false); + ConversationItem item = ConversationMessageItemImpl.from(h); + ((Partial) item).setContent(body); + return item; + } + + @Override + public ContactId getContactId(GroupId g) throws DbException { + return messagingManager.getContactId(g); + } + + @Override + public GroupId getConversationId(ContactId c) throws DbException { + return messagingManager.getConversationId(c); + } + + @Override + public List getMessages(ContactId c) + throws DbException { + return getMessages(c, true); + } + + @Override + public List getMessages(ContactId c, boolean content) + throws DbException { + Collection headers = + messagingManager.getMessageHeaders(c); + Collection introductions = + introductionManager.getIntroductionMessages(c); + Collection invitations = + forumSharingManager.getInvitationMessages(c); + List items = new ArrayList(); + for (PrivateMessageHeader h : headers) { + ConversationItem item = ConversationMessageItemImpl.from(h); + if (content) { + byte[] body = bodyCache.get(h.getId()); + if (body == null) loadMessageContent((Partial) item); + else ((Partial) item).setContent(body); + } + items.add(item); + } + for (IntroductionMessage m : introductions) { + ConversationItem item; + if (m instanceof IntroductionRequest) { + item = ConversationIntroductionRequestItemImpl.from( + (IntroductionRequest) m); + } else { + item = ConversationIntroductionResponseItemImpl.from( + (IntroductionResponse) m); + } + items.add(item); + } + for (ForumInvitationMessage i : invitations) { + ConversationItem item = ConversationForumInvitationItemImpl.from(i); + items.add(item); + } + return items; + } + + @Override + public void loadMessageContent(final Partial m) { + dbExecutor.execute(new Runnable() { + @Override + public void run() { + try { + MessageId id = m.getId(); + long now = System.currentTimeMillis(); + byte[] body = messagingManager.getMessageBody(id); + long duration = System.currentTimeMillis() - now; + if (LOG.isLoggable(INFO)) + LOG.info("Loading message took " + duration + " ms"); + bodyCache.put(id, body); + m.setContent(body); + } catch (NoSuchMessageException e) { + // The item will be removed when we get the event + } catch (DbException e) { + if (LOG.isLoggable(WARNING)) + LOG.log(WARNING, e.toString(), e); + } + } + }); + } + + @Override + public void respondToItem(ContactId c, ConversationItem item, + boolean accept, long timestamp) + throws DbException, FormatException { + if (item instanceof ConversationIntroductionRequestItem) { + SessionId sessionId = ((ConversationIntroductionRequestItem) item) + .getIntroductionRequest().getSessionId(); + if (accept) { + introductionManager + .acceptIntroduction(c, sessionId, + timestamp); + } else { + introductionManager + .declineIntroduction(c, sessionId, + timestamp); + } + } + } + + @Override + public void setReadFlag(ConversationItem item, boolean read) + throws DbException { + MessageId id = item.getId(); + if (item instanceof ConversationMessageItem) { + messagingManager.setReadFlag(id, read); + } else if (item instanceof ConversationIntroductionRequestItem || + item instanceof ConversationIntroductionResponseItem) { + introductionManager.setReadFlag(id, read); + } else if (item instanceof ConversationForumInvitationItem) { + forumSharingManager.setReadFlag(id, read); + } + } +} diff --git a/briar-core/src/org/briarproject/conversation/ConversationMessageItemImpl.java b/briar-core/src/org/briarproject/conversation/ConversationMessageItemImpl.java new file mode 100644 index 000000000..6da77f4c8 --- /dev/null +++ b/briar-core/src/org/briarproject/conversation/ConversationMessageItemImpl.java @@ -0,0 +1,131 @@ +package org.briarproject.conversation; + +import org.briarproject.api.conversation.ConversationItem; +import org.briarproject.api.conversation.ConversationMessageItem; +import org.briarproject.api.messaging.PrivateMessageHeader; + +import java.util.concurrent.locks.ReentrantReadWriteLock; + +class ConversationMessageItemImpl { + + static ConversationItem from(PrivateMessageHeader h) { + return h.isLocal() ? new Outgoing(h) : new Incoming(h); + } + + static class Outgoing extends OutgoingConversationItem + implements ConversationMessageItem { + + private final PrivateMessageHeader header; + private byte[] body; + private ContentListener listener; + private final ReentrantReadWriteLock bodyLock = + new ReentrantReadWriteLock(); + private final ReentrantReadWriteLock listenerLock = + new ReentrantReadWriteLock(); + + public Outgoing(PrivateMessageHeader header) { + super(header.getId(), header.getTimestamp(), header.isSent(), + header.isSeen()); + + this.header = header; + body = null; + listener = null; + } + + @Override + public PrivateMessageHeader getHeader() { + return header; + } + + @Override + public byte[] getBody() { + bodyLock.readLock().lock(); + byte[] ret = body; + bodyLock.readLock().unlock(); + return ret; + } + + @Override + public void setContent(byte[] content) { + bodyLock.writeLock().lock(); + this.body = content; + bodyLock.writeLock().unlock(); + listenerLock.readLock().lock(); + if (listener != null) { + listener.contentReady(); + } + listenerLock.readLock().unlock(); + } + + @Override + public void setContentListener( + ContentListener listener) { + listenerLock.writeLock().lock(); + this.listener = listener; + listenerLock.writeLock().unlock(); + bodyLock.readLock().lock(); + if (body != null) { + listener.contentReady(); + } + bodyLock.readLock().unlock(); + } + } + + static class Incoming extends IncomingConversationItem + implements ConversationMessageItem { + + private final PrivateMessageHeader header; + private byte[] body; + private ContentListener listener; + private final ReentrantReadWriteLock bodyLock = + new ReentrantReadWriteLock(); + private final ReentrantReadWriteLock listenerLock = + new ReentrantReadWriteLock(); + + public Incoming(PrivateMessageHeader header) { + super(header.getId(), header.getTimestamp(), header.isRead()); + + this.header = header; + body = null; + listener = null; + } + + @Override + public PrivateMessageHeader getHeader() { + return header; + } + + @Override + public byte[] getBody() { + bodyLock.readLock().lock(); + byte[] ret = body; + bodyLock.readLock().unlock(); + return ret; + } + + @Override + public void setContent(byte[] content) { + bodyLock.writeLock().lock(); + this.body = content; + bodyLock.writeLock().unlock(); + listenerLock.readLock().lock(); + if (listener != null) { + listener.contentReady(); + } + listenerLock.readLock().unlock(); + } + + @Override + public void setContentListener( + ContentListener listener) { + listenerLock.writeLock().lock(); + this.listener = listener; + listenerLock.writeLock().unlock(); + bodyLock.readLock().lock(); + if (body != null) { + listener.contentReady(); + } + bodyLock.readLock().unlock(); + } + } +} diff --git a/briar-core/src/org/briarproject/conversation/ConversationModule.java b/briar-core/src/org/briarproject/conversation/ConversationModule.java new file mode 100644 index 000000000..1424b4f0a --- /dev/null +++ b/briar-core/src/org/briarproject/conversation/ConversationModule.java @@ -0,0 +1,25 @@ +package org.briarproject.conversation; + +import org.briarproject.api.conversation.ConversationManager; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class ConversationModule { + + public static class EagerSingletons { + @Inject + ConversationManager conversationManager; + } + + @Provides + @Singleton + ConversationManager getConversationManager( + ConversationManagerImpl conversationManager) { + return conversationManager; + } +} diff --git a/briar-core/src/org/briarproject/conversation/IncomingConversationItem.java b/briar-core/src/org/briarproject/conversation/IncomingConversationItem.java new file mode 100644 index 000000000..16f493018 --- /dev/null +++ b/briar-core/src/org/briarproject/conversation/IncomingConversationItem.java @@ -0,0 +1,27 @@ +package org.briarproject.conversation; + +import org.briarproject.api.conversation.ConversationItem.IncomingItem; +import org.briarproject.api.sync.MessageId; + +// This class is not thread-safe +class IncomingConversationItem extends ConversationItemImpl + implements IncomingItem { + + private boolean read; + + public IncomingConversationItem(MessageId id, long time, boolean read) { + super(id, time); + + this.read = read; + } + + @Override + public boolean isRead() { + return read; + } + + @Override + public void setRead(boolean read) { + this.read = read; + } +} diff --git a/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java b/briar-core/src/org/briarproject/conversation/OutgoingConversationItem.java similarity index 51% rename from briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java rename to briar-core/src/org/briarproject/conversation/OutgoingConversationItem.java index c8a6b5459..be802f041 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationNoticeOutItem.java +++ b/briar-core/src/org/briarproject/conversation/OutgoingConversationItem.java @@ -1,28 +1,24 @@ -package org.briarproject.android.contact; +package org.briarproject.conversation; +import org.briarproject.api.conversation.ConversationItem.OutgoingItem; import org.briarproject.api.sync.MessageId; // This class is not thread-safe -public class ConversationNoticeOutItem extends ConversationNoticeItem - implements ConversationItem.OutgoingItem { +class OutgoingConversationItem extends ConversationItemImpl + implements OutgoingItem { private boolean sent, seen; - public ConversationNoticeOutItem(MessageId id, String text, long time, - boolean sent, boolean seen) { - super(id, text, time); + public OutgoingConversationItem(MessageId id, long time, boolean sent, + boolean seen) { + super(id, time); this.sent = sent; this.seen = seen; } @Override - int getType() { - return NOTICE_OUT; - } - - @Override - public boolean isSent() { + public boolean isSent() { return sent; } diff --git a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java index 623a1ed37..d315313f8 100644 --- a/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java +++ b/briar-core/src/org/briarproject/introduction/IntroductionManagerImpl.java @@ -457,6 +457,17 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook return list; } + @Override + public void setReadFlag(MessageId m, boolean read) throws DbException { + try { + BdfDictionary meta = new BdfDictionary(); + meta.put(READ, read); + clientHelper.mergeMessageMetadata(m, meta); + } catch (FormatException e) { + throw new RuntimeException(e); + } + } + private String getNameForIntroducer(ContactId contactId, BdfDictionary state) throws FormatException { diff --git a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java index 9df89ceb6..69be0d4b3 100644 --- a/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java +++ b/briar-core/src/org/briarproject/sharing/SharingManagerImpl.java @@ -492,6 +492,17 @@ abstract class SharingManagerImpl