From 132b27adeef3ab4d305facda7dd035015b00766f Mon Sep 17 00:00:00 2001 From: steckbrief Date: Sat, 5 May 2018 20:28:04 +0200 Subject: introduces new message state model --- .../conversationsplus/entities/Conversation.java | 166 +++++++++------------ .../conversationsplus/entities/Message.java | 151 ++++++++++--------- 2 files changed, 156 insertions(+), 161 deletions(-) (limited to 'src/main/java/de/thedevstack/conversationsplus/entities') diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java index 2edd1135..0cce63f4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java @@ -16,12 +16,14 @@ import org.json.JSONObject; import java.security.interfaces.DSAPublicKey; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; -import java.util.Iterator; import java.util.List; +import java.util.NavigableSet; +import java.util.TreeSet; import de.thedevstack.conversationsplus.dto.LoadAvatarFor; +import de.thedevstack.conversationsplus.enums.MessageDirection; +import de.thedevstack.conversationsplus.enums.MessageStatus; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; @@ -66,7 +68,14 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata private Jid nextCounterpart; - protected final ArrayList messages = new ArrayList<>(); + protected final TreeSet messages = new TreeSet<>(new Comparator() { + @Override + public int compare(Message message, Message otherMessage) { + long thisTimeSent = message.getTimeSent(); + long otherTimeSent = otherMessage.getTimeSent(); + return (thisTimeSent < otherTimeSent ? -1 : (thisTimeSent == otherTimeSent ? 0 : 1)); + } + }); protected Account account = null; private transient SessionImpl otrSession; @@ -100,8 +109,8 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata public Message findUnsentMessageWithUuid(String uuid) { synchronized(this.messages) { for (final Message message : this.messages) { - final int s = message.getStatus(); - if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) { + if ((MessageUtil.isOutgoingMessage(message) + && (MessageStatus.WAITING == message.getMessageStatus() || MessageStatus.TRANSMITTING == message.getMessageStatus())) && message.getUuid().equals(uuid)) { return message; } } @@ -112,7 +121,7 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata public void findWaitingMessages(OnMessageFound onMessageFound) { synchronized (this.messages) { for(Message message : this.messages) { - if (message.getStatus() == Message.STATUS_WAITING) { + if (MessageUtil.isOutgoingMessage(message) && MessageStatus.WAITING == message.getMessageStatus()) { onMessageFound.onMessageFound(message); } } @@ -193,7 +202,9 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata final int size = messages.size(); final int maxsize = Config.PAGE_SIZE * Config.MAX_NUM_PAGES; if (size > maxsize) { - this.messages.subList(0, size - maxsize).clear(); + for (int i = 0; i < size - maxsize; i++) { + this.messages.pollFirst(); + } } } } @@ -201,7 +212,7 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) { synchronized (this.messages) { for (Message message : this.messages) { - if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING) + if ((MessageUtil.isOutgoingMessage(message) && (MessageStatus.WAITING == message.getMessageStatus() || MessageStatus.TRANSMITTING == message.getMessageStatus())) && (message.getEncryption() == encryptionType)) { onMessageFound.onMessageFound(message); } @@ -209,23 +220,25 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata } } - public void findUnsentTextMessages(OnMessageFound onMessageFound) { - synchronized (this.messages) { - for (Message message : this.messages) { - if (message.getType() != Message.TYPE_IMAGE - && message.getStatus() == Message.STATUS_UNSEND) { - onMessageFound.onMessageFound(message); - } - } - } - } + public void findOugoingTextMessagesInTransmission(OnMessageFound onMessageFound) { + synchronized (this.messages) { + for (Message message : this.messages) { + if (message.getType() != Message.TYPE_IMAGE // TODO: Check if we should resend everything + && MessageUtil.isOutgoingMessage(message) + && MessageStatus.TRANSMITTING == message.getMessageStatus()) { + onMessageFound.onMessageFound(message); + } + } + } + } public Message findSentMessageWithUuidOrRemoteId(String id) { synchronized (this.messages) { for (Message message : this.messages) { if (id.equals(message.getUuid()) - || (message.getStatus() >= Message.STATUS_SEND - && id.equals(message.getRemoteMsgId()))) { + || (MessageUtil.isOutgoingMessage(message) + && MessageUtil.isMessageTransmittedOrDisplayedOrReceived(message) + && id.equals(message.getRemoteMsgId()))) { return message; } } @@ -233,24 +246,6 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata return null; } - public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) { - synchronized (this.messages) { - for(int i = this.messages.size() - 1; i >= 0; --i) { - Message message = messages.get(i); - if (counterpart.equals(message.getCounterpart()) - && ((message.getStatus() == Message.STATUS_RECEIVED) == received) - && (carbon == message.isCarbon() || received) ) { - if (id.equals(message.getRemoteMsgId())) { - return message; - } else { - return null; - } - } - } - } - return null; - } - public Message findSentMessageWithUuid(String id) { synchronized (this.messages) { for (Message message : this.messages) { @@ -350,33 +345,42 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata } public boolean isRead() { - return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); + return (this.messages.size() == 0) || this.messages.last().isRead(); } + /** + * Marks messages as read, starting with the latest message. + * Stops iterating at the first messages that is already read + * @return List of the messages which are newly marked as read + */ public List markRead() { final List unread = new ArrayList<>(); synchronized (this.messages) { - for(Message message : this.messages) { + for (Message message : this.messages.descendingSet()) { if (!message.isRead()) { message.markRead(); unread.add(message); - } + } else { + break; + } } } return unread; } public Message getLatestMarkableMessage() { - for (int i = this.messages.size() - 1; i >= 0; --i) { - if (this.messages.get(i).getStatus() <= Message.STATUS_RECEIVED - && this.messages.get(i).markable) { - if (this.messages.get(i).isRead()) { + NavigableSet descendingSet = this.messages.descendingSet(); + for (Message message : descendingSet) { + if (MessageUtil.isIncomingMessage(message) + && message.markable) { + if (message.isRead()) { return null; } else { - return this.messages.get(i); + return message; } - } + } } + return null; } @@ -386,7 +390,7 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata message.setTime(getCreated()); return message; } else { - Message message = this.messages.get(this.messages.size() - 1); + Message message = this.messages.last(); message.setConversation(this); return message; } @@ -621,9 +625,8 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata private int getMostRecentlyUsedOutgoingEncryption() { synchronized (this.messages) { - for(int i = this.messages.size() -1; i >= 0; --i) { - final Message m = this.messages.get(i); - if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) { + for (Message m : this.messages.descendingSet()) { + if (!m.isCarbon() && MessageUtil.isOutgoingMessage(m)) { final int e = m.getEncryption(); if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { return Message.ENCRYPTION_PGP; @@ -638,10 +641,9 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata private int getMostRecentlyUsedIncomingEncryption() { synchronized (this.messages) { - for(int i = this.messages.size() -1; i >= 0; --i) { - final Message m = this.messages.get(i); - if (m.getStatus() == Message.STATUS_RECEIVED) { - final int e = m.getEncryption(); + for (Message message : this.messages.descendingSet()) { + if (MessageUtil.isIncomingMessage(message)) { + final int e = message.getEncryption(); if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { return Message.ENCRYPTION_PGP; } else { @@ -734,8 +736,8 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata public boolean hasDuplicateMessage(Message message) { synchronized (this.messages) { - for (int i = this.messages.size() - 1; i >= 0; --i) { - if (this.messages.get(i).equals(message)) { + for (Message m : this.messages.descendingSet()) { + if (m.equals(message)) { return true; } } @@ -745,9 +747,8 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata public Message findSentMessageWithBody(String body) { synchronized (this.messages) { - for (int i = this.messages.size() - 1; i >= 0; --i) { - Message message = this.messages.get(i); - if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) { + for (Message message : this.messages.descendingSet()) { + if (MessageUtil.isMessageSent(message)) { String otherBody = message.getBody(); if (otherBody != null && otherBody.equals(body)) { return message; @@ -760,9 +761,8 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata public long getLastMessageTransmitted() { synchronized (this.messages) { - for(int i = this.messages.size() - 1; i >= 0; --i) { - Message message = this.messages.get(i); - if (message.getStatus() == Message.STATUS_RECEIVED || message.isCarbon()) { + for (Message message : this.messages.descendingSet()) { + if (MessageUtil.isIncomingMessage(message) || message.isCarbon()) { return message.getTimeSent(); } } @@ -880,48 +880,20 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata } } - public void prepend(Message message) { - message.setConversation(this); - synchronized (this.messages) { - this.messages.add(0,message); - } - } - public void addAll(int index, List messages) { synchronized (this.messages) { - this.messages.addAll(index, messages); + this.messages.addAll(messages); } account.getPgpDecryptionService().addAll(messages); } - public void sort() { - synchronized (this.messages) { - Collections.sort(this.messages, new Comparator() { - @Override - public int compare(Message left, Message right) { - if (left.getTimeSent() < right.getTimeSent()) { - return -1; - } else if (left.getTimeSent() > right.getTimeSent()) { - return 1; - } else { - return 0; - } - } - }); - for(Message message : this.messages) { - message.untie(); - } - } - } - public int unreadCount() { if (getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) { return 0; } synchronized (this.messages) { int count = 0; - for(int i = this.messages.size() - 1; i >= 0; --i) { - Message message = this.messages.get(i); + for (Message message : this.messages.descendingSet()) { if (message.isRead()) { return count; } @@ -933,6 +905,14 @@ public class Conversation extends AbstractEntity implements Blockable, LoadAvata } } + public Message getMessageBefore(Message message) { + return this.messages.lower(message); + } + + public Message getMessageAfter(Message message) { + return this.messages.higher(message); + } + public class Smp { public static final int STATUS_NONE = 0; public static final int STATUS_CONTACT_REQUESTED = 1; diff --git a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java index 36ab036e..86adb4af 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Message.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Message.java @@ -9,8 +9,11 @@ import java.net.URL; import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlSession; import de.thedevstack.conversationsplus.dto.LoadAvatarFor; import de.thedevstack.conversationsplus.enums.FileStatus; +import de.thedevstack.conversationsplus.enums.MessageConfirmation; +import de.thedevstack.conversationsplus.enums.MessageDirection; +import de.thedevstack.conversationsplus.enums.MessageStatus; import de.thedevstack.conversationsplus.utils.FileUtils; -import de.thedevstack.conversationsplus.utils.MessageUtil; +import de.thedevstack.conversationsplus.utils.MessageParserUtil; import de.thedevstack.conversationsplus.utils.MimeUtils; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; @@ -19,16 +22,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { public static final String TABLENAME = "messages"; - public static final int STATUS_RECEIVED = 0; - public static final int STATUS_UNSEND = 1; - public static final int STATUS_SEND = 2; - public static final int STATUS_SEND_FAILED = 3; - public static final int STATUS_WAITING = 5; - public static final int STATUS_OFFERED = 6; - public static final int STATUS_SEND_RECEIVED = 7; - public static final int STATUS_SEND_DISPLAYED = 8; - public static final int STATUS_SEND_CANCELED = 9; // FIXME This bullshit is needed until status is handled more properly - public static final int ENCRYPTION_NONE = 0; public static final int ENCRYPTION_PGP = 1; public static final int ENCRYPTION_OTR = 2; @@ -69,11 +62,13 @@ public class Message extends AbstractEntity implements LoadAvatarFor { protected String encryptedBody; protected long timeSent; protected int encryption; - protected int status; protected int type; + /** + * Indicates that a message was received via + * Message Carbons (XEP-0280) + */ protected boolean carbon = false; protected boolean oob = false; - protected String edited = null; protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; @@ -87,16 +82,29 @@ public class Message extends AbstractEntity implements LoadAvatarFor { private boolean httpUploaded; private FileParams fileParams; + /** + * The direction of the message, + * incoming is a msg received from a jid, + * outgoing is a msg sent to a jid + */ + private MessageDirection direction; + private MessageStatus messageStatus; + /** + * Indicates that a message was received via + * Message Archive Management (XEP-0313) + */ + private boolean mamReceived; - private Message() { + /** + * Whether Delivery Receipts (XEP-0184) and/ or Chat Markers (XEP-0333) + * need to be requested and processed (outgoing msgs) or sent (incoming msgs) + */ + private MessageConfirmation confirmation; + public Message() { } public Message(Conversation conversation, String body, int encryption) { - this(conversation, body, encryption, STATUS_UNSEND); - } - - public Message(Conversation conversation, String body, int encryption, int status) { this(java.util.UUID.randomUUID().toString(), conversation.getUuid(), conversation.getJid() == null ? null : conversation.getJid().toBareJid(), @@ -104,7 +112,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { body, System.currentTimeMillis(), encryption, - status, TYPE_TEXT, false, null, @@ -112,17 +119,16 @@ public class Message extends AbstractEntity implements LoadAvatarFor { null, null, true, - null, false); this.conversation = conversation; } private Message(final String uuid, final String conversationUUid, final Jid counterpart, final Jid trueCounterpart, final String body, final long timeSent, - final int encryption, final int status, final int type, final boolean carbon, + final int encryption, final int type, final boolean carbon, final String remoteMsgId, final String relativeFilePath, final String serverMsgId, final String fingerprint, final boolean read, - final String edited, final boolean oob) { + final boolean oob) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -130,7 +136,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { this.body = body; this.timeSent = timeSent; this.encryption = encryption; - this.status = status; this.type = type; this.carbon = carbon; this.remoteMsgId = remoteMsgId; @@ -138,7 +143,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { this.serverMsgId = serverMsgId; this.axolotlFingerprint = fingerprint; this.read = read; - this.edited = edited; this.oob = oob; } @@ -172,7 +176,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { cursor.getString(cursor.getColumnIndex(BODY)), cursor.getLong(cursor.getColumnIndex(TIME_SENT)), cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), - cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), cursor.getInt(cursor.getColumnIndex(CARBON)) > 0, cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), @@ -180,18 +183,9 @@ public class Message extends AbstractEntity implements LoadAvatarFor { cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), cursor.getString(cursor.getColumnIndex(FINGERPRINT)), cursor.getInt(cursor.getColumnIndex(READ)) > 0, - cursor.getString(cursor.getColumnIndex(EDITED)), cursor.getInt(cursor.getColumnIndex(OOB)) > 0); } - public static Message createStatusMessage(Conversation conversation, String body) { - final Message message = new Message(); - message.setType(Message.TYPE_STATUS); - message.setConversation(conversation); - message.setBody(body); - return message; - } - @Override public ContentValues getContentValues() { ContentValues values = new ContentValues(); @@ -210,7 +204,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { values.put(BODY, body); values.put(TIME_SENT, timeSent); values.put(ENCRYPTION, encryption); - values.put(STATUS, status); values.put(TYPE, type); values.put(CARBON, carbon ? 1 : 0); values.put(REMOTE_MSG_ID, remoteMsgId); @@ -218,7 +211,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { values.put(SERVER_MSG_ID, serverMsgId); values.put(FINGERPRINT, axolotlFingerprint); values.put(READ,read ? 1 : 0); - values.put(EDITED, edited); values.put(OOB, oob ? 1 : 0); return values; } @@ -276,14 +268,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { this.encryption = encryption; } - public int getStatus() { - return status; - } - - public void setStatus(int status) { - this.status = status; - } - public String getRelativeFilePath() { return this.relativeFilePath; } @@ -351,10 +335,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { this.carbon = carbon; } - public boolean edited() { - return this.edited != null; - } - public void setTrueCounterpart(Jid trueCounterpart) { this.trueCounterpart = trueCounterpart; } @@ -367,6 +347,37 @@ public class Message extends AbstractEntity implements LoadAvatarFor { this.transferable = transferable; } + + public boolean isMamReceived() { + return mamReceived; + } + + public void setMamReceived(boolean mamReceived) { + this.mamReceived = mamReceived; + } + + public MessageConfirmation getConfirmation() { + return confirmation; + } + + public void setConfirmation(MessageConfirmation confirmation) { + this.confirmation = confirmation; + } + + @Override + public int hashCode() { + if (null != this.getServerMsgId()) { + return this.getServerMsgId().hashCode(); + } else if (null != this.getRemoteMsgId()) { + return this.getRemoteMsgId().hashCode(); + } else if (null != this.getUuid()) { + return this.getUuid().hashCode(); + } else if (null != this.getBody()){ + return this.getBody().hashCode(); + } + return super.hashCode(); + } + public boolean equals(Message message) { if (this.getServerMsgId() != null && message.getServerMsgId() != null) { return this.getServerMsgId().equals(message.getServerMsgId()); @@ -400,12 +411,7 @@ public class Message extends AbstractEntity implements LoadAvatarFor { public Message next() { synchronized (this.conversation.messages) { if (this.mNextMessage == null) { - int index = this.conversation.messages.indexOf(this); - if (index < 0 || index >= this.conversation.messages.size() - 1) { - this.mNextMessage = null; - } else { - this.mNextMessage = this.conversation.messages.get(index + 1); - } + this.mNextMessage = this.conversation.messages.higher(this); } return this.mNextMessage; } @@ -414,12 +420,7 @@ public class Message extends AbstractEntity implements LoadAvatarFor { public Message prev() { synchronized (this.conversation.messages) { if (this.mPreviousMessage == null) { - int index = this.conversation.messages.indexOf(this); - if (index <= 0 || index > this.conversation.messages.size()) { - this.mPreviousMessage = null; - } else { - this.mPreviousMessage = this.conversation.messages.get(index - 1); - } + this.mPreviousMessage = this.conversation.messages.lower(this); } return this.mPreviousMessage; } @@ -439,7 +440,7 @@ public class Message extends AbstractEntity implements LoadAvatarFor { public boolean trusted() { Contact contact = this.getContact(); - return (status > STATUS_RECEIVED || (contact != null && contact.trusted())); + return contact != null && contact.trusted(); } public boolean fixCounterpart() { @@ -466,10 +467,6 @@ public class Message extends AbstractEntity implements LoadAvatarFor { this.uuid = uuid; } - public String getEditedId() { - return edited; - } - public void setOob(boolean isOob) { this.oob = isOob; } @@ -505,17 +502,19 @@ public class Message extends AbstractEntity implements LoadAvatarFor { mTreatAsDownloadAble = Decision.NEVER; } + @Deprecated public void setTreatAsDownloadable(Decision downloadable) { this.mTreatAsDownloadAble = downloadable; } + @Deprecated public Decision treatAsDownloadable() { // only test this ones, body will not change if (mTreatAsDownloadAble != Decision.NOT_DECIDED) { return mTreatAsDownloadAble; } - MessageUtil.extractFileParamsFromBody(this); + MessageParserUtil.extractFileParamsFromBody(this); return this.mTreatAsDownloadAble; } @@ -561,7 +560,7 @@ public class Message extends AbstractEntity implements LoadAvatarFor { private int getPreviousEncryption() { for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){ - if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + if( iterator.isCarbon() || MessageDirection.IN == iterator.getDirection()) { continue; } return iterator.getEncryption(); @@ -571,7 +570,7 @@ public class Message extends AbstractEntity implements LoadAvatarFor { private int getNextEncryption() { for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){ - if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + if( iterator.isCarbon() || MessageDirection.IN == iterator.getDirection()) { continue; } return iterator.getEncryption(); @@ -606,6 +605,22 @@ public class Message extends AbstractEntity implements LoadAvatarFor { this.fileParams = params; } + public MessageDirection getDirection() { + return direction; + } + + public void setDirection(MessageDirection direction) { + this.direction = direction; + } + + public MessageStatus getMessageStatus() { + return messageStatus; + } + + public void setMessageStatus(MessageStatus messageStatus) { + this.messageStatus = messageStatus; + } + private static int getCleanedEncryption(int encryption) { if (encryption == ENCRYPTION_DECRYPTED || encryption == ENCRYPTION_DECRYPTION_FAILED) { return ENCRYPTION_PGP; -- cgit v1.2.3