aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlMessageParser.java37
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrMessageParser.java67
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrUtil.java39
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java166
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/entities/Message.java151
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/enums/MessageConfirmation.java23
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/enums/MessageDirection.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/enums/MessageStatus.java54
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java22
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java735
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java12
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java25
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java143
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/MessageStatusMigration.java56
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java11
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java48
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java76
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/avatar/AvatarCache.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java9
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java10
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java46
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java7
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java274
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageViewHolder.java97
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java36
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnClickListener.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnLongClickListener.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java9
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java35
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/GeoHelper.java4
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/MessageParserUtil.java164
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java255
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java18
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/utils/messaging/MessageReceiptUtil.java41
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xml/Element.java29
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xml/XmlHelper.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/Forwarded.java26
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/PacketReceived.java2
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java8
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkers.java28
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersHandler.java11
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersPacketHandler.java24
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersXep.java49
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java1
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java24
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java19
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java3
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/muc/MucPacketParser.java62
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/receipts/MessageDeliveryReceipts.java24
-rw-r--r--src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java2
-rw-r--r--src/main/res/values-de/strings.xml3
-rw-r--r--src/main/res/values/strings.xml7
60 files changed, 1905 insertions, 1147 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlMessageParser.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlMessageParser.java
new file mode 100644
index 00000000..d77db0d4
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlMessageParser.java
@@ -0,0 +1,37 @@
+package de.thedevstack.conversationsplus.crypto.axolotl;
+
+import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.Config;
+import de.thedevstack.conversationsplus.entities.Conversation;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+
+/**
+ */
+public class AxolotlMessageParser {
+ public static Message parseAxolotlChat(Element axolotlMessage, Jid counterpart, Jid from, Conversation conversation) {
+ Jid origin;
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ origin = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
+ if (origin == null) {
+ Logging.d(Config.LOGTAG,"axolotl message in non anonymous conference received");
+ return null;
+ }
+ } else {
+ origin = from;
+ }
+
+ Message finishedMessage = null;
+ AxolotlService service = conversation.getAccount().getAxolotlService();
+ XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid());
+ XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage);
+ if(plaintextMessage != null) {
+ finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL);
+ finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
+ Logging.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
+ }
+
+ return finishedMessage;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java
index 9cf540a3..3569a7a5 100644
--- a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java
+++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java
@@ -39,6 +39,7 @@ import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.Contact;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.parser.IqParser;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
@@ -944,7 +945,7 @@ public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, Axolo
public void run() {
XmppAxolotlMessage axolotlMessage = encrypt(message);
if (axolotlMessage == null) {
- MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.FAILED);
//mXmppConnectionService.updateConversationUi();
} else {
Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrMessageParser.java b/src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrMessageParser.java
new file mode 100644
index 00000000..8791bff3
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrMessageParser.java
@@ -0,0 +1,67 @@
+package de.thedevstack.conversationsplus.crypto.otr;
+
+import net.java.otr4j.session.Session;
+import net.java.otr4j.session.SessionStatus;
+
+import de.thedevstack.conversationsplus.crypto.OtrService;
+import de.thedevstack.conversationsplus.entities.Conversation;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.utils.CryptoHelper;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+
+/**
+ */
+public class OtrMessageParser {
+ public static Message parseOtrChat(String body, Jid from, String id, Conversation conversation) {
+ String presence;
+ if (from.isBareJid()) {
+ presence = "";
+ } else {
+ presence = from.getResourcepart();
+ }
+ if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) {
+ conversation.endOtrIfNeeded();
+ }
+ if (!conversation.hasValidOtrSession()) {
+ conversation.startOtrSession(presence,false);
+ } else {
+ String foreignPresence = conversation.getOtrSession().getSessionID().getUserID();
+ if (!foreignPresence.equals(presence)) {
+ conversation.endOtrIfNeeded();
+ conversation.startOtrSession(presence, false);
+ }
+ }
+ try {
+ conversation.setLastReceivedOtrMessageId(id);
+ Session otrSession = conversation.getOtrSession();
+ body = otrSession.transformReceiving(body);
+ SessionStatus status = otrSession.getSessionStatus();
+ if (body == null && status == SessionStatus.ENCRYPTED) {
+ XmppConnectionServiceAccessor.xmppConnectionService.onOtrSessionEstablished(conversation);
+ return null;
+ } else if (body == null && status == SessionStatus.FINISHED) {
+ conversation.resetOtrSession();
+ UiUpdateHelper.updateConversationUi();
+ return null;
+ } else if (body == null || (body.isEmpty())) {
+ return null;
+ }
+ if (body.startsWith(CryptoHelper.FILETRANSFER)) {
+ String key = body.substring(CryptoHelper.FILETRANSFER.length());
+ conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
+ return null;
+ }
+ final OtrService otrService = conversation.getAccount().getOtrService();
+ Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR);
+ finishedMessage.setFingerprint(otrService.getFingerprint(otrSession.getRemotePublicKey()));
+ conversation.setLastReceivedOtrMessageId(null);
+
+ return finishedMessage;
+ } catch (Exception e) {
+ conversation.resetOtrSession();
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrUtil.java b/src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrUtil.java
new file mode 100644
index 00000000..97075455
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/crypto/otr/OtrUtil.java
@@ -0,0 +1,39 @@
+package de.thedevstack.conversationsplus.crypto.otr;
+
+import net.java.otr4j.session.SessionID;
+import net.java.otr4j.session.SessionImpl;
+
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Conversation;
+import de.thedevstack.conversationsplus.xmpp.Forwarded;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
+
+/**
+ */
+public class OtrUtil {
+ public static boolean isOtrSessionActive(Conversation conversation) {
+ return null != conversation && null != conversation.getOtrSession();
+ }
+
+ public static boolean isCounterpartOfActiveOtrSession(Conversation conversation, Jid counterpart) {
+ SessionImpl otrSession = (null != conversation) ? conversation.getOtrSession() : null;
+ SessionID sessionID = (null != otrSession) ? otrSession.getSessionID() : null;
+ String userID = (null != sessionID) ? sessionID.getUserID() : null;
+
+ return null != userID && null != counterpart && userID.equals(counterpart.getResourcepart());
+ }
+
+ public static boolean isValidOtrMessagePacket(MessagePacket messagePacket, Account account) {
+ Jid to = messagePacket.getTo();
+ boolean isProperlyAddressed = (to != null) && (!to.isBareJid() || account.countPresences() <= 1);
+ boolean isNotTypeGroupChat = messagePacket.getType() != MessagePacket.TYPE_GROUPCHAT;
+ boolean isNotForwarded = !messagePacket.hasChildRecursive(Forwarded.FORWARDED);
+
+ return isProperlyAddressed && isNotTypeGroupChat && isNotForwarded;
+ }
+
+ public static boolean isOtrBody(String body) {
+ return body != null && body.startsWith("?OTR");
+ }
+}
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<Message> messages = new ArrayList<>();
+ protected final TreeSet<Message> messages = new TreeSet<>(new Comparator<Message>() {
+ @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<Message> markRead() {
final List<Message> 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<Message> 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<Message> 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<Message>() {
- @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;
diff --git a/src/main/java/de/thedevstack/conversationsplus/enums/MessageConfirmation.java b/src/main/java/de/thedevstack/conversationsplus/enums/MessageConfirmation.java
new file mode 100644
index 00000000..bd4a9ccb
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/enums/MessageConfirmation.java
@@ -0,0 +1,23 @@
+package de.thedevstack.conversationsplus.enums;
+
+/**
+ * This enum represents the different combinations
+ * of confirmations which need to be requested, processed
+ * or sent.
+ */
+public enum MessageConfirmation {
+ NONE,
+ /**
+ * Message Delivery Receipts (XEP-0184)
+ */
+ DELIVERY_RECEIPT,
+ /**
+ * Chat Markers (XEP-0333)
+ */
+ CHAT_MARKERS,
+ /**
+ * Message Delivery Receipts (XEP-0184) and
+ * Chat Markers (XEP-0333)
+ */
+ DELIVERY_RECEIPT_CHAT_MARKERS;
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/enums/MessageDirection.java b/src/main/java/de/thedevstack/conversationsplus/enums/MessageDirection.java
new file mode 100644
index 00000000..59746194
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/enums/MessageDirection.java
@@ -0,0 +1,8 @@
+package de.thedevstack.conversationsplus.enums;
+
+/**
+ */
+public enum MessageDirection {
+ OUT,
+ IN;
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/enums/MessageStatus.java b/src/main/java/de/thedevstack/conversationsplus/enums/MessageStatus.java
new file mode 100644
index 00000000..8a31a325
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/enums/MessageStatus.java
@@ -0,0 +1,54 @@
+package de.thedevstack.conversationsplus.enums;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ */
+public enum MessageStatus {
+ WAITING,
+ TRANSMITTING,
+ TRANSMITTED,
+ FAILED,
+ CANCELED,
+ DISPLAYED,
+ RECEIVED;
+
+ static {
+ /*
+ * Initialization of valid next states in this block,
+ * because the enum values need to be known at initialization.
+ * If trying to initialize in Constructor, then only forward transitions are possible.,
+ * no way to handle jumping back.
+ */
+ WAITING.initValidNextStates(TRANSMITTING);
+ TRANSMITTING.initValidNextStates(FAILED, TRANSMITTED, CANCELED);
+ CANCELED.initValidNextStates(WAITING);
+ FAILED.initValidNextStates(WAITING);
+
+ // Restrict some transitions to be only valid for outgoing messages
+ TRANSMITTED.initValidNextStatesForDirection(MessageDirection.OUT, RECEIVED, DISPLAYED);
+ RECEIVED.initValidNextStatesForDirection(MessageDirection.OUT, DISPLAYED);
+ }
+
+ private List<MessageStatus> validNextStates;
+ private HashMap<MessageDirection, List<MessageStatus>> directionRestricted = new HashMap<>();
+
+ private void initValidNextStates(MessageStatus... validNextStates) {
+ this.validNextStates = Arrays.asList(validNextStates);
+ }
+
+ private void initValidNextStatesForDirection(MessageDirection direction, MessageStatus... newStatus) {
+ this.directionRestricted.put(direction, Arrays.asList(newStatus));
+ }
+
+ public boolean isTransitionToStateAllowed(MessageStatus newStatus, MessageDirection direction) {
+ boolean hasNoValidNextStates = this.validNextStates.isEmpty() && (!this.directionRestricted.containsKey(direction) || this.directionRestricted.get(direction).isEmpty());
+ boolean isValidNextState = this.validNextStates.contains(newStatus);
+ boolean isValidNextStateForDirection = (this.directionRestricted.containsKey(direction) && this.directionRestricted.get(direction).contains(newStatus));
+ return hasNoValidNextStates // no valid next states -> every new state is allowed
+ || isValidNextState // if newStatus is conditionless
+ || isValidNextStateForDirection; // if the newStatus is valid for the direction
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java
index 2e580f09..13494baf 100644
--- a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java
+++ b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java
@@ -4,7 +4,6 @@ import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@@ -25,6 +24,10 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
public class MessageGenerator extends AbstractGenerator {
+ @Deprecated
+ /**
+ * Moved to messaging.MessageGenerator
+ */
private MessagePacket preparePacket(Message message) {
Conversation conversation = message.getConversation();
Account account = conversation.getAccount();
@@ -48,9 +51,7 @@ public class MessageGenerator extends AbstractGenerator {
}
packet.setFrom(account.getJid());
packet.setId(message.getUuid());
- if (message.edited()) {
- packet.addChild("replace","urn:xmpp:message-correct:0").setAttribute("id",message.getEditedId());
- }
+
return packet;
}
@@ -112,7 +113,7 @@ public class MessageGenerator extends AbstractGenerator {
FileParams fileParams = message.getFileParams();
content = message.getBody();
if (message.isHttpUploaded()) {
- packet.addChild(new HttpUploadHint());
+ packet.addChild(HttpUploadHint.HTTP_UPLOAD_HINT.getXmlElement());
}
packet.addChild("x","jabber:x:oob").addChild("url").setContent(content);
if (fileParams.getWidth() > 0 && fileParams.getHeight() > 0) {
@@ -192,17 +193,6 @@ public class MessageGenerator extends AbstractGenerator {
return packet;
}
- public MessagePacket received(Account account, MessagePacket originalMessage, ArrayList<String> namespaces, int type) {
- MessagePacket receivedPacket = new MessagePacket();
- receivedPacket.setType(type);
- receivedPacket.setTo(originalMessage.getFrom());
- receivedPacket.setFrom(account.getJid());
- for(String namespace : namespaces) {
- receivedPacket.addChild("received", namespace).setAttribute("id", originalMessage.getId());
- }
- return receivedPacket;
- }
-
public MessagePacket generateOtrError(Jid to, String id, String errorText) {
MessagePacket packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_ERROR);
diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java
index 889adbac..a849d26b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java
+++ b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java
@@ -3,35 +3,37 @@ package de.thedevstack.conversationsplus.parser;
import android.util.Log;
import android.util.Pair;
-import de.thedevstack.conversationsplus.entities.FileParams;
-import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlMessageParser;
+import de.thedevstack.conversationsplus.crypto.otr.OtrMessageParser;
+import de.thedevstack.conversationsplus.crypto.otr.OtrUtil;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.persistance.DatabaseBackend;
import de.thedevstack.conversationsplus.services.avatar.AvatarCache;
import de.thedevstack.conversationsplus.services.filetransfer.http.download.AutomaticFileDownload;
import de.thedevstack.conversationsplus.services.filetransfer.http.download.HttpRetrieveHead;
+import de.thedevstack.conversationsplus.utils.ConversationUtil;
+import de.thedevstack.conversationsplus.utils.MessageParserUtil;
import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
-import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacket;
import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketParser;
import de.thedevstack.conversationsplus.xmpp.carbons.Carbons;
+import de.thedevstack.conversationsplus.xmpp.chatmarkers.ChatMarkers;
import de.thedevstack.conversationsplus.xmpp.httpuploadim.HttpUploadHint;
import de.thedevstack.conversationsplus.xmpp.mam.Mam;
+import de.thedevstack.conversationsplus.xmpp.muc.MucPacketParser;
import de.thedevstack.conversationsplus.xmpp.openpgp.OpenPgpXep;
+import de.thedevstack.conversationsplus.xmpp.receipts.MessageDeliveryReceipts;
import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketReceiver;
-import de.tzur.conversations.Settings;
-import net.java.otr4j.session.Session;
-import net.java.otr4j.session.SessionStatus;
-
-import java.util.ArrayList;
import java.util.Set;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
import de.thedevstack.conversationsplus.utils.AvatarUtil;
import de.thedevstack.conversationsplus.Config;
-import de.thedevstack.conversationsplus.crypto.OtrService;
import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService;
import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlServiceImpl;
import de.thedevstack.conversationsplus.crypto.axolotl.XmppAxolotlMessage;
@@ -44,7 +46,6 @@ import de.thedevstack.conversationsplus.entities.MucOptions;
import de.thedevstack.conversationsplus.services.avatar.AvatarService;
import de.thedevstack.conversationsplus.services.mam.MessageArchiveService;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
-import de.thedevstack.conversationsplus.utils.CryptoHelper;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.OnMessagePacketReceived;
import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState;
@@ -72,117 +73,13 @@ public class MessageParser extends AbstractParser implements
}
return false;
} else {
+ updateLastseen(packet, account, true); // Todo: Should the timestamp be extracted here?
return conversation.setIncomingChatState(state);
}
}
return false;
}
- private Message parseOtrChat(String body, Jid from, String id, Conversation conversation) {
- String presence;
- if (from.isBareJid()) {
- presence = "";
- } else {
- presence = from.getResourcepart();
- }
- if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) {
- conversation.endOtrIfNeeded();
- }
- if (!conversation.hasValidOtrSession()) {
- conversation.startOtrSession(presence,false);
- } else {
- String foreignPresence = conversation.getOtrSession().getSessionID().getUserID();
- if (!foreignPresence.equals(presence)) {
- conversation.endOtrIfNeeded();
- conversation.startOtrSession(presence, false);
- }
- }
- try {
- conversation.setLastReceivedOtrMessageId(id);
- Session otrSession = conversation.getOtrSession();
- body = otrSession.transformReceiving(body);
- SessionStatus status = otrSession.getSessionStatus();
- if (body == null && status == SessionStatus.ENCRYPTED) {
- mXmppConnectionService.onOtrSessionEstablished(conversation);
- return null;
- } else if (body == null && status == SessionStatus.FINISHED) {
- conversation.resetOtrSession();
- UiUpdateHelper.updateConversationUi();
- return null;
- } else if (body == null || (body.isEmpty())) {
- return null;
- }
- if (body.startsWith(CryptoHelper.FILETRANSFER)) {
- String key = body.substring(CryptoHelper.FILETRANSFER.length());
- conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
- return null;
- }
- final OtrService otrService = conversation.getAccount().getOtrService();
- Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR, Message.STATUS_RECEIVED);
- finishedMessage.setFingerprint(otrService.getFingerprint(otrSession.getRemotePublicKey()));
- conversation.setLastReceivedOtrMessageId(null);
-
- return finishedMessage;
- } catch (Exception e) {
- conversation.resetOtrSession();
- return null;
- }
- }
-
- private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status) {
- Message finishedMessage = null;
- AxolotlService service = conversation.getAccount().getAxolotlService();
- XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid());
- XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage);
- if(plaintextMessage != null) {
- finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
- finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
- Logging.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
- }
-
- return finishedMessage;
- }
-
- private class Invite {
- Jid jid;
- String password;
- Invite(Jid jid, String password) {
- this.jid = jid;
- this.password = password;
- }
-
- public boolean execute(Account account) {
- if (jid != null) {
- Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true);
- if (!conversation.getMucOptions().online()) {
- conversation.getMucOptions().setPassword(password);
- DatabaseBackend.getInstance().updateConversation(conversation);
- mXmppConnectionService.joinMuc(conversation);
- UiUpdateHelper.updateConversationUi();
- }
- return true;
- }
- return false;
- }
- }
-
- private Invite extractInvite(Element message) {
- Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
- if (x != null) {
- Element invite = x.findChild("invite");
- if (invite != null) {
- Element pw = x.findChild("password");
- return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent(): null);
- }
- } else {
- x = message.findChild("x","jabber:x:conference");
- if (x != null) {
- return new Invite(x.getAttributeAsJid("jid"),x.getAttribute("password"));
- }
- }
- return null;
- }
-
private static String extractStanzaId(Element packet, Jid by) {
for(Element child : packet.getChildren()) {
if (child.getName().equals("stanza-id")
@@ -251,13 +148,15 @@ public class MessageParser extends AbstractParser implements
} else if (error != null) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + error);
}
- Message message = mXmppConnectionService.markMessage(account,
+ Message message = XmppConnectionServiceAccessor.xmppConnectionService.getMessage(account,
from.toBareJid(),
- packet.getId(),
- Message.STATUS_SEND_FAILED);
- if (message != null && message.getEncryption() == Message.ENCRYPTION_OTR) {
- message.getConversation().endOtrIfNeeded();
- }
+ packet.getId());
+ if (null != message) {
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.FAILED);
+ if (Message.ENCRYPTION_OTR == message.getEncryption()) {
+ message.getConversation().endOtrIfNeeded();
+ }
+ }
}
return true;
}
@@ -269,326 +168,338 @@ public class MessageParser extends AbstractParser implements
if (handleErrorMessage(account, original)) {
return;
}
- final MessagePacket packet;
+ MessagePacket packet = null;
Long timestamp = null;
- final boolean isForwarded;
boolean isCarbon = false;
+ boolean isMAMCatchup = false;
String serverMsgId = null;
+ MessageDirection direction = null;
+
final Element fin = original.findChild("fin", Mam.NAMESPACE);
+ MessageArchiveService messageArchiveService = XmppConnectionServiceAccessor.xmppConnectionService.getMessageArchiveService();
if (fin != null) {
- mXmppConnectionService.getMessageArchiveService().processFin(fin,original.getFrom());
+ // Todo: Check for query.getWith() -> MAM Catchup -> Handle??
+ messageArchiveService.processFin(fin,original.getFrom());
return;
}
final Element result = original.findChild("result", Mam.NAMESPACE);
- final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid"));
- if (query != null && query.validFrom(original.getFrom())) {
- Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", Mam.NAMESPACE);
- if (f == null) {
- return;
- }
- timestamp = f.second;
- packet = f.first;
- isForwarded = true;
- serverMsgId = result.getAttribute("id");
- query.incrementMessageCount();
- } else if (query != null) {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
- return;
- } else if (original.fromServer(account)) {
- Pair<MessagePacket, Long> f;
- f = original.getForwardedMessagePacket("received", Carbons.NAMESPACE);
- f = f == null ? original.getForwardedMessagePacket("sent", Carbons.NAMESPACE) : f;
- packet = f != null ? f.first : original;
- if (handleErrorMessage(account, packet)) {
- return;
- }
- timestamp = f != null ? f.second : null;
- isCarbon = f != null;
- isForwarded = isCarbon;
- } else {
- packet = original;
- isForwarded = false;
- }
+ boolean isMAM = result != null;
+
+ if (isMAM) {
+ final MessageArchiveService.Query query = messageArchiveService.findQuery(result.getAttribute("queryid"));
+ if (null != query && query.validFrom(original.getFrom())) {
+ Pair<MessagePacket, Long> forwardedMessagePacket = original.getForwardedMessagePacket("result", Mam.NAMESPACE);
+ if (forwardedMessagePacket == null) {
+ return;
+ }
+ isMAMCatchup = null == query.getWith();
+ timestamp = forwardedMessagePacket.second;
+ packet = forwardedMessagePacket.first;
+ serverMsgId = result.getAttribute("id");
+ query.incrementMessageCount();
+ } else {
+ Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": received mam result from invalid sender");
+ return;
+ }
+ } else if (original.fromServer(account)) { // Todo: Check if carbons are not always from the own bare JID
+ Pair<MessagePacket, Long> forwardedMessagePacket = original.getForwardedMessagePacket("received", Carbons.NAMESPACE);
+ if (null != forwardedMessagePacket) {
+ direction = MessageDirection.IN;
+ } else {
+ forwardedMessagePacket = original.getForwardedMessagePacket("sent", Carbons.NAMESPACE);
+ if (null != forwardedMessagePacket) {
+ direction = MessageDirection.OUT;
+ }
+ }
+
+ isCarbon = forwardedMessagePacket != null;
+ if (isCarbon) {
+ packet = forwardedMessagePacket.first;
+ timestamp = forwardedMessagePacket.second;
+ if (handleErrorMessage(account, packet)) {
+ return;
+ }
+ }
+ }
+
+ if (null == packet) {
+ packet = original;
+ }
+
+ if (null == timestamp) {
+ timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis());
+ }
+
+ final Jid from = packet.getFrom();
+
+ if (from == null) {
+ Log.d(Config.LOGTAG,"no from in: "+packet.toString());
+ return;
+ }
- if (timestamp == null) {
- timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis());
- }
final String body = packet.getBody();
- final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
- final String pgpEncrypted = packet.findChildContent(OpenPgpXep.ENCRYPTED_ELEMENT, OpenPgpXep.ENCRYPTED_NAMESPACE);
- final Element oob = packet.findChild("x", "jabber:x:oob");
- final boolean isOob = oob!= null && body != null && body.equals(oob.findChildContent("url"));
- final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
- int status;
+ final String pgpEncrypted = (Config.supportOpenPgp()) ? packet.findChildContent(OpenPgpXep.ENCRYPTED_ELEMENT, OpenPgpXep.ENCRYPTED_NAMESPACE) : null;
+ final Element axolotlEncrypted = (Config.supportOmemo()) ? packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX) : null;
final Jid counterpart;
final Jid to = packet.getTo();
- final Jid from = packet.getFrom();
- final String remoteMsgId = packet.getId();
- if (from == null) {
- Log.d(Config.LOGTAG,"no from in: "+packet.toString());
- return;
- }
-
- boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
- boolean isProperlyAddressed = (to != null ) && (!to.isBareJid() || account.countPresences() <= 1);
- boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
if (packet.fromAccount(account)) {
- status = Message.STATUS_SEND;
+ direction = (null == direction) ? MessageDirection.OUT : direction;
counterpart = to != null ? to : account.getJid();
} else {
- status = Message.STATUS_RECEIVED;
+ direction = (null == direction) ? MessageDirection.IN : direction;
counterpart = from;
}
- Invite invite = extractInvite(packet);
- if (invite != null && invite.execute(account)) {
- return;
- }
-
- if (extractChatState(mXmppConnectionService.find(account, counterpart.toBareJid()), packet)) {
- UiUpdateHelper.updateConversationUi();
- }
-
- if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) {
- Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat, query);
- if (isTypeGroupChat) {
- if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
- status = Message.STATUS_SEND_RECEIVED;
- isCarbon = true; //not really carbon but received from another resource
- if (MessageUtil.markMessage(conversation, remoteMsgId, status)) {
- return;
- } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
- Message message = conversation.findSentMessageWithBody(packet.getBody());
- if (message != null) {
- MessageUtil.markMessage(message, status);
- return;
- }
- }
- } else {
- status = Message.STATUS_RECEIVED;
- }
- }
- Message message;
- if (body != null && body.startsWith("?OTR") && Config.supportOtr()) {
- if (!isForwarded && !isTypeGroupChat && isProperlyAddressed) {
- message = parseOtrChat(body, from, remoteMsgId, conversation);
- if (message == null) {
- return;
- }
- } else {
- Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring OTR message from "+from+" isForwarded="+Boolean.toString(isForwarded)+", isProperlyAddressed="+Boolean.valueOf(isProperlyAddressed));
- message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
- }
- } else if (pgpEncrypted != null && Config.supportOpenPgp()) {
- message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
- } else if (axolotlEncrypted != null && Config.supportOmemo()) {
- Jid origin;
- if (conversation.getMode() == Conversation.MODE_MULTI) {
- origin = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
- if (origin == null) {
- Log.d(Config.LOGTAG,"axolotl message in non anonymous conference received");
- return;
- }
- } else {
- origin = from;
- }
- message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status);
- if (message == null) {
- return;
- }
- } else {
- message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
- }
-
- if (serverMsgId == null) {
- serverMsgId = extractStanzaId(packet, isTypeGroupChat ? conversation.getJid().toBareJid() : account.getServer());
- }
- message.setHttpUploaded(packet.hasChild(HttpUploadHint.ELEMENT_NAME, HttpUploadHint.NAMESPACE));
- if (message.isHttpUploaded()) {
- message.setTreatAsDownloadable(Message.Decision.MUST);
- }
-
- message.setCounterpart(counterpart);
- message.setRemoteMsgId(remoteMsgId);
- message.setServerMsgId(serverMsgId);
- message.setCarbon(isCarbon);
- message.setTime(timestamp);
- message.setOob(isOob);
- message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
- if (conversation.getMode() == Conversation.MODE_MULTI) {
- Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
- message.setTrueCounterpart(trueCounterpart);
- if (trueCounterpart != null) {
- updateLastseen(timestamp, account, trueCounterpart, false);
- }
- if (!isTypeGroupChat) {
- message.setType(Message.TYPE_PRIVATE);
- }
- } else {
- updateLastseen(timestamp, account, packet.getFrom(), true);
- }
+ if (MucPacketParser.extractAndExecuteInvite(account, packet)) {
+ return;
+ }
- boolean checkForDuplicates = query != null
- || (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay"))
- || message.getType() == Message.TYPE_PRIVATE;
- if (checkForDuplicates && conversation.hasDuplicateMessage(message)) {
- Logging.d(Config.LOGTAG, "skipping duplicate message from '" + message.getCounterpart().toString() + "' with remote id " + message.getRemoteMsgId());
- return;
- }
-
- if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
- conversation.prepend(message);
- } else {
- conversation.add(message);
- }
-
- if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- conversation.getAccount().getPgpDecryptionService().add(message);
- }
-
- if (query == null || query.getWith() == null) { //either no mam or catchup
- if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
- Logging.d("markRead", "MessageParser.onMessagePacketReceived1 (" + conversation.getName() + ")");
- mXmppConnectionService.markRead(conversation);
- if (query == null) {
- account.activateGracePeriod();
- }
- } else {
- // only not mam messages should be marked as unread
- if (query == null) {
- message.markUnread();
- }
- }
- }
+ Conversation conversation = null;
- if (query == null) {
- UiUpdateHelper.updateConversationUi();
- }
+ if ((body != null || pgpEncrypted != null || axolotlEncrypted != null)) {
+ boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
+ conversation = XmppConnectionServiceAccessor.xmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat);
- if (Settings.CONFIRM_MESSAGE_READ && remoteMsgId != null && !isForwarded && !isTypeGroupChat) {
- sendMessageReceipts(account, packet);
- }
+ final String remoteMsgId = packet.getId();
- if (message.getStatus() == Message.STATUS_RECEIVED
- && conversation.getOtrSession() != null
- && !conversation.getOtrSession().getSessionID().getUserID()
- .equals(message.getCounterpart().getResourcepart())) {
- conversation.endOtrIfNeeded();
- }
+ Message message = null;
+ if (Config.supportOtr() && OtrUtil.isOtrBody(body)) {
+ if (OtrUtil.isValidOtrMessagePacket(packet, account)) {
+ message = OtrMessageParser.parseOtrChat(body, counterpart, remoteMsgId, conversation);
+ } else {
+ Logging.d(Config.LOGTAG,conversation.getAccount().getJid().toBareJid()+": ignoring OTR message from "+counterpart+", addressed to: " + to);
+ }
+ } else {
+ message = this.createMessage(conversation, counterpart, body, pgpEncrypted, axolotlEncrypted);
+ }
- MessageUtil.extractFileParamsFromBody(message);
- FileParams fileParams = message.getFileParams();
- if (message.treatAsDownloadable() != Message.Decision.NEVER && message.treatAsDownloadable() != Message.Decision.NOT_DECIDED) {
- if (null != fileParams) {
- fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD);
- }
- }
- if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) {
- DatabaseBackend.getInstance().createMessage(message);
- }
- if (message.trusted()
- && message.treatAsDownloadable() != Message.Decision.NEVER
- && ConversationsPlusPreferences.autoAcceptFileSize() > 0
- && (message.isHttpUploaded() || MessageUtil.hasDownloadableLink(message))) { // Can this be checked by MessageUtil.needsDownload(message) ??
- HttpRetrieveHead hrh = new HttpRetrieveHead(message);
- hrh.setListener(new AutomaticFileDownload(true));
- hrh.retrieveAndSetContentTypeAndLength();
- } else {
- if (query == null) {
- mXmppConnectionService.getNotificationService().push(message);
- } else if (query.getWith() == null) { // mam catchup
- /*
- Like suggested in https://bugs.thedevstack.de/task/156 user should be notified
- in some other way of loaded messages.
- */
- // mXmppConnectionService.getNotificationService().pushFromBacklog(message);
- }
- }
+ if (null != message) {
+ message.setDirection(direction);
+ message.setMessageStatus(MessageStatus.TRANSMITTING);
+ message.setCarbon(isCarbon);
+ message.setMamReceived(isMAM);
+ message.setConfirmation(MessageParserUtil.extractMessageConfirmation(packet));
+
+ if (isTypeGroupChat
+ && this.fixMessageDirectionForGroupChatIfNecessary(message, counterpart)) {
+ return;
+ }
+
+ this.handleMessagePacketWithBodyOrEncryptedContent(message, packet, account, conversation, counterpart, isTypeGroupChat, remoteMsgId, serverMsgId, timestamp);
+
+ this.markMessagesUnreadIfNecessary(message, isMAM, isMAMCatchup);
+
+ if (!isMAM) {
+ UiUpdateHelper.updateConversationUi();
+ }
+
+ if (message.trusted()
+ && MessageStatus.TRANSMITTING == message.getMessageStatus() // Todo: Every message should be in transmitting state here
+ && ConversationsPlusPreferences.autoAcceptFileSize() > 0
+ && (message.isHttpUploaded() || MessageUtil.hasDownloadableLink(message))) { // Can this be checked by MessageUtil.needsDownload(message) ??
+ HttpRetrieveHead hrh = new HttpRetrieveHead(message);
+ hrh.setListener(new AutomaticFileDownload());
+ hrh.retrieveAndSetContentTypeAndLength();
+ } else {
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTED);
+ }
+ }
} else if (!packet.hasChild("body")){ //no body
- if (isTypeGroupChat) {
- Conversation conversation = mXmppConnectionService.find(account, from.toBareJid());
- if (packet.hasChild("subject")) {
- if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
- conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
- String subject = packet.findChildContent("subject");
- conversation.getMucOptions().setSubject(subject);
- final Bookmark bookmark = conversation.getBookmark();
- if (bookmark != null && bookmark.getBookmarkName() == null) {
- if (bookmark.setBookmarkName(subject)) {
- mXmppConnectionService.pushBookmarks(account);
- }
- }
- UiUpdateHelper.updateConversationUi();
- return;
- }
- }
-
- if (conversation != null && isMucStatusMessage) {
- for (Element child : mucUserElement.getChildren()) {
- if (child.getName().equals("status")
- && MucOptions.STATUS_CODE_ROOM_CONFIG_CHANGED.equals(child.getAttribute("code"))) {
- mXmppConnectionService.fetchConferenceConfiguration(conversation);
- }
- }
- }
- }
+ this.handleMessagePacketWithoutBody(packet, account);
}
- Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
- if (received == null) {
- received = packet.findChild("received", "urn:xmpp:receipts");
- }
- if (received != null && !packet.fromAccount(account)) {
- mXmppConnectionService.markMessage(account, from.toBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
- }
- Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
- if (displayed != null) {
- if (packet.fromAccount(account)) {
- Conversation conversation = mXmppConnectionService.find(account,counterpart.toBareJid());
- if (conversation != null) {
- Logging.d("markRead", "MessageParser.onMessagePacketReceived2 (" + conversation.getName() + ")");
- mXmppConnectionService.markRead(conversation);
- }
- } else {
- updateLastseen(timestamp, account, packet.getFrom(), true);
- final Message displayedMessage = mXmppConnectionService.markMessage(account, from.toBareJid(), displayed.getAttribute("id"), Message.STATUS_SEND_DISPLAYED);
- Message message = displayedMessage == null ? null : displayedMessage.prev();
- while (message != null
- && message.getStatus() == Message.STATUS_SEND_RECEIVED
- && message.getTimeSent() < displayedMessage.getTimeSent()) {
- MessageUtil.markMessage(message, Message.STATUS_SEND_DISPLAYED);
- message = message.prev();
- }
- }
- }
+ this.handleChatState(conversation, account, packet, counterpart);
- Element event = packet.findChild("event", "http://jabber.org/protocol/pubsub#event");
- if (event != null) {
- parseEvent(event, from, account);
- }
+ this.handleMessageReceipts(packet, account, from, counterpart, timestamp);
- String nick = packet.findChildContent("nick", "http://jabber.org/protocol/nick");
- if (nick != null) {
- Contact contact = account.getRoster().getContact(from);
- contact.setPresenceName(nick);
- }
- }
+ this.handleEvent(packet, account, from);
- private void sendMessageReceipts(Account account, MessagePacket packet) {
- ArrayList<String> receiptsNamespaces = new ArrayList<>();
- if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
- receiptsNamespaces.add("urn:xmpp:chat-markers:0");
- }
- if (packet.hasChild("request", "urn:xmpp:receipts")) {
- receiptsNamespaces.add("urn:xmpp:receipts");
- }
- if (receiptsNamespaces.size() > 0) {
- MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
- packet,
- receiptsNamespaces,
- packet.getType());
- XmppSendUtil.sendMessagePacket(account, receipt);
- }
+ this.handleNick(packet, account, from);
}
+
+ private void markMessagesUnreadIfNecessary(Message message, boolean isMAM, boolean isCatchup) {
+ Conversation conversation = message.getConversation();
+ Account account = conversation.getAccount();
+ if (!isMAM || isCatchup) { //either no mam or catchup
+ if (MessageUtil.isOutgoingMessage(message)) {
+ Logging.d("markRead", "MessageParser.onMessagePacketReceived1 (" + conversation.getName() + ")");
+ XmppConnectionServiceAccessor.xmppConnectionService.markRead(conversation);
+ if (!isMAM) {
+ account.activateGracePeriod();
+ }
+ } else {
+ // only not mam messages should be marked as unread
+ if (!isMAM) {
+ message.markUnread();
+ }
+ }
+ }
+ }
+
+ private void handleChatState(Conversation conversation, Account account, MessagePacket packet, Jid counterpart) {
+ if (null == conversation) {
+ conversation = XmppConnectionServiceAccessor.xmppConnectionService.find(account, counterpart.toBareJid());
+ }
+ if (extractChatState(conversation, packet)) {
+ UiUpdateHelper.updateConversationUi();
+ }
+ }
+
+ private boolean fixMessageDirectionForGroupChatIfNecessary(Message message, Jid counterpart) {
+ Conversation conversation = message.getConversation();
+ if (MessageUtil.isIncomingMessage(message)
+ && counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) { // Message is sent from me
+ message.setDirection(MessageDirection.OUT); // Not sure, but assuming
+ String remoteMsgId = message.getRemoteMsgId();
+ if (MessageUtil.setAndSaveMessageStatus(conversation, remoteMsgId, MessageStatus.RECEIVED)) { // TODO: Check if this status is correct
+ return true;
+ } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
+ Message existingMessage = conversation.findSentMessageWithBody(message.getBody());
+ if (existingMessage != null) {
+ MessageUtil.setAndSaveMessageStatus(existingMessage, MessageStatus.RECEIVED);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private Message createMessage(Conversation conversation, Jid counterpart, String body,
+ String pgpEncrypted, Element axolotlEncrypted) {
+ Message message;
+
+ if (pgpEncrypted != null) {
+ message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP);
+ } else if (axolotlEncrypted != null) {
+ message = AxolotlMessageParser.parseAxolotlChat(axolotlEncrypted, counterpart, counterpart, conversation);
+ if (message == null) {
+ return null;
+ }
+ } else {
+ message = new Message(conversation, body, Message.ENCRYPTION_NONE);
+ }
+
+ return message;
+ }
+
+ private Message handleMessagePacketWithBodyOrEncryptedContent(Message message, MessagePacket packet, Account account, Conversation conversation, Jid counterpart,
+ boolean isTypeGroupChat, String remoteMsgId, String serverMsgId, long timestamp) {
+ if (serverMsgId == null) {
+ serverMsgId = extractStanzaId(packet, isTypeGroupChat ? conversation.getJid().toBareJid() : account.getServer());
+ }
+ message.setHttpUploaded(packet.hasChild(HttpUploadHint.HTTP_UPLOAD_HINT));
+
+ message.setCounterpart(counterpart);
+ message.setRemoteMsgId(remoteMsgId);
+ message.setServerMsgId(serverMsgId);
+ message.setTime(timestamp);
+
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
+ message.setTrueCounterpart(trueCounterpart);
+ if (trueCounterpart != null) {
+ updateLastseen(timestamp, account, trueCounterpart, false);
+ }
+ if (!isTypeGroupChat) {
+ message.setType(Message.TYPE_PRIVATE);
+ }
+ } else {
+ updateLastseen(timestamp, account, packet.getFrom(), true);
+ }
+
+ conversation.add(message);
+
+ if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ conversation.getAccount().getPgpDecryptionService().add(message);
+ }
+
+ if (MessageUtil.isIncomingMessage(message)
+ && OtrUtil.isOtrSessionActive(conversation)
+ && !OtrUtil.isCounterpartOfActiveOtrSession(conversation, message.getCounterpart())) {
+ conversation.endOtrIfNeeded();
+ }
+
+ MessageParserUtil.extractFileParamsFromBody(message);
+
+ if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) {
+ DatabaseBackend.getInstance().createMessage(message);
+ }
+
+ return message;
+ }
+
+ private void handleNick(MessagePacket packet, Account account, Jid from) { // TODO: Is this a nick change?
+ String nick = packet.findChildContent("nick", "http://jabber.org/protocol/nick");
+ if (nick != null) {
+ Contact contact = account.getRoster().getContact(from);
+ contact.setPresenceName(nick);
+ }
+ }
+
+ private void handleEvent(MessagePacket packet, Account account, Jid from) {
+ Element event = packet.findChild("event", "http://jabber.org/protocol/pubsub#event");
+ if (event != null) {
+ parseEvent(event, from, account);
+ }
+ }
+
+ private void handleMessageReceipts(MessagePacket packet, Account account, Jid from, Jid counterpart, long timestamp) {
+ Element received = packet.findChild(ChatMarkers.RECEIVED);
+ if (received == null) {
+ received = packet.findChild(MessageDeliveryReceipts.RECEIVED);
+ }
+ if (received != null && !packet.fromAccount(account)) {
+ MessageUtil.markMessageAsReceived(account, from.toBareJid(), received.getAttribute("id"));
+ }
+ Element displayed = packet.findChild(ChatMarkers.DISPLAYED);
+ if (displayed != null) {
+ if (packet.fromAccount(account)) {
+ Conversation conversation = mXmppConnectionService.find(account, counterpart.toBareJid());
+ if (conversation != null) {
+ Logging.d("markRead", "MessageParser.onMessagePacketReceived2 (" + conversation.getName() + ")");
+ mXmppConnectionService.markRead(conversation);
+ }
+ } else {
+ updateLastseen(timestamp, account, packet.getFrom(), true);
+ Message message = XmppConnectionServiceAccessor.xmppConnectionService.getMessage(account, from.toBareJid(), displayed.getAttribute("id"));
+ if (null != message) {
+ ConversationUtil.markMessagesAsDisplayedUpToMessage(message);
+ }
+ }
+ }
+ }
+
+ private void handleMessagePacketWithoutBody(MessagePacket packet, Account account) {
+ if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
+ Jid from = packet.getFrom();
+ final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
+ boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
+ Conversation conversation = mXmppConnectionService.find(account, from.toBareJid());
+ if (packet.hasChild("subject")) {
+ if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
+ conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
+ String subject = packet.findChildContent("subject");
+ conversation.getMucOptions().setSubject(subject);
+ final Bookmark bookmark = conversation.getBookmark();
+ if (bookmark != null && bookmark.getBookmarkName() == null) {
+ if (bookmark.setBookmarkName(subject)) {
+ mXmppConnectionService.pushBookmarks(account);
+ }
+ }
+ UiUpdateHelper.updateConversationUi();
+ return;
+ }
+ }
+
+ if (conversation != null && isMucStatusMessage) {
+ for (Element child : mucUserElement.getChildren()) {
+ if (child.getName().equals("status")
+ && MucOptions.STATUS_CODE_ROOM_CONFIG_CHANGED.equals(child.getAttribute("code"))) {
+ mXmppConnectionService.fetchConferenceConfiguration(conversation);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java
index ed366a2c..a3caf503 100644
--- a/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java
+++ b/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java
@@ -6,6 +6,8 @@ import java.util.ArrayList;
import java.util.List;
import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.persistance.DatabaseBackend;
import de.thedevstack.conversationsplus.services.avatar.AvatarCache;
import de.thedevstack.conversationsplus.utils.AvatarUtil;
@@ -241,12 +243,14 @@ public class PresenceParser extends AbstractParser implements
if (statusMessage != null
&& !statusMessage.isEmpty()
&& conversation.countMessages() == 0) {
- conversation.add(new Message(
+ Message message = new Message(
conversation,
statusMessage,
- Message.ENCRYPTION_NONE,
- Message.STATUS_RECEIVED
- ));
+ Message.ENCRYPTION_NONE
+ );
+ message.setDirection(MessageDirection.IN);
+ message.setMessageStatus(MessageStatus.TRANSMITTED);
+ conversation.add(message);
}
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
index e721afbb..fd227e5a 100644
--- a/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/DatabaseBackend.java
@@ -58,7 +58,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 25;
private static final int C_TO_CPLUS_VERSION_OFFSET = 1000;
- private static final int CPLUS_DATABASE_VERSION = 3;
+ private static final int CPLUS_DATABASE_VERSION = 5;
private static final int CPLUS_DATABASE_VERSION_MULTIPLIER = 100;
private static final int PHYSICAL_DATABASE_VERSION = DATABASE_VERSION + C_TO_CPLUS_VERSION_OFFSET + (CPLUS_DATABASE_VERSION * CPLUS_DATABASE_VERSION_MULTIPLIER);
@@ -217,7 +217,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
cursor.close();
}
- if (oldVersion < 3 && newVersion >= 3) {
+ if ((oldVersion < 3 && newVersion >= 3)
+ || (oldVersion <= 5 && newVersion >= 4)) {
MessageDatabaseAccess.upgrade(db, oldVersion, newVersion);
}
}
@@ -719,25 +720,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
public Pair<Long, String> getLastMessageReceived(Account account) {
- Cursor cursor = null;
- try {
- SQLiteDatabase db = this.getReadableDatabase();
- String sql = "select messages.timeSent,messages.serverMsgId from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and (messages.status=0 or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
- String[] args = {account.getUuid()};
- cursor = db.rawQuery(sql, args);
- if (cursor.getCount() == 0) {
- return null;
- } else {
- cursor.moveToFirst();
- return new Pair<>(cursor.getLong(0), cursor.getString(1));
- }
- } catch (Exception e) {
- return null;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
+ return MessageDatabaseAccess.getLastMessageReceived(this.getReadableDatabase(), account.getUuid());
}
private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java
index 43ae6ebb..cfef7a53 100644
--- a/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/access/MessageDatabaseAccess.java
@@ -3,18 +3,25 @@ package de.thedevstack.conversationsplus.persistance.db.access;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.util.Pair;
import de.thedevstack.android.logcat.Logging;
+import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.FileParams;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.persistance.db.migrations.FileParamsBodyToDatabaseFieldsMigration;
+import de.thedevstack.conversationsplus.persistance.db.migrations.MessageStatusMigration;
import de.thedevstack.conversationsplus.utils.CryptoHelper;
/**
*
*/
public class MessageDatabaseAccess extends AbstractDatabaseAccess {
+
+ private static final String TABLE_NAME_MESSAGES = "messages";
// since cplus db version 1
public static final String TABLE_NAME_ADDITIONAL_PARAMETERS = "message_parameters";
private static final String COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD = "httpupload";
@@ -32,6 +39,9 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess {
private static final String COLUMN_NAME_MSG_PARAMS_FILE_IV = "file_iv";
private static final String COLUMN_NAME_MSG_PARAMS_FILE_KEY = "file_key";
private static final String COLUMN_NAME_MSG_PARAMS_ORIGINAL_FILE_NAME = "original_file_name";
+ // since cplus db version 4
+ private static final String COLUMN_NAME_MSG_STATUS = "status";
+ private static final String COLUMN_NAME_MSG_DIRECTION = "direction";
private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V1 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " ("
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, "
@@ -39,7 +49,24 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess {
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', "
+ "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)";
- private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " ("
+ private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V3 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " ("
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD + " INTEGER DEFAULT 0, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE + " TEXT DEFAULT NULL, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_SIZE + " INTEGER DEFAULT 0, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_HEIGHT + " INTEGER DEFAULT 0, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_WIDTH + " INTEGER DEFAULT 0, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_STATUS + " TEXT DEFAULT 'UNDEFINED', "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_NAME + " TEXT DEFAULT NULL, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_ORIGINAL_FILE_NAME + " TEXT DEFAULT NULL, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_PATH + " TEXT DEFAULT NULL, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL + " TEXT DEFAULT NULL, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_IV + " TEXT DEFAULT NULL, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_KEY + " TEXT DEFAULT NULL, "
+ + "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)";
+
+ private static final String TABLE_ADDITIONAL_PARAMETERS_CREATE_V4 = "CREATE TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " ("
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " TEXT, "
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD + " INTEGER DEFAULT 0, "
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION + " TEXT DEFAULT 'NOT_DECIDED', "
@@ -54,6 +81,8 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess {
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_URL + " TEXT DEFAULT NULL, "
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_IV + " TEXT DEFAULT NULL, "
+ MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_KEY + " TEXT DEFAULT NULL, "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_STATUS + " TEXT DEFAULT 'WAITING', "
+ + MessageDatabaseAccess.COLUMN_NAME_MSG_DIRECTION + " TEXT DEFAULT 'OUT', "
+ "FOREIGN KEY(" + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + ") REFERENCES " + Message.TABLENAME + "(" + Message.UUID + ") ON DELETE CASCADE)";
public static void updateMessageParameters(SQLiteDatabase db, Message message, String uuid) {
@@ -66,6 +95,8 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess {
additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID, message.getUuid());
additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_HTTPUPLOAD, message.isHttpUploaded() ? 1 : 0);
additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_TREATASDOWNLOADABLE_DECISION, message.treatAsDownloadable().name());
+ additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_DIRECTION, message.getDirection().name());
+ additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_STATUS, message.getMessageStatus().name());
if (null != message.getFileParams()) {
FileParams fileParams = message.getFileParams();
additionalParameters.put(MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_FILE_TYPE, fileParams.getMimeType());
@@ -104,6 +135,28 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess {
message.setTreatAsDownloadable(treatAsDownloadable);
+ String msgStatus = CursorHelper.getString(cursor, COLUMN_NAME_MSG_STATUS);
+ MessageStatus messageStatus = MessageStatus.WAITING;
+ try {
+ messageStatus = MessageStatus.valueOf(msgStatus);
+ } catch (IllegalArgumentException e) {
+ // Should only happen if the database is corrupted, but to be on the save side catch it here
+ Logging.e("db.msg", "Unknown MessageStatus found: '" + msgStatus + "'");
+ }
+
+ message.setMessageStatus(messageStatus);
+
+ String msgDirection = CursorHelper.getString(cursor, COLUMN_NAME_MSG_DIRECTION);
+ MessageDirection messageDirection = MessageDirection.OUT;
+ try {
+ messageDirection = MessageDirection.valueOf(msgDirection);
+ } catch (IllegalArgumentException e) {
+ // Should only happen if the database is corrupted, but to be on the save side catch it here
+ Logging.e("db.msg", "Unknown MessageDirection found: '" + msgDirection + "'");
+ }
+
+ message.setDirection(messageDirection);
+
String status = CursorHelper.getString(cursor, COLUMN_NAME_MSG_PARAMS_FILE_STATUS);
FileStatus fileStatus = FileStatus.UNDEFINED;
if (null != status && !status.isEmpty()) {
@@ -209,10 +262,96 @@ public class MessageDatabaseAccess extends AbstractDatabaseAccess {
}
cursor.close();
}
+ if (oldVersion < 4 && newVersion >= 4) {
+ Logging.d("db.upgrade.cplus", "Upgrade " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + ": new fields for status and direction");
+ String[] columnDefinitions = {
+ MessageDatabaseAccess.COLUMN_NAME_MSG_STATUS + " TEXT DEFAULT 'WAITING'",
+ MessageDatabaseAccess.COLUMN_NAME_MSG_DIRECTION + " TEXT DEFAULT 'OUT'"
+ };
+
+ addNewColumns(db, MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, columnDefinitions);
+
+ Logging.d("db.upgrade.cplus", "Migrate integer status from " + Message.TABLENAME + "." + Message.STATUS + " to dedicated status and direction field in " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS);
+ Cursor cursor = db.rawQuery("SELECT " + Message.UUID + ", " + Message.STATUS + " FROM " + Message.TABLENAME, null);
+ while (cursor.moveToNext()) {
+ String uuid = CursorHelper.getString(cursor, Message.UUID);
+ int status = CursorHelper.getInt(cursor, Message.STATUS);
+
+ MessageDirection direction = MessageStatusMigration.getMessageDirection(status);
+ MessageStatus newStatus = MessageStatusMigration.getMessageStatus(status);
+
+ ContentValues parameterValues = new ContentValues();
+ parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_STATUS, newStatus.name());
+ parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_DIRECTION, direction.name());
+
+ db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, parameterValues, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", new String[] {uuid});
+ }
+ cursor.close();
+ }
+ if (oldVersion < 5 && newVersion >= 5) { // ONLY NEEDED FOR DEV
+ Logging.d("db.upgrade.cplus", "Fixing hick up in first try");
+ String alterStatement = "ALTER TABLE " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " RENAME TO old_msg_params";
+ db.execSQL(alterStatement);
+
+ db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V4);
+
+ db.execSQL("INSERT INTO " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " SELECT message_uuid, httpupload, treatasdownloadable_decision," +
+ "file_type, file_size, file_height, file_width, file_status, file_name, original_file_name, file_path, file_url, file_iv, file_key, status, 'IN' FROM old_msg_params");
+
+ db.execSQL("DROP TABLE old_msg_params");
+
+ Logging.d("db.upgrade.cplus", "Migrate integer status from " + Message.TABLENAME + "." + Message.STATUS + " to dedicated status and direction field in " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS);
+ Cursor cursor = db.rawQuery("SELECT " + Message.UUID + ", " + Message.STATUS + " FROM " + Message.TABLENAME, null);
+ while (cursor.moveToNext()) {
+ String uuid = CursorHelper.getString(cursor, Message.UUID);
+ int status = CursorHelper.getInt(cursor, Message.STATUS);
+
+ MessageDirection direction = MessageStatusMigration.getMessageDirection(status);
+ MessageStatus newStatus = MessageStatusMigration.getMessageStatus(status);
+
+ ContentValues parameterValues = new ContentValues();
+ parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_STATUS, newStatus.name());
+ parameterValues.put(MessageDatabaseAccess.COLUMN_NAME_MSG_DIRECTION, direction.name());
+
+ db.update(MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS, parameterValues, MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + "=?", new String[] {uuid});
+ }
+ cursor.close();
+ }
+ if (oldVersion < 6 && newVersion >= 6) {
+
+ }
}
public static void create(SQLiteDatabase db) {
// Create Conversations+ related tables
- db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE);
+ db.execSQL(MessageDatabaseAccess.TABLE_ADDITIONAL_PARAMETERS_CREATE_V4);
+ }
+
+ public static Pair<Long, String> getLastMessageReceived(SQLiteDatabase db, String accountUuid) {
+ Cursor cursor = null;
+ try {
+ String sql = "select messages.timeSent,messages.serverMsgId " +
+ "from accounts " +
+ "join conversations on accounts.uuid=conversations.accountUuid " +
+ "join messages on conversations.uuid=messages.conversationUuid " +
+ "join " + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + " on messages.uuid=" + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "." + MessageDatabaseAccess.COLUMN_NAME_MSG_PARAMS_MSGUUID + " " +
+ "where accounts.uuid=? " +
+ "and (" + MessageDatabaseAccess.TABLE_NAME_ADDITIONAL_PARAMETERS + "." + MessageDatabaseAccess.COLUMN_NAME_MSG_DIRECTION + "='IN'" +
+ " or messages.carbon=1 or messages.serverMsgId not null) order by messages.timesent desc limit 1";
+ String[] args = {accountUuid};
+ cursor = db.rawQuery(sql, args);
+ if (cursor.getCount() == 0) {
+ return null;
+ } else {
+ cursor.moveToFirst();
+ return new Pair<>(cursor.getLong(0), cursor.getString(1));
+ }
+ } catch (Exception e) {
+ return null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/MessageStatusMigration.java b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/MessageStatusMigration.java
new file mode 100644
index 00000000..a0ffa256
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/db/migrations/MessageStatusMigration.java
@@ -0,0 +1,56 @@
+package de.thedevstack.conversationsplus.persistance.db.migrations;
+
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
+
+/**
+ */
+public class MessageStatusMigration {
+ // Following status integers copied from old Message class
+ 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 MessageDirection getMessageDirection(int status) {
+ switch (status) {
+ case STATUS_SEND:
+ case STATUS_SEND_DISPLAYED:
+ case STATUS_SEND_FAILED:
+ case STATUS_SEND_RECEIVED:
+ case STATUS_SEND_CANCELED:
+ case STATUS_UNSEND:
+ case STATUS_WAITING:
+ case STATUS_OFFERED:
+ return MessageDirection.OUT;
+ default:
+ return MessageDirection.IN;
+ }
+ }
+
+ public static MessageStatus getMessageStatus(int status) {
+ switch (status) {
+ case STATUS_UNSEND:
+ case STATUS_WAITING:
+ return MessageStatus.WAITING;
+ case STATUS_RECEIVED:
+ case STATUS_SEND:
+ return MessageStatus.TRANSMITTED;
+ case STATUS_OFFERED:
+ return MessageStatus.TRANSMITTING;
+ case STATUS_SEND_FAILED:
+ return MessageStatus.FAILED;
+ case STATUS_SEND_DISPLAYED:
+ return MessageStatus.DISPLAYED;
+ case STATUS_SEND_RECEIVED:
+ return MessageStatus.RECEIVED;
+ default:
+ return MessageStatus.CANCELED;
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java b/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java
index 3534ccd2..344e25a2 100644
--- a/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java
+++ b/src/main/java/de/thedevstack/conversationsplus/persistance/observers/FileDeletionObserver.java
@@ -6,6 +6,8 @@ import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Transferable;
import de.thedevstack.conversationsplus.entities.TransferablePlaceholder;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
@@ -30,11 +32,10 @@ public class FileDeletionObserver extends FileObserver {
for (Conversation conversation : XmppConnectionServiceAccessor.xmppConnectionService.getConversations()) {
Message message = conversation.findMessageWithFileAndUuid(uuid);
if (message != null) {
- message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
- final int s = message.getStatus();
- if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
- MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED);
- } else {
+ MessageUtil.setAndSaveStatusForFileDeleted(message);
+ if (MessageUtil.isIncomingMessage(message)
+ || (MessageUtil.isOutgoingMessage(message)
+ && !(MessageStatus.TRANSMITTING == message.getMessageStatus() || MessageStatus.WAITING == message.getMessageStatus()))) {
UiUpdateHelper.updateConversationUi();
}
return;
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java b/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java
index 0dcfa05e..4dd0a714 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/ExportLogsService.java
@@ -92,13 +92,11 @@ public class ExportLogsService extends Service {
new File(dir, contactJid.toBareJid().toString() + ".txt")));
}
String jid = null;
- switch (message.getStatus()) {
- case Message.STATUS_RECEIVED:
+ switch (message.getDirection()) {
+ case IN:
jid = getMessageCounterpart(message);
break;
- case Message.STATUS_SEND:
- case Message.STATUS_SEND_RECEIVED:
- case Message.STATUS_SEND_DISPLAYED:
+ case OUT:
jid = accountJid.toBareJid().toString();
break;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java b/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java
index c21c90d1..c5ada873 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java
@@ -29,9 +29,12 @@ import java.util.List;
import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import de.thedevstack.conversationsplus.ConversationsPlusColors;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
import de.thedevstack.conversationsplus.services.avatar.AvatarCache;
import de.thedevstack.conversationsplus.utils.ImageUtil;
import de.thedevstack.conversationsplus.utils.MessageUtil;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
import de.tzur.conversations.Settings;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.R;
@@ -62,7 +65,7 @@ public class NotificationService {
}
public boolean notify(final Message message) {
- return (message.getStatus() == Message.STATUS_RECEIVED)
+ return MessageUtil.isIncomingMessage(message)
&& !message.isRead()
&& ConversationsPlusPreferences.showNotification()
&& !message.getConversation().isMuted()
@@ -133,25 +136,28 @@ public class NotificationService {
}
public void push(final Message message) {
- if (!notify(message)) {
- return;
- }
- mXmppConnectionService.updateUnreadCountBadge();
- final boolean isScreenOn = mXmppConnectionService.isInteractive();
- if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
- return;
- }
- synchronized (notifications) {
- pushToStack(message);
- final Account account = message.getConversation().getAccount();
- final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
- && !account.inGracePeriod()
- && !this.inMiniGracePeriod(account);
- updateNotification(doNotify);
- if (doNotify) {
- notifyPebble(message);
+ if (message.isMamReceived()) {
+ return;
+ }
+ if (notify(message)) {
+ mXmppConnectionService.updateUnreadCountBadge();
+ final boolean isScreenOn = mXmppConnectionService.isInteractive();
+ if (this.mIsInForeground && isScreenOn && this.mOpenConversation == message.getConversation()) {
+ return;
+ }
+ synchronized (notifications) {
+ pushToStack(message);
+ final Account account = message.getConversation().getAccount();
+ final boolean doNotify = (!(this.mIsInForeground && this.mOpenConversation == null) || !isScreenOn)
+ && !account.inGracePeriod()
+ && !this.inMiniGracePeriod(account);
+ updateNotification(doNotify);
+ if (doNotify) {
+ notifyPebble(message);
+ }
}
}
+
}
public void clear() {
@@ -354,11 +360,9 @@ public class NotificationService {
private Message getImage(final Iterable<Message> messages) {
for (final Message message : messages) {
- if (message.getType() != Message.TYPE_TEXT
- && message.getTransferable() == null
+ if (message.getTransferable() == null
&& message.getEncryption() != Message.ENCRYPTION_PGP
- && message.getFileParams() != null
- && message.getFileParams().getHeight() > 0) { // TODO Use FileParams.getMimeType()
+ && MessageUtil.isAttachedFileAnImage(message)) { // TODO Use FileParams.getMimeType()
return message;
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
index bad0415f..2285a29d 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java
@@ -55,6 +55,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.services.avatar.AvatarCache;
import de.thedevstack.conversationsplus.services.avatar.AvatarService;
import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
@@ -71,6 +73,7 @@ import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
import de.thedevstack.conversationsplus.utils.XmppSendUtil;
import de.thedevstack.conversationsplus.services.muc.listener.ConferenceServiceDiscoveryReceived;
import de.thedevstack.conversationsplus.xmpp.XepRegistry;
+import de.thedevstack.conversationsplus.xmpp.chatmarkers.ChatMarkersXep;
import de.thedevstack.conversationsplus.xmpp.disco.ServiceDiscoveryIqPacketGenerator;
import de.thedevstack.conversationsplus.xmpp.disco.ServiceDiscoveryIqPacketParser;
import de.thedevstack.conversationsplus.xmpp.disco.ServiceDiscoveryXep;
@@ -97,8 +100,6 @@ import de.thedevstack.conversationsplus.entities.MucOptions.OnRenameListener;
import de.thedevstack.conversationsplus.entities.Presence;
import de.thedevstack.conversationsplus.entities.Roster;
import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult;
-import de.thedevstack.conversationsplus.entities.Transferable;
-import de.thedevstack.conversationsplus.entities.TransferablePlaceholder;
import de.thedevstack.conversationsplus.generator.IqGenerator;
import de.thedevstack.conversationsplus.generator.MessageGenerator;
import de.thedevstack.conversationsplus.generator.PresenceGenerator;
@@ -224,7 +225,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (conversation.getAccount() == account) {
Message message = conversation.findUnsentMessageWithUuid(uuid);
if (message != null) {
- MessageUtil.markMessage(message, Message.STATUS_SEND);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTED);
}
}
}
@@ -717,6 +718,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
XepRegistry.add(new ServiceDiscoveryXep());
XepRegistry.add(new PingXep());
XepRegistry.add(new OpenPgpXep());
+ XepRegistry.add(new ChatMarkersXep(Settings.CONFIRM_MESSAGE_RECEIVED));
final XmppConnection connection = new XmppConnection(account, this);
connection.setOnMessagePacketReceivedListener(this.mMessageParser);
@@ -752,10 +754,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
account.deactivateGracePeriod();
MessagePacket packet = null;
final boolean addToConversation = (conversation.getMode() != Conversation.MODE_MULTI
- || account.getServerIdentity() != XmppConnection.Identity.SLACK)
- && !message.edited();
+ || account.getServerIdentity() != XmppConnection.Identity.SLACK);
boolean saveInDb = addToConversation;
- message.setStatus(Message.STATUS_WAITING);
+ MessageUtil.setMessageStatus(message, MessageStatus.WAITING);
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
message.getConversation().endOtrIfNeeded();
@@ -763,7 +764,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
new Conversation.OnMessageFound() {
@Override
public void onMessageFound(Message message) {
- MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.FAILED);
}
});
}
@@ -833,9 +834,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
if (packet != null) {
if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
- message.setStatus(Message.STATUS_UNSEND);
+ MessageUtil.setMessageStatus(message, MessageStatus.TRANSMITTING);
} else {
- message.setStatus(Message.STATUS_SEND);
+ MessageUtil.setMessageStatus(message, MessageStatus.TRANSMITTED);
}
}
} else {
@@ -867,9 +868,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (resend) {
if (packet != null && addToConversation) {
if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
- MessageUtil.markMessage(message, Message.STATUS_UNSEND);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTING);
} else {
- MessageUtil.markMessage(message, Message.STATUS_SEND);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTED);
}
}
} else {
@@ -879,8 +880,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) {
if (saveInDb) {
databaseBackend.createMessage(message);
- } else if (message.edited()) {
- databaseBackend.updateMessage(message, message.getEditedId());
}
}
updateConversationUi();
@@ -1085,11 +1084,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void onMessageFound(Message message) {
if (!FileBackend.isFileAvailable(message)) {
- message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
- final int s = message.getStatus();
- if (s == Message.STATUS_WAITING || s == Message.STATUS_OFFERED || s == Message.STATUS_UNSEND) {
- MessageUtil.markMessage(message, Message.STATUS_SEND_FAILED);
- }
+ MessageUtil.setAndSaveStatusForFileDeleted(message);
}
}
});
@@ -2017,8 +2012,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
MessagePacket outPacket = mMessageGenerator.generateOtrChat(message);
if (outPacket != null) {
mMessageGenerator.addDelay(outPacket, message.getTimeSent());
- message.setStatus(Message.STATUS_SEND);
- databaseBackend.updateMessage(message);
+ MessageUtil.setMessageStatus(message, MessageStatus.TRANSMITTED);
+ DatabaseBackend.getInstance().updateMessage(message);
sendMessagePacket(account, outPacket);
}
}
@@ -2140,32 +2135,28 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void resetSendingToWaiting(Account account) {
for (Conversation conversation : getConversations()) {
if (conversation.getAccount() == account) {
- conversation.findUnsentTextMessages(new Conversation.OnMessageFound() {
+ conversation.findOugoingTextMessagesInTransmission(new Conversation.OnMessageFound() {
@Override
public void onMessageFound(Message message) {
- MessageUtil.markMessage(message, Message.STATUS_WAITING);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.WAITING);
}
});
}
}
}
- public Message markMessage(final Account account, final Jid recipient, final String uuid, final int status) {
- if (uuid == null) {
- return null;
- }
- for (Conversation conversation : getConversations()) {
- if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) {
- final Message message = conversation.findSentMessageWithUuidOrRemoteId(uuid);
- if (message != null) {
- MessageUtil.markMessage(message, status);
- }
- return message;
- }
- }
- return null;
- }
+ public Message getMessage(final Account account, final Jid recipient, final String uuid) {
+ if (uuid == null) {
+ return null;
+ }
+ for (Conversation conversation : getConversations()) {
+ if (conversation.getJid().toBareJid().equals(recipient) && conversation.getAccount() == account) {
+ return conversation.findSentMessageWithUuidOrRemoteId(uuid);
+ }
+ }
+ return null;
+ }
public int unreadCount() {
int count = 0;
@@ -2258,7 +2249,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
@Override
public void run() {
for (Message message : readMessages) {
- databaseBackend.updateMessage(message);
+ DatabaseBackend.getInstance().updateMessage(message);
}
}
};
@@ -2414,11 +2405,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return this.mNotificationService;
}
- public void resendFailedMessages(final Message message) {
- if (message.getStatus() == Message.STATUS_SEND_FAILED
- || message.getStatus() == Message.STATUS_SEND_CANCELED) {
+ public void resendFailedOrCanceledMessages(final Message message) {
+ if (MessageUtil.isOutgoingMessage(message)
+ && (MessageStatus.FAILED == message.getMessageStatus()
+ || MessageStatus.CANCELED == message.getMessageStatus())) {
message.setTime(System.currentTimeMillis());
- MessageUtil.markMessage(message, Message.STATUS_WAITING);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.WAITING);
this.resendMessage(message, false);
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/avatar/AvatarCache.java b/src/main/java/de/thedevstack/conversationsplus/services/avatar/AvatarCache.java
index a4def149..5e10f27c 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/avatar/AvatarCache.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/avatar/AvatarCache.java
@@ -12,8 +12,10 @@ import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.ListItem;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.MucOptions;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
import de.thedevstack.conversationsplus.utils.AvatarUtil;
import de.thedevstack.conversationsplus.utils.ImageUtil;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.UIHelper;
/**
@@ -177,7 +179,7 @@ public final class AvatarCache {
public static Bitmap get(Message message, int size, boolean cachedOnly) {
final Conversation conversation = message.getConversation();
- if (message.getStatus() == Message.STATUS_RECEIVED) {
+ if (MessageUtil.isIncomingMessage(message)) {
Contact c = message.getContact();
if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) {
return get(c, size, cachedOnly);
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java
index 63f1547f..62fed29b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/FileTransferManager.java
@@ -9,6 +9,7 @@ import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.services.FileTransferService;
import de.thedevstack.conversationsplus.services.filetransfer.http.upload.HttpUploadFileTransferEntity;
import de.thedevstack.conversationsplus.utils.MessageUtil;
@@ -124,9 +125,8 @@ public class FileTransferManager implements FileTransferStatusListener {
}
}
}
- if (!retransferStarted) {
- entity.getMessage().getFileParams().setFileStatus(FileStatus.UPLOAD_FAILED);
- MessageUtil.markMessage(entity.getMessage(), Message.STATUS_SEND_FAILED);
+ if (!retransferStarted && (entity instanceof HttpUploadFileTransferEntity)) {
+ MessageUtil.setAndSaveFileStatus(entity.getMessage(), FileStatus.UPLOAD_FAILED);
}
}
}
@@ -134,12 +134,13 @@ public class FileTransferManager implements FileTransferStatusListener {
@Override
public void onCancel(FileTransferEntity entity) {
this.activeTransfers.remove(entity.getMessage().getUuid());
- MessageUtil.markMessage(entity.getMessage(), Message.STATUS_SEND_CANCELED);
+ MessageUtil.setAndSaveMessageStatus(entity.getMessage(), MessageStatus.CANCELED);
}
@Override
public void onSuccess(FileTransferEntity entity) {
this.activeTransfers.remove(entity.getMessage().getUuid());
+ MessageUtil.setAndSaveMessageStatus(entity.getMessage(), MessageStatus.TRANSMITTED);
}
private boolean startFileTransfer(Message message, boolean delayed, WeightedTransferService wts) {
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java
index 5885e2e1..4401fb06 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/AutomaticFileDownload.java
@@ -12,20 +12,12 @@ import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
*
*/
public class AutomaticFileDownload implements HttpHeadRetrievedListener {
- private boolean notify;
-
- public AutomaticFileDownload(boolean notify) {
- this.notify = notify;
- }
@Override
public void onFileSizeRetrieved(long size, Message message) {
if (!this.transferFile(message)) {
}
- if (this.notify) {
- XmppConnectionServiceAccessor.xmppConnectionService.getNotificationService().push(message);
- }
}
/**
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java
index 1313bcca..62c1af59 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/download/HttpRetrieveHead.java
@@ -10,6 +10,7 @@ import de.thedevstack.conversationsplus.http.Http;
import de.thedevstack.conversationsplus.http.HttpClient;
import de.thedevstack.conversationsplus.http.HttpHeadRetrievedListener;
import de.thedevstack.conversationsplus.persistance.DatabaseBackend;
+import de.thedevstack.conversationsplus.utils.MessageParserUtil;
import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
import okhttp3.Call;
@@ -36,12 +37,11 @@ public class HttpRetrieveHead implements Http, Callback {
* If this code is reached and the URL is null something went wrong.
* Try again to extract the file parameters from the message.
*/
- MessageUtil.extractFileParamsFromBody(message);
+ MessageParserUtil.extractFileParamsFromBody(message);
this.url = (null != message.getFileParams()) ? message.getFileParams().getUrl() : null;
if (null == this.url) {
- message.setTreatAsDownloadable(Message.Decision.NEVER); // TODO find sth better
if (null != message.getFileParams()) {
- MessageUtil.setAndSaveFileStatus(message, FileStatus.UNDEFINED);
+ MessageUtil.setAndSaveFileStatus(message, FileStatus.NOT_FOUND);
}
}
}
@@ -89,7 +89,7 @@ public class HttpRetrieveHead implements Http, Callback {
fileParams.setSize(size);
fileParams.setMimeType(contentType);
if (0 < size) {
- fileParams.setFileStatus(FileStatus.NEEDS_DOWNLOAD);
+ MessageUtil.setFileStatus(message, FileStatus.NEEDS_DOWNLOAD);
}
DatabaseBackend.getInstance().updateMessage(message);
UiUpdateHelper.updateConversationUi();
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java
index 54186983..19c965e2 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferEntity.java
@@ -32,7 +32,7 @@ public class HttpUploadFileTransferEntity extends FileTransferEntity {
fileParams = new FileParams();
this.getMessage().setFileParams(fileParams);
}
- fileParams.setFileStatus(FileStatus.NEEDS_UPLOAD);
+ MessageUtil.setFileStatus(this.getMessage(), FileStatus.NEEDS_DOWNLOAD);
if (Config.ENCRYPT_ON_HTTP_UPLOADED
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
@@ -96,7 +96,7 @@ public class HttpUploadFileTransferEntity extends FileTransferEntity {
getUrl = new URL(getUrl.toString() + "#" + CryptoHelper.bytesToHex(this.getKey()));
}
- this.getMessage().getFileParams().setFileStatus(FileStatus.UPLOADED);
+ MessageUtil.setFileStatus(getMessage(), FileStatus.UPLOADED);
MessageUtil.updateFileParams(this.getMessage(), getUrl);
} catch (MalformedURLException e) {
Logging.e("httpupload", "Not a valid get url");
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java
index 28bb3a1c..b901ae4b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/filetransfer/http/upload/HttpUploadFileTransferService.java
@@ -9,6 +9,7 @@ import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
import de.thedevstack.conversationsplus.services.FileTransferService;
import de.thedevstack.conversationsplus.services.filetransfer.AbstractFileTransferService;
@@ -62,7 +63,7 @@ public class HttpUploadFileTransferService extends AbstractFileTransferService i
Logging.d("httpupload", "Requesting upload slot for file upload");
IqPacket request = HttpUploadRequestSlotPacketGenerator.generate(account, message.getContact().getJid(), file.getName(), file.getSize(), file.getMimeType());
XmppSendUtil.sendIqPacket(account, request, new HttpUploadSlotRequestReceived(entity));
- MessageUtil.markMessage(message, Message.STATUS_UNSEND);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTING);
Logging.d("httpupload", "Upload slot for file upload requested");
started = true;
diff --git a/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java b/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java
index 6ad413b4..34620808 100644
--- a/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java
+++ b/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java
@@ -15,6 +15,7 @@ import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.generator.AbstractGenerator;
+import de.thedevstack.conversationsplus.persistance.DatabaseBackend;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.xml.Element;
import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded;
@@ -47,7 +48,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
}
- Pair<Long,String> pair = mXmppConnectionService.databaseBackend.getLastMessageReceived(account);
+ Pair<Long,String> pair = DatabaseBackend.getInstance().getLastMessageReceived(account);
long startCatchup = pair == null ? 0 : pair.first;
long endCatchup = account.getXmppConnection().getLastSessionEstablished();
final Query query;
@@ -168,14 +169,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
final Conversation conversation = query.getConversation();
if (conversation != null) {
- conversation.sort();
conversation.setHasMessagesLeftOnServer(!done);
- } else {
- for(Conversation tmp : this.mXmppConnectionService.getConversations()) {
- if (tmp.getAccount() == query.getAccount()) {
- tmp.sort();
- }
- }
}
if (query.hasCallback()) {
query.callback();
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
index cd2ee4f8..4cecd7f8 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java
@@ -43,6 +43,8 @@ import java.util.Collections;
import java.util.List;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager;
import de.thedevstack.conversationsplus.services.filetransfer.http.delete.DeleteRemoteFileService;
import de.thedevstack.conversationsplus.ui.dialogs.SimpleConfirmDialog;
@@ -124,8 +126,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private RelativeLayout snackbar;
private TextView snackbarMessage;
private TextView snackbarAction;
- private boolean messagesLoaded = true;
- private Toast messageLoaderToast;
private final int KEYCHAIN_UNLOCK_NOT_REQUIRED = 0;
private final int KEYCHAIN_UNLOCK_REQUIRED = 1;
private final int KEYCHAIN_UNLOCK_PENDING = 2;
@@ -242,16 +242,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private ConversationActivity activity;
private Message selectedMessage;
- public void setMessagesLoaded() {
- this.messagesLoaded = true;
- }
-
private void sendMessage() {
final String body = mEditMessage.getText().toString();
if (body.length() == 0 || this.conversation == null) {
return;
}
- Message message = new Message(conversation, body, conversation.getNextEncryption());
+ Message message = MessageUtil.createOutgoingMessage(conversation, body);
+
if (conversation.getMode() == Conversation.MODE_MULTI) {
if (conversation.getNextCounterpart() != null) {
message.setCounterpart(conversation.getNextCounterpart());
@@ -503,8 +500,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (treatAsFile || (GeoHelper.isGeoUri(m.getBody()))) {
shareWith.setVisible(true);
}
- if (m.getStatus() == Message.STATUS_SEND_FAILED
- || Message.STATUS_SEND_CANCELED == m.getStatus()) {
+ if (MessageUtil.isOutgoingMessage(m)
+ && (MessageStatus.FAILED == m.getMessageStatus()
+ || MessageStatus.CANCELED == m.getMessageStatus())) {
sendAgain.setVisible(true);
}
if (m.hasFileOnRemoteHost()
@@ -518,15 +516,17 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m)));
}
if ((t != null && !(t instanceof TransferablePlaceholder))
- || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING
- || m.getStatus() == Message.STATUS_OFFERED))) {
+ || (m.isFileOrImage() && (MessageStatus.WAITING == m.getMessageStatus() || MessageStatus.TRANSMITTING == m.getMessageStatus()))) { // TODO Welche Einschraenkung noch um uebertragung abzubrechen?
cancelTransmission.setVisible(true);
}
if (treatAsFile) {
deleteFile.setVisible(true);
deleteFile.setTitle(activity.getString(R.string.delete_x_file,UIHelper.getFileDescriptionString(activity, m)));
}
- if (m.isHttpUploaded() && MessageUtil.isMessageSent(m) && AccountUtil.isFileTransferHttpAvailable(m.getConversation().getAccount())) {
+ if (m.isHttpUploaded()
+ && AccountUtil.isFileTransferHttpAvailable(m.getConversation().getAccount())
+ && MessageUtil.isOutgoingMessage(m)
+ && (MessageUtil.isMessageTransmittedOrDisplayedOrReceived(m) || MessageStatus.FAILED == m.getMessageStatus())) {
MenuItem deleteRemoteFile = menu.findItem(R.id.msg_ctx_menu_delete_remote_file);
deleteRemoteFile.setVisible(true);
}
@@ -613,7 +613,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
return;
}
}
- activity.xmppConnectionService.resendFailedMessages(message);
+ activity.xmppConnectionService.resendFailedOrCanceledMessages(message);
}
private void copyUrl(Message message) {
@@ -651,7 +651,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (transferable != null) {
transferable.cancel();
} else {
- MessageUtil.markMessage(message, Message.STATUS_SEND_CANCELED);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.CANCELED);
}
}
@@ -726,7 +726,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
this.mEditMessage.setKeyboardListener(this);
this.messagesView.setAdapter(messageListAdapter);
updateMessages();
- this.messagesLoaded = true;
int size = this.messageList.size();
if (size > 0) {
messagesView.setSelection(size - 1);
@@ -878,7 +877,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private Message getLastPgpDecryptableMessage() {
for (final Message message : this.messageList) {
if (message.getEncryption() == Message.ENCRYPTION_PGP
- && (message.getStatus() == Message.STATUS_RECEIVED || message.getStatus() >= Message.STATUS_SEND)
+ && MessageUtil.isMessageTransmittedOrDisplayedOrReceived(message)
&& message.getTransferable() == null) {
return message;
}
@@ -1047,18 +1046,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
synchronized (this.messageList) {
if (conversation.getMode() == Conversation.MODE_SINGLE) {
ChatState state = conversation.getIncomingChatState();
- if (state == ChatState.COMPOSING) {
- //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName())));
- } else if (state == ChatState.PAUSED) {
- //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName())));
- } else {
+ if (state != ChatState.COMPOSING && state != ChatState.PAUSED) { // Do not show status message
for (int i = this.messageList.size() - 1; i >= 0; --i) {
- if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
+ Message message = this.messageList.get(i);
+ if (MessageUtil.isIncomingMessage(message)) {
return;
} else {
- if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
+ if (MessageStatus.DISPLAYED == message.getMessageStatus()) {
this.messageList.add(i + 1,
- Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName())));
+ MessageUtil.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName())));
return;
}
}
@@ -1315,7 +1311,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
updatePgpMessages();
} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
final String body = mEditMessage.getText().toString();
- Message message = new Message(conversation, body, conversation.getNextEncryption());
+ Message message = MessageUtil.createOutgoingMessage(conversation, body);
sendAxolotlMessage(message);
} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) {
int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java
index 8852212b..080c7a51 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java
@@ -32,6 +32,7 @@ import de.thedevstack.conversationsplus.ui.adapter.ConversationAdapter;
import de.thedevstack.conversationsplus.utils.AccountUtil;
import de.thedevstack.conversationsplus.utils.ConversationUtil;
import de.thedevstack.conversationsplus.utils.FileUtils;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
@@ -319,10 +320,8 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
final OnPresenceSelected callback = new OnPresenceSelected() {
@Override
public void onPresenceSelected() {
- Message message = new Message(conversation,share.text, conversation.getNextEncryption());
- if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
- message.setCounterpart(conversation.getNextCounterpart());
- }
+ Message message = MessageUtil.createOutgoingMessage(conversation, share.text);
+
xmppConnectionService.sendMessage(message);
replaceToast(getString(R.string.shared_text_with_x, conversation.getName()));
finish();
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java
index e87cb052..97219b18 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java
@@ -101,7 +101,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
imagePreview.setVisibility(View.GONE);
CharSequence msgText = preview.first;
String msgPrefix = null;
- if (MessageUtil.isMessageSent(message)) {
+ if (MessageUtil.isOutgoingMessage(message)) {
msgPrefix = activity.getString(R.string.cplus_me);
} else if (conversation.getMode() == Conversation.MODE_MULTI) {
msgPrefix = UIHelper.getMessageDisplayName(message);
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java
index 7c7a6b57..331f8458 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageAdapter.java
@@ -14,10 +14,7 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
-import android.widget.Button;
import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import java.util.List;
@@ -32,6 +29,7 @@ import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Transferable;
import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.services.avatar.AvatarCache;
import de.thedevstack.conversationsplus.services.avatar.AvatarService;
import de.thedevstack.conversationsplus.services.filetransfer.http.download.AutomaticFileDownload;
@@ -48,12 +46,12 @@ import de.thedevstack.conversationsplus.utils.UIHelper;
import de.thedevstack.conversationsplus.utils.ui.TextViewUtil;
import de.thedevstack.conversationsplus.utils.ui.ViewUtil;
-public class MessageAdapter extends ArrayAdapter<Message> {
+import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.ME_COMMAND;
+import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.RECEIVED;
+import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.SENT;
+import static de.thedevstack.conversationsplus.ui.adapter.MessageViewHolder.STATUS;
- private static final int SENT = 0;
- private static final int RECEIVED = 1;
- private static final int STATUS = 2;
- private static final int ME_COMMAND = 3;
+public class MessageAdapter extends ArrayAdapter<Message> {
private ConversationActivity activity;
@@ -81,7 +79,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return ME_COMMAND;
} else if (message.getType() == Message.TYPE_STATUS) {
return STATUS;
- } else if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ } else if (MessageUtil.isIncomingMessage(message)) {
return RECEIVED;
}
@@ -101,15 +99,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private void displayStatus(ViewHolder viewHolder, Message message, int type) {
+ private void displayStatus(MessageViewHolder viewHolder, Message message, int type) {
String filesize = null;
String info = null;
boolean error = false;
ViewUtil.gone(viewHolder.indicatorReceived);
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
- && message.getStatus() <= Message.STATUS_RECEIVED;
- if (message.hasFileAttached() || message.getTransferable() != null) {
+ && MessageUtil.isIncomingMessage(message);
+ if (message.hasFileAttached() || MessageUtil.hasDownloadableLink(message)) {
FileParams params = message.getFileParams();
if (null != params) {
filesize = UIHelper.getHumanReadableFileSize(params.getSize());
@@ -119,36 +117,45 @@ public class MessageAdapter extends ArrayAdapter<Message> {
error = true;
}
}
- switch (message.getStatus()) {
- case Message.STATUS_WAITING:
+ Transferable d = message.getTransferable();
+ switch (message.getMessageStatus()) {
+ case WAITING:
info = getContext().getString(R.string.waiting);
break;
- case Message.STATUS_UNSEND:
- Transferable d = message.getTransferable();
- if (d != null) {
- info = getContext().getString(R.string.sending_file, d.getProgress());
+ case TRANSMITTING:
+ if (null != d) {
+ int resId = R.string.receiving_file;
+ if (MessageUtil.isOutgoingMessage(message)) {
+ resId = R.string.sending_file;
+ }
+ info = getContext().getString(resId, d.getProgress());
+ } else if (null != message.getFileParams() && FileStatus.NEEDS_UPLOAD == message.getFileParams().getFileStatus()) { // FIXME: Remove when Jingle is migrated
+ info = getContext().getString(R.string.offering);
} else {
- info = getContext().getString(R.string.sending);
+ if (!MessageUtil.needsDownload(message)) {
+ int resId = R.string.receiving;
+ if (MessageUtil.isOutgoingMessage(message)) {
+ resId = R.string.sending;
+ }
+ info = getContext().getString(resId);
+ }
}
break;
- case Message.STATUS_OFFERED:
- info = getContext().getString(R.string.offering);
- break;
- case Message.STATUS_SEND_RECEIVED:
+ case RECEIVED:
if (ConversationsPlusPreferences.indicateReceived()) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
}
break;
- case Message.STATUS_SEND_DISPLAYED:
+ case DISPLAYED:
if (ConversationsPlusPreferences.indicateReceived()) {
viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
}
break;
- case Message.STATUS_SEND_FAILED:
+ case FAILED:
info = getContext().getString(R.string.send_failed);
error = true;
break;
- case Message.STATUS_SEND_CANCELED:
+ case CANCELED:
info = getContext().getString(R.string.send_canceled);
error = true;
break;
@@ -156,7 +163,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
if (multiReceived) {
info = UIHelper.getMessageDisplayName(message);
}
- break;
}
this.displayEncryptionIndicator(message, viewHolder);
@@ -166,7 +172,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
this.displayRemoteFileStatus(message, viewHolder);
}
- private void displayRemoteFileStatus(Message message, ViewHolder viewHolder) {
+ private void displayRemoteFileStatus(Message message, MessageViewHolder viewHolder) {
if (message.hasFileAttached() && null != message.getFileParams() && null != viewHolder.remoteFileStatus) {
FileStatus fileStatus = message.getFileParams().getFileStatus();
if (fileStatus == FileStatus.DELETE_FAILED || fileStatus == FileStatus.DELETED || fileStatus == FileStatus.DELETING || fileStatus == FileStatus.NOT_FOUND) {
@@ -193,7 +199,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private void displayMessageTime(Message message, ViewHolder viewHolder, String filesize, String info, boolean error, boolean isSent) {
+ private void displayMessageTime(Message message, MessageViewHolder viewHolder, String filesize, String info, boolean error, boolean isSent) {
if (error && isSent) {
viewHolder.time.setTextColor(ConversationsPlusColors.warning());
} else {
@@ -201,7 +207,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(), message.getTimeSent());
String timeText = null;
- if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ if (MessageUtil.isIncomingMessage(message)) {
StringBuilder timeTextBuilder = new StringBuilder();
timeTextBuilder.append((null != formatedTime) ? formatedTime + ((null != info || null != filesize) ? " \u00B7 " : "") : "");
timeTextBuilder.append((null != filesize) ? filesize + ((null != info) ? " \u00B7 " : "") : "");
@@ -227,7 +233,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
TextViewUtil.setTextWithoutAutoLink(viewHolder.time, timeText);
}
- private void displayEncryptionIndicator(Message message, ViewHolder viewHolder) {
+ private void displayEncryptionIndicator(Message message, MessageViewHolder viewHolder) {
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
viewHolder.indicator.setVisibility(View.GONE);
} else {
@@ -260,40 +266,28 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private void displayInfoMessage(ViewHolder viewHolder, String text) {
- if (viewHolder.download_button != null) {
- viewHolder.download_button.setVisibility(View.GONE);
- }
- if (null != viewHolder.image) {
- viewHolder.image.setVisibility(View.GONE);
- }
+ private void displayInfoMessage(MessageViewHolder viewHolder, String text) {
+ ViewUtil.gone(viewHolder.download_button, viewHolder.image);
- viewHolder.messageBody.setVisibility(View.VISIBLE);
- viewHolder.messageBody.setText(text);
+ TextViewUtil.showAndSetText(viewHolder.messageBody, text);
viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false));
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayDecryptionFailed(ViewHolder viewHolder) {
- if (viewHolder.download_button != null) {
- viewHolder.download_button.setVisibility(View.GONE);
- }
-
- viewHolder.image.setVisibility(View.GONE);
+ private void displayDecryptionFailed(MessageViewHolder viewHolder) {
+ ViewUtil.gone(viewHolder.download_button, viewHolder.image);
- viewHolder.messageBody.setVisibility(View.VISIBLE);
- viewHolder.messageBody.setText(getContext().getString(
- R.string.decryption_failed));
+ TextViewUtil.showAndSetText(viewHolder.messageBody, R.string.decryption_failed);
viewHolder.messageBody.setTextColor(getMessageTextColor(viewHolder.darkBackground, false));
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
viewHolder.messageBody.setTextIsSelectable(false);
}
- private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
+ private void displayTextMessage(final MessageViewHolder viewHolder, final Message message) {
ViewUtil.gone(viewHolder.download_button, viewHolder.image);
- viewHolder.messageBody.setVisibility(View.VISIBLE);
+ ViewUtil.visible(viewHolder.messageBody);
viewHolder.messageBody.setIncludeFontPadding(true);
if (message.getBody() != null) {
@@ -317,7 +311,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
} else {
String privateMarker;
- if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ if (MessageUtil.isIncomingMessage(message)) {
privateMarker = activity.getString(R.string.private_message);
} else {
final String to;
@@ -352,15 +346,14 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.messageBody.setOnLongClickListener(openContextMenu);
}
- private void displayDownloadButton(ViewHolder viewHolder, String btnText, OnClickListener onClickListener) {
- viewHolder.download_button.setVisibility(View.VISIBLE);
- viewHolder.download_button.setText(btnText);
- viewHolder.download_button.setOnClickListener(onClickListener);
+ private void displayDownloadButton(MessageViewHolder viewHolder, String btnText, OnClickListener onClickListener) {
+ TextViewUtil.showAndSetText(viewHolder.download_button, btnText)
+ .setOnClickListener(onClickListener);
viewHolder.download_button.setOnLongClickListener(openContextMenu);
}
- private void displayFileInfoForFileMessage(final Message message, ViewHolder viewHolder) {
- viewHolder.messageBody.setVisibility(View.VISIBLE);
+ private void displayFileInfoForFileMessage(final Message message, MessageViewHolder viewHolder) {
+ ViewUtil.visible(viewHolder.messageBody);
StringBuilder fileInfos = new StringBuilder();
String filename = UIHelper.getDisplayFilename(message);
fileInfos.append((null != filename && !filename.isEmpty()) ? (filename) : "");
@@ -368,8 +361,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
TextViewUtil.setTextWithoutAutoLink(viewHolder.messageBody, fileInfos);
}
- private void displayDownloadableMessage(ViewHolder viewHolder, final Message message) {
- viewHolder.image.setVisibility(View.GONE);
+ private void displayDownloadableMessage(MessageViewHolder viewHolder, final Message message) {
+ ViewUtil.gone(viewHolder.image);
FileParams fileParams = message.getFileParams();
String btnText;
int resId = R.string.download_x_file;
@@ -393,8 +386,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
});
}
- private void displayOpenableMessage(ViewHolder viewHolder, final Message message) {
- viewHolder.image.setVisibility(View.GONE);
+ private void displayOpenableMessage(MessageViewHolder viewHolder, final Message message) {
+ ViewUtil.gone(viewHolder.image);
FileParams fileParams = message.getFileParams();
String btnText;
@@ -410,19 +403,18 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
- private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
- viewHolder.image.setVisibility(View.GONE);
- viewHolder.messageBody.setVisibility(View.GONE);
- viewHolder.download_button.setVisibility(View.VISIBLE);
- viewHolder.download_button.setText(R.string.show_location);
- viewHolder.download_button.setOnClickListener(new OpenLocationOnClickListener(this.activity, message));
+ private void displayLocationMessage(MessageViewHolder viewHolder, final Message message) {
+ ViewUtil.gone(viewHolder.image, viewHolder.messageBody);
+
+ TextViewUtil.showAndSetText(viewHolder.download_button, R.string.show_location)
+ .setOnClickListener(new OpenLocationOnClickListener(this.activity, message));
viewHolder.download_button.setOnLongClickListener(openContextMenu);
}
- private void displayImageMessage(ViewHolder viewHolder, final Message message) {
- ViewUtil.gone(viewHolder.download_button);
-
+ private void displayImageMessage(MessageViewHolder viewHolder, final Message message) {
+ ViewUtil.gone(viewHolder.download_button, viewHolder.messageBody);
+ ViewUtil.visible(viewHolder.image);
ImageUtil.loadBitmap(message, viewHolder.image, viewHolder.messageBody, true);
viewHolder.image.setOnClickListener(new OpenFileOnClickListener(this.activity, message));
@@ -430,11 +422,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
viewHolder.image.setOnLongClickListener(openContextMenu);
}
- private View displayStatusMessage(final Message message, ViewHolder viewHolder) {
+ private View displayStatusMessage(final Message message, MessageViewHolder viewHolder) {
final Conversation conversation = message.getConversation();
- viewHolder.status_message.setVisibility(View.VISIBLE);
- viewHolder.contact_picture.setVisibility(View.VISIBLE);
+ ViewUtil.visible(viewHolder.status_message, viewHolder.contact_picture);
+
if (conversation.getMode() == Conversation.MODE_SINGLE) {
viewHolder.contact_picture.setImageBitmap(AvatarCache.get(conversation.getContact(),
activity.getPixel(32)));
@@ -445,29 +437,20 @@ public class MessageAdapter extends ArrayAdapter<Message> {
return viewHolder.view;
}
- private void displayFileMessage(final Message message, ViewHolder viewHolder) {
+ private void displayFileMessage(final Message message, MessageViewHolder viewHolder) {
if (!(message.trusted()
&& MessageUtil.needsDownload(message)
&& ConversationsPlusPreferences.autoAcceptFileSize() > 0
&& message.isHttpUploaded() || ConversationsPlusPreferences.autoDownloadFileLink())) {
- new AutomaticFileDownload(false).transferFile(message);
+ new AutomaticFileDownload().transferFile(message);
}
Transferable transferable = message.getTransferable();
if (FileStatus.CHECKING_FILE_SIZE == message.getFileParams().getFileStatus()) {
displayInfoMessage(viewHolder, activity.getString(R.string.checking_remote_filesize));
} else if (MessageUtil.isAttachedFileAnImage(message)
- && (FileStatus.DOWNLOADED == message.getFileParams().getFileStatus()
- || FileStatus.DELETED == message.getFileParams().getFileStatus()
- || FileStatus.DELETING == message.getFileParams().getFileStatus()
- || FileStatus.DELETE_FAILED == message.getFileParams().getFileStatus()
- || FileStatus.NEEDS_UPLOAD == message.getFileParams().getFileStatus()
- || FileStatus.UPLOADED == message.getFileParams().getFileStatus()
- || FileStatus.UPLOAD_FAILED == message.getFileParams().getFileStatus()
- || (null != transferable
- && (transferable.isCanceled()
- || Transferable.STATUS_UPLOADING == transferable.getStatus())))) {
+ && hasFileStatusToShowMessage(message)) {
displayImageMessage(viewHolder, message);
- } else if ((MessageUtil.isTypeFileAndDecrypted(message) || FileStatus.DOWNLOADED == message.getFileParams().getFileStatus())
+ } else if ((MessageUtil.isTypeFileAndDecrypted(message) || hasFileStatusToShowMessage(message))
&& !MessageUtil.needsDownload(message)) {
displayOpenableMessage(viewHolder, message);
} else if (Message.Decision.NEVER == message.treatAsDownloadable() || !MessageUtil.mayFileRemoteAvailable(message)) {
@@ -478,10 +461,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
case Transferable.STATUS_OFFER_CHECK_FILESIZE:
displayDownloadableMessage(viewHolder, message);
break;
- case Transferable.STATUS_UPLOADING:
- // Should not happen, since this is now covered by the other if-statements
- // TODO Maybe in Jingle File Transfer?? Needs to be checked!
- break;
case Transferable.STATUS_DELETED:
case Transferable.STATUS_CHECKING:
case Transferable.STATUS_FAILED:
@@ -495,7 +474,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private void displayAvatar(final Message message, int type, ViewHolder viewHolder) {
+ private boolean hasFileStatusToShowMessage(Message message) {
+ Transferable transferable = message.getTransferable();
+ return (FileStatus.DOWNLOADED == message.getFileParams().getFileStatus()
+ || FileStatus.DELETED == message.getFileParams().getFileStatus()
+ || FileStatus.DELETING == message.getFileParams().getFileStatus()
+ || FileStatus.DELETE_FAILED == message.getFileParams().getFileStatus()
+ || FileStatus.NEEDS_UPLOAD == message.getFileParams().getFileStatus()
+ || FileStatus.UPLOADED == message.getFileParams().getFileStatus()
+ || FileStatus.UPLOAD_FAILED == message.getFileParams().getFileStatus()
+ || (null != transferable
+ && (transferable.isCanceled()
+ || Transferable.STATUS_UPLOADING == transferable.getStatus())));
+ }
+
+ private void displayAvatar(final Message message, int type, MessageViewHolder viewHolder) {
if (type == ME_COMMAND || (type == RECEIVED)) { // && message.getConversation().getMode() == Conversation.MODE_MULTI
ImageView imageView = viewHolder.contact_picture;
if (null != imageView) {
@@ -509,79 +502,24 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
- private View initializeView(int type, ViewGroup parent) {
- Integer viewResId = null;
-
- switch (type) {
- case SENT:
- viewResId = R.layout.message_sent;
- break;
- case RECEIVED:
- viewResId = R.layout.message_received;
- break;
- case STATUS:
- viewResId = R.layout.message_status;
- break;
- case ME_COMMAND:
- viewResId = R.layout.message_mecmd;
- break;
- }
-
- return activity.getLayoutInflater().inflate(viewResId, parent, false);
- }
-
- private ViewHolder initializeViewHolderAndView(int type, ViewGroup parent) {
- View view = initializeView(type, parent);
- ViewHolder viewHolder = new ViewHolder(view);
- if (SENT == type
- || RECEIVED == type
- || ME_COMMAND == type) {
- viewHolder.message_box = ViewUtil.visible(view, R.id.message_box);
- viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
- viewHolder.messageBody = (TextView) view.findViewById(R.id.message_body);
- viewHolder.time = (TextView) view.findViewById(R.id.message_time);
- viewHolder.indicatorReceived = (ImageView) view.findViewById(R.id.indicator_received);
- }
- if ((SENT == type
- || RECEIVED == type)) {
- viewHolder.download_button = (Button) view.findViewById(R.id.download_button);
- viewHolder.image = (ImageView) view.findViewById(R.id.message_image);
- }
- if (ME_COMMAND == type
- || (RECEIVED == type)) { // && message.getConversation().getMode() == Conversation.MODE_MULTI --> only muc received msgs
- viewHolder.contact_picture = ViewUtil.visible(view, R.id.message_photo);
- }
- if (RECEIVED == type) {
- viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption);
- }
- if (STATUS == type) {
- viewHolder.contact_picture = ViewUtil.visible(view, R.id.message_photo);
- viewHolder.status_message = TextViewUtil.visible(view, R.id.status_message);
- }
- if (SENT == type) { // This field is only useful for sent messages -> because of deletion of own files -> maybe a use case for recvd possible
- viewHolder.remoteFileStatus = TextViewUtil.gone(view, R.id.remote_file_status);
- }
- view.setTag(viewHolder);
-
- return viewHolder;
- }
-
@Override
public View getView(int position, View view, @NonNull ViewGroup parent) {
final Message message = getItem(position);
- if (null == message) {
+ if (null == message
+ || MessageStatus.DISPLAYED == message.getMessageStatus()
+ || MessageStatus.RECEIVED == message.getMessageStatus()) {
return view;
}
final boolean isInValidSession = message.isValidInSession();
final Conversation conversation = message.getConversation();
final Account account = conversation.getAccount();
final int type = getItemViewType(position);
- ViewHolder viewHolder;
+ MessageViewHolder viewHolder;
if (null == view) {
- viewHolder = initializeViewHolderAndView(type, parent);
+ viewHolder = new MessageViewHolder(this.activity, type, parent);
view = viewHolder.view;
} else {
- viewHolder = (ViewHolder) view.getTag();
+ viewHolder = (MessageViewHolder) view.getTag();
if (null == viewHolder) {
return view;
}
@@ -594,7 +532,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
this.displayAvatar(message, type, viewHolder);
viewHolder.darkBackground = (type == RECEIVED && !isInValidSession);
- this.displayStatus(viewHolder, message, type);
if (null != message.getTransferable() || message.hasFileAttached() || MessageUtil.hasDownloadableLink(message)) {
displayFileMessage(message, viewHolder);
@@ -607,21 +544,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
} else {
displayTextMessage(viewHolder, message);
}
+ this.displayStatus(viewHolder, message, type);
if (type == RECEIVED) {
if (isInValidSession) {
- viewHolder.encryption.setVisibility(View.GONE);
+ ViewUtil.gone(viewHolder.encryption);
} else {
viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
- viewHolder.encryption.setVisibility(View.VISIBLE);
- viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption()));
+ TextViewUtil.showAndSetText(viewHolder.encryption, CryptoHelper.encryptionTypeToText(message.getEncryption()));
}
}
return view;
}
- private void displayPgpEncryptedMessage(ViewHolder viewHolder, Account account) {
+ private void displayPgpEncryptedMessage(MessageViewHolder viewHolder, Account account) {
if (activity.hasPgp()) {
if (account.getPgpDecryptionService().isRunning()) {
displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting));
@@ -638,25 +575,4 @@ public class MessageAdapter extends ArrayAdapter<Message> {
});
}
}
-
- private static class ViewHolder {
- protected ViewHolder(View view) {
- this.view = view;
- }
-
- protected View view;
-
- protected LinearLayout message_box;
- protected Button download_button;
- protected ImageView image;
- protected ImageView indicator;
- protected ImageView indicatorReceived;
- protected TextView time;
- protected TextView messageBody;
- protected ImageView contact_picture;
- protected TextView status_message;
- protected TextView encryption;
- public TextView remoteFileStatus;
- protected boolean darkBackground;
- }
} \ No newline at end of file
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageViewHolder.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageViewHolder.java
new file mode 100644
index 00000000..d66d4f01
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/MessageViewHolder.java
@@ -0,0 +1,97 @@
+package de.thedevstack.conversationsplus.ui.adapter;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import de.thedevstack.conversationsplus.R;
+import de.thedevstack.conversationsplus.utils.ui.TextViewUtil;
+import de.thedevstack.conversationsplus.utils.ui.ViewUtil;
+
+/**
+ */
+class MessageViewHolder {
+
+ static final int SENT = 0;
+ static final int RECEIVED = 1;
+ static final int STATUS = 2;
+ static final int ME_COMMAND = 3;
+
+ View view;
+
+ LinearLayout message_box;
+ Button download_button;
+ ImageView image;
+ ImageView indicator;
+ ImageView indicatorReceived;
+ TextView time;
+ TextView messageBody;
+ ImageView contact_picture;
+ TextView status_message;
+ TextView encryption;
+ TextView remoteFileStatus;
+ boolean darkBackground;
+
+ MessageViewHolder(Activity activity, int type, ViewGroup parent) {
+ int viewResId = this.resolveViewId(type, parent);
+ this.view = activity.getLayoutInflater().inflate(viewResId, parent, false);
+ this.initializeViewElements(type);
+ }
+
+ private int resolveViewId(int type, ViewGroup parent) {
+ Integer viewResId = null;
+
+ switch (type) {
+ case SENT:
+ viewResId = R.layout.message_sent;
+ break;
+ case RECEIVED:
+ viewResId = R.layout.message_received;
+ break;
+ case STATUS:
+ viewResId = R.layout.message_status;
+ break;
+ case ME_COMMAND:
+ viewResId = R.layout.message_mecmd;
+ break;
+ }
+
+ return viewResId;
+ }
+
+ private void initializeViewElements(int type) {
+ if (SENT == type
+ || RECEIVED == type
+ || ME_COMMAND == type) {
+ this.message_box = ViewUtil.visible(view, R.id.message_box);
+ this.indicator = (ImageView) view.findViewById(R.id.security_indicator);
+ this.messageBody = (TextView) view.findViewById(R.id.message_body);
+ this.time = (TextView) view.findViewById(R.id.message_time);
+ this.indicatorReceived = (ImageView) view.findViewById(R.id.indicator_received);
+ }
+ if ((SENT == type
+ || RECEIVED == type)) {
+ this.download_button = (Button) view.findViewById(R.id.download_button);
+ this.image = (ImageView) view.findViewById(R.id.message_image);
+ }
+ if (ME_COMMAND == type
+ || (RECEIVED == type)) { // && message.getConversation().getMode() == Conversation.MODE_MULTI --> only muc received msgs
+ this.contact_picture = ViewUtil.visible(view, R.id.message_photo);
+ }
+ if (RECEIVED == type) {
+ this.encryption = (TextView) view.findViewById(R.id.message_encryption);
+ }
+ if (STATUS == type) {
+ this.contact_picture = ViewUtil.visible(view, R.id.message_photo);
+ this.status_message = TextViewUtil.visible(view, R.id.status_message);
+ }
+ if (SENT == type) { // This field is only useful for sent messages -> because of deletion of own files -> maybe a use case for recvd possible
+ this.remoteFileStatus = TextViewUtil.gone(view, R.id.remote_file_status);
+ }
+ view.setTag(this);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java
index b43384a4..60431d54 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/dialogs/MessageDetailsDialog.java
@@ -13,6 +13,10 @@ import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.FileParams;
import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.UIHelper;
import de.thedevstack.conversationsplus.utils.ui.TextViewUtil;
@@ -78,26 +82,30 @@ public class MessageDetailsDialog extends AbstractAlertDialog {
protected void displayMessageStatusInfo(View view, Message message) {
TextView msgStatusTextView = (TextView) view.findViewById(R.id.dlgMsgDetMsgStatus);
int msgStatusResId;
- switch (message.getStatus()) {
- case Message.STATUS_WAITING:
+ switch (message.getMessageStatus()) {
+ case WAITING:
msgStatusResId = R.string.dlg_msg_details_msg_status_waiting;
break;
- case Message.STATUS_UNSEND:
- msgStatusResId = R.string.dlg_msg_details_msg_status_unsend;
+ case TRANSMITTING:
+ if (MessageUtil.isOutgoingMessage(message)) {
+ msgStatusResId = R.string.dlg_msg_details_msg_status_unsend;
+ } else if (FileStatus.NEEDS_UPLOAD == message.getFileParams().getFileStatus()) { // FIXME: Remove when Jingle is migrated
+ msgStatusResId = R.string.dlg_msg_details_msg_status_offered;
+ } else {
+ msgStatusResId = R.string.dlg_msg_details_msg_status_receiving;
+ }
break;
- case Message.STATUS_OFFERED:
- msgStatusResId = R.string.dlg_msg_details_msg_status_offered;
- break;
- case Message.STATUS_SEND_FAILED:
+ case FAILED:
msgStatusResId = R.string.dlg_msg_details_msg_status_failed;
msgStatusTextView.setTextColor(ConversationsPlusColors.error());
break;
- case Message.STATUS_RECEIVED:
- msgStatusResId = R.string.dlg_msg_details_msg_status_received;
+ case TRANSMITTED:
+ if (MessageUtil.isIncomingMessage(message)) {
+ msgStatusResId = R.string.dlg_msg_details_msg_status_received;
+ } else {
+ msgStatusResId = R.string.dlg_msg_details_msg_status_sent;
+ }
break;
- case Message.STATUS_SEND:
- case Message.STATUS_SEND_DISPLAYED:
- case Message.STATUS_SEND_RECEIVED:
default:
msgStatusResId = R.string.dlg_msg_details_msg_status_sent;
}
@@ -154,7 +162,7 @@ public class MessageDetailsDialog extends AbstractAlertDialog {
// Get own nick for MUC
me = conversation.getMucOptions().getActualNick();
}
- if (Message.STATUS_RECEIVED == message.getStatus()) {
+ if (MessageUtil.isIncomingMessage(message)) {
// Sender was chat partner, if the status is for my account received
sender.setText(other);
// Set receipient to myself in case of normal chat or private message in MUC
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnClickListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnClickListener.java
index e3c85fdf..9eaec00c 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnClickListener.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnClickListener.java
@@ -1,6 +1,5 @@
package de.thedevstack.conversationsplus.ui.listeners;
-import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.Toast;
@@ -11,6 +10,7 @@ import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.ui.ConversationActivity;
import de.thedevstack.conversationsplus.ui.EditAccountActivity;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
/**
*/
@@ -25,7 +25,7 @@ public class ContactPictureOnClickListener implements View.OnClickListener {
@Override
public void onClick(View view) {
- if (message.getStatus() <= Message.STATUS_RECEIVED) { // What does this mean?
+ if (MessageUtil.isIncomingMessage(message)) {
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
if (message.getCounterpart() != null) {
String user = message.getCounterpart().isBareJid() ? message.getCounterpart().toString() : message.getCounterpart().getResourcepart();
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnLongClickListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnLongClickListener.java
index 49ff764b..fef10f90 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnLongClickListener.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ContactPictureOnLongClickListener.java
@@ -7,6 +7,7 @@ import de.thedevstack.conversationsplus.R;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.ui.ConversationActivity;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.ui.QrCodeUtil;
/**
@@ -22,7 +23,7 @@ public class ContactPictureOnLongClickListener implements View.OnLongClickListen
@Override
public boolean onLongClick(View view) {
- if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ if (MessageUtil.isIncomingMessage(message)) {
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
if (message.getCounterpart() != null) {
String user = message.getCounterpart().getResourcepart();
diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java
index e6ec7496..a65e9c7a 100644
--- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java
+++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ResizePictureUserDecisionListener.java
@@ -113,19 +113,14 @@ public class ResizePictureUserDecisionListener implements UserDecisionListener {
}
protected Message createMessage() {
- final Message message;
- if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
- } else {
- message = new Message(conversation, "", conversation.getNextEncryption());
- }
+ final Message message = MessageUtil.createOutgoingMessage(conversation, "");
if (null != conversation.getNextCounterpart() && null == message.getCounterpart()) {
message.setCounterpart(conversation.getNextCounterpart());
}
//message.setType(Message.TYPE_IMAGE);
message.setFileParams(new FileParams());
- message.getFileParams().setFileStatus(FileStatus.NEEDS_UPLOAD);
+ MessageUtil.setFileStatus(message, FileStatus.NEEDS_UPLOAD);
return message;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java
index 709b3f20..616cf962 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/ConversationUtil.java
@@ -8,6 +8,7 @@ import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.FileParams;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.exceptions.FileCopyException;
import de.thedevstack.conversationsplus.persistance.FileBackend;
import de.thedevstack.conversationsplus.ui.UiCallback;
@@ -17,18 +18,22 @@ import de.thedevstack.conversationsplus.ui.UiCallback;
*/
public class ConversationUtil {
+ public static void markMessagesAsDisplayedUpToMessage(Message message) {
+ Conversation conversation = message.getConversation();
+
+ while (null != message && (MessageStatus.RECEIVED == message.getMessageStatus() || MessageStatus.TRANSMITTED == message.getMessageStatus())) {
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.DISPLAYED);
+
+ message = conversation.getMessageBefore(message);
+ }
+ }
+
public static void attachLocationToConversation(final Conversation conversation,
final Uri uri,
final UiCallback<Message> callback) {
- int encryption = conversation.getNextEncryption();
- if (encryption == Message.ENCRYPTION_PGP) {
- encryption = Message.ENCRYPTION_DECRYPTED;
- }
- Message message = new Message(conversation, uri.toString(), encryption);
- if (conversation.getNextCounterpart() != null) {
- message.setCounterpart(conversation.getNextCounterpart());
- }
- if (encryption == Message.ENCRYPTION_DECRYPTED) {
+ Message message = MessageUtil.createOutgoingMessage(conversation, uri.toString());
+
+ if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
PgpEngine.getInstance().encrypt(message, callback);
} else {
callback.success(message);
@@ -38,18 +43,12 @@ public class ConversationUtil {
public static void attachFileToConversation(final Conversation conversation,
final Uri uri,
final UiCallback<Message> callback) {
- final Message message;
- if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
- message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
- } else {
- message = new Message(conversation, "", conversation.getNextEncryption());
- }
- message.setCounterpart(conversation.getNextCounterpart());
- //message.setType(Message.TYPE_FILE);
+ final Message message = MessageUtil.createOutgoingMessage(conversation, "");
+
if (null == message.getFileParams()) {
message.setFileParams(new FileParams());
}
- message.getFileParams().setFileStatus(FileStatus.NEEDS_UPLOAD);
+ MessageUtil.setFileStatus(message, FileStatus.NEEDS_UPLOAD);
String path = FileUtils.getPath(uri);
if (path != null) {
message.getFileParams().setPath(path);
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java
index 913f2ab5..907e7fd7 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java
@@ -95,8 +95,7 @@ public class ExceptionHelper {
Jid.fromString(activity.getString(R.string.cplus_bugreport_jabberid)), false);
} catch (final InvalidJidException ignored) {
}
- Message message = new Message(conversation, report
- .toString(), Message.ENCRYPTION_NONE);
+ Message message = MessageUtil.createOutgoingMessage(conversation, report.toString());
service.sendMessage(message);
}
});
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/GeoHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/GeoHelper.java
index 662f797a..b4be2d35 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/GeoHelper.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/GeoHelper.java
@@ -41,7 +41,7 @@ public class GeoHelper {
}
final Conversation conversation = message.getConversation();
String label;
- if (conversation.getMode() == Conversation.MODE_SINGLE && message.getStatus() == Message.STATUS_RECEIVED) {
+ if (conversation.getMode() == Conversation.MODE_SINGLE && MessageUtil.isIncomingMessage(message)) {
try {
label = "(" + URLEncoder.encode(message.getConversation().getName(), "UTF-8") + ")";
} catch (UnsupportedEncodingException e) {
@@ -55,7 +55,7 @@ public class GeoHelper {
locationPluginIntent.putExtra("latitude",latitude);
locationPluginIntent.putExtra("longitude",longitude);
if (conversation.getMode() == Conversation.MODE_SINGLE) {
- if (message.getStatus() == Message.STATUS_RECEIVED) {
+ if (MessageUtil.isIncomingMessage(message)) {
locationPluginIntent.putExtra("name",conversation.getName());
locationPluginIntent.putExtra("jid",message.getCounterpart().toString());
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageParserUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageParserUtil.java
new file mode 100644
index 00000000..5044d132
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageParserUtil.java
@@ -0,0 +1,164 @@
+package de.thedevstack.conversationsplus.utils;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import de.thedevstack.conversationsplus.entities.FileParams;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.entities.Transferable;
+import de.thedevstack.conversationsplus.enums.FileStatus;
+import de.thedevstack.conversationsplus.enums.MessageConfirmation;
+import de.thedevstack.conversationsplus.xmpp.chatmarkers.ChatMarkers;
+import de.thedevstack.conversationsplus.xmpp.receipts.MessageDeliveryReceipts;
+import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
+
+/**
+ */
+public class MessageParserUtil {
+ public static MessageConfirmation extractMessageConfirmation(MessagePacket messagePacket) {
+ if (messagePacket.hasChild(ChatMarkers.MARKABLE)
+ && messagePacket.hasChild(MessageDeliveryReceipts.REQUEST)) {
+ return MessageConfirmation.DELIVERY_RECEIPT_CHAT_MARKERS;
+ } else if (messagePacket.hasChild(ChatMarkers.MARKABLE)) {
+ return MessageConfirmation.CHAT_MARKERS;
+ } else if (messagePacket.hasChild(MessageDeliveryReceipts.REQUEST)) {
+ return MessageConfirmation.DELIVERY_RECEIPT;
+ } else {
+ return MessageConfirmation.NONE;
+ }
+ }
+
+ public static void extractFileParamsFromBody(Message message) {
+ if (null == message) {
+ return;
+ }
+
+ // Ensure that for every message the fileParams are set after calling this method
+ FileParams fileParams = message.getFileParams();
+ if (null == fileParams) {
+ fileParams = new FileParams();
+ message.setFileParams(fileParams);
+ }
+
+ String body = message.getBody();
+ /**
+ * there are a few cases where spaces result in an unwanted behavior, e.g.
+ * "http://example.com/image.jpg" text that will not be shown /abc.png"
+ * or more than one image link in one message.
+ */
+ if (null == body || body.isEmpty() || body.contains(" ")) {
+ return;
+ }
+
+ URL url = MessageParserUtil.extractValidUrlFromBody(message);
+ if (null != url) {
+ String extension = FileUtils.getRelevantExtension(url);
+ if (message.isHttpUploaded()) {
+ fileParams.setUrl(url.toString());
+ if (null != extension
+ && (Transferable.WELL_KNOWN_EXTENSIONS.contains(extension.toLowerCase()) || Transferable.VALID_IMAGE_EXTENSIONS.contains(extension.toLowerCase()))) {
+ message.setTreatAsDownloadable(Message.Decision.MUST);
+ } else {
+ MessageParserUtil.setParamsToAvoidDownload(message, fileParams);
+ }
+
+ extractFilename(message, url.toString());
+ return;
+ }
+
+ if (extension == null) {
+ MessageParserUtil.setParamsToAvoidDownload(message, fileParams);
+ return;
+ }
+ byte[] ivAndKey = UrlUtil.getIvAndKeyFromURL(url);
+
+ if (null != ivAndKey) {
+ if (MimeUtils.guessMimeTypeFromExtension(extension) != null) {
+ message.setTreatAsDownloadable(Message.Decision.MUST);
+ fileParams.setKeyAndIv(ivAndKey);
+ } else {
+ MessageParserUtil.setParamsToAvoidDownload(message, fileParams);
+ }
+ } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
+ || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
+ message.setTreatAsDownloadable(Message.Decision.SHOULD);
+ MessageUtil.setFileStatus(message, FileStatus.NEEDS_DOWNLOAD);
+ } else {
+ MessageParserUtil.setParamsToAvoidDownload(message, fileParams);
+ }
+
+ if (message.treatAsDownloadable() == Message.Decision.MUST
+ || message.treatAsDownloadable() == Message.Decision.SHOULD) {
+ fileParams.setUrl(url.toString());
+ extractFilename(message, url.toString());
+ MessageUtil.setFileStatus(message, FileStatus.NEEDS_DOWNLOAD);
+ }
+ } else {
+ MessageParserUtil.setParamsToAvoidDownload(message, fileParams);
+ }
+ }
+
+ private static void setParamsToAvoidDownload(Message message, FileParams fileParams) {
+ message.setTreatAsDownloadable(Message.Decision.NEVER);
+ MessageUtil.setFileStatus(message, FileStatus.UNDEFINED);
+ }
+
+ private static void extractFilename(Message message, String url) {
+ String originalFilename = FileUtils.getFilenameFromPath(url);
+ final String lowerCaseFilename = originalFilename.toLowerCase();
+ final String lastPart = FileUtils.getLastExtension(lowerCaseFilename);
+
+ detectAndSetEncryption(lastPart, message);
+
+ String filenameExtension;
+ if (!lastPart.isEmpty() && Transferable.VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
+ filenameExtension = FileUtils.getSecondToLastExtension(lowerCaseFilename);
+ originalFilename = originalFilename.replace("." + lastPart, "");
+ } else {
+ filenameExtension = lastPart;
+ }
+ message.setRelativeFilePath(message.getUuid() + "." + filenameExtension);
+
+ message.getFileParams().setOriginalFilename(originalFilename);
+ }
+
+ private static void detectAndSetEncryption(String lastPart, Message message) {
+ if (!lastPart.isEmpty() && ("pgp".equals(lastPart) || "gpg".equals(lastPart))) {
+ message.setEncryption(Message.ENCRYPTION_PGP);
+ } else if (message.getEncryption() != Message.ENCRYPTION_OTR
+ && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ } else if ((message.getEncryption() == Message.ENCRYPTION_OTR
+ || message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
+ && message.getFileParams() != null && message.getFileParams().getKey() == null) {
+ // If an encryption is set for the message, but no key given -> decryption not possible
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ }
+ }
+
+ private static URL extractValidUrlFromBody(Message message) {
+ if (null == message) {
+ return null;
+ }
+ String body = message.getBody();
+ /**
+ * there are a few cases where spaces result in an unwanted behavior, e.g.
+ * "http://example.com/image.jpg" text that will not be shown /abc.png"
+ * or more than one image link in one message.
+ */
+ if (null == body || body.isEmpty() || body.contains(" ")) {
+ return null;
+ }
+
+ try {
+ URL url = new URL(body);
+ if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
+ return null;
+ }
+
+ return url;
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
index 2e346486..0be1461b 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java
@@ -3,138 +3,35 @@ package de.thedevstack.conversationsplus.utils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusApplication;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.DownloadableFile;
import de.thedevstack.conversationsplus.entities.FileParams;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Transferable;
+import de.thedevstack.conversationsplus.entities.TransferablePlaceholder;
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.persistance.DatabaseBackend;
import de.thedevstack.conversationsplus.persistance.FileBackend;
+import de.thedevstack.conversationsplus.utils.messaging.MessageReceiptUtil;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+import de.tzur.conversations.Settings;
/**
* Utility class to work with messages.
*/
public final class MessageUtil {
- public static void extractFileParamsFromBody(Message message) {
- if (null == message) {
- return;
- }
-
- // Ensure that for every message the fileParams are set after calling this method
- FileParams fileParams = message.getFileParams();
- if (null == fileParams) {
- fileParams = new FileParams();
- message.setFileParams(fileParams);
- }
-
- String body = message.getBody();
- /**
- * there are a few cases where spaces result in an unwanted behavior, e.g.
- * "http://example.com/image.jpg" text that will not be shown /abc.png"
- * or more than one image link in one message.
- */
- if (null == body || body.isEmpty() || body.contains(" ")) {
- return;
- }
-
- try {
- URL url = new URL(body);
- if (!url.getProtocol().equalsIgnoreCase("http") && !url.getProtocol().equalsIgnoreCase("https")) {
- message.setTreatAsDownloadable(Message.Decision.NEVER);
- fileParams.setFileStatus(FileStatus.UNDEFINED);
- return;
- }
- String extension = FileUtils.getRelevantExtension(url);
- if (message.isHttpUploaded()) {
- fileParams.setUrl(url.toString());
- if (null != extension
- && (Transferable.WELL_KNOWN_EXTENSIONS.contains(extension.toLowerCase()) || Transferable.VALID_IMAGE_EXTENSIONS.contains(extension.toLowerCase()))) {
- message.setTreatAsDownloadable(Message.Decision.MUST);
- } else {
- message.setTreatAsDownloadable(Message.Decision.NEVER);
- fileParams.setFileStatus(FileStatus.UNDEFINED);
- }
-
- extractFilename(message, url.toString());
- return;
- }
-
- if (extension == null) {
- message.setTreatAsDownloadable(Message.Decision.NEVER);
- fileParams.setFileStatus(FileStatus.UNDEFINED);
- return;
- }
- byte[] ivAndKey = UrlUtil.getIvAndKeyFromURL(url);
-
- if (null != ivAndKey) {
- if (MimeUtils.guessMimeTypeFromExtension(extension) != null) {
- message.setTreatAsDownloadable(Message.Decision.MUST);
- fileParams.setKeyAndIv(ivAndKey);
- } else {
- message.setTreatAsDownloadable(Message.Decision.NEVER);
- fileParams.setFileStatus(FileStatus.UNDEFINED);
- }
- } else if (Transferable.VALID_IMAGE_EXTENSIONS.contains(extension)
- || Transferable.WELL_KNOWN_EXTENSIONS.contains(extension)) {
- message.setTreatAsDownloadable(Message.Decision.SHOULD);
- } else {
- message.setTreatAsDownloadable(Message.Decision.NEVER);
- fileParams.setFileStatus(FileStatus.UNDEFINED);
- }
-
- if (message.treatAsDownloadable() == Message.Decision.MUST
- || message.treatAsDownloadable() == Message.Decision.SHOULD) {
- fileParams.setUrl(url.toString());
- extractFilename(message, url.toString());
- }
- } catch (MalformedURLException e) {
- message.setTreatAsDownloadable(Message.Decision.NEVER);
- fileParams.setFileStatus(FileStatus.UNDEFINED);
- }
- }
-
- private static void extractFilename(Message message, String url) {
- String originalFilename = FileUtils.getFilenameFromPath(url);
- final String lowerCaseFilename = originalFilename.toLowerCase();
- final String lastPart = FileUtils.getLastExtension(lowerCaseFilename);
-
- detectAndSetEncryption(lastPart, message);
-
- String filenameExtension;
- if (!lastPart.isEmpty() && Transferable.VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
- filenameExtension = FileUtils.getSecondToLastExtension(lowerCaseFilename);
- originalFilename = originalFilename.replace("." + lastPart, "");
- } else {
- filenameExtension = lastPart;
- }
- message.setRelativeFilePath(message.getUuid() + "." + filenameExtension);
-
- message.getFileParams().setOriginalFilename(originalFilename);
- }
-
- private static void detectAndSetEncryption(String lastPart, Message message) {
- if (!lastPart.isEmpty() && ("pgp".equals(lastPart) || "gpg".equals(lastPart))) {
- message.setEncryption(Message.ENCRYPTION_PGP);
- } else if (message.getEncryption() != Message.ENCRYPTION_OTR
- && message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
- message.setEncryption(Message.ENCRYPTION_NONE);
- } else if ((message.getEncryption() == Message.ENCRYPTION_OTR
- || message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
- && message.getFileParams() != null && message.getFileParams().getKey() == null) {
- // If an encryption is set for the message, but no key given -> decryption not possible
- message.setEncryption(Message.ENCRYPTION_NONE);
- }
- }
-
/**
* Checks if an attached file is an image or not.
* Prerequisite for calling this method: The check if a file is attached is done before.
@@ -179,8 +76,8 @@ public final class MessageUtil {
public static boolean needsDownload(Message message) {
FileStatus fileStatus = (null != message.getFileParams()) ? message.getFileParams().getFileStatus() : null;
return (message.hasFileAttached() || MessageUtil.hasDownloadableLink(message))
- &&(null == fileStatus
- || (null != fileStatus && (fileStatus == FileStatus.NEEDS_DOWNLOAD || fileStatus == FileStatus.UNDEFINED)))
+ && (null == fileStatus
+ || (fileStatus == FileStatus.NEEDS_DOWNLOAD || fileStatus == FileStatus.UNDEFINED))
&& message.treatAsDownloadable() != Message.Decision.NEVER;
}
@@ -190,31 +87,75 @@ public final class MessageUtil {
&& null != url;
}
+ public static boolean isOutgoingMessage(Message message) {
+ return MessageDirection.OUT == message.getDirection();
+ }
+
+ public static boolean isIncomingMessage(Message message) {
+ return MessageDirection.IN == message.getDirection();
+ }
+
public static boolean isMessageSent(Message message) {
- switch (message.getStatus()) {
- case Message.STATUS_SEND:
- case Message.STATUS_SEND_DISPLAYED:
- case Message.STATUS_SEND_FAILED:
- case Message.STATUS_SEND_RECEIVED:
- return true;
- default:
- return false;
+ return MessageUtil.isOutgoingMessage(message)
+ && MessageStatus.TRANSMITTED == message.getMessageStatus();
+ }
+
+ public static boolean isMessageReceived(Message message) {
+ return MessageUtil.isIncomingMessage(message)
+ && MessageStatus.TRANSMITTED == message.getMessageStatus();
+ }
+
+ public static boolean isMessageTransmittedOrDisplayedOrReceived(Message message) {
+ MessageStatus status = message.getMessageStatus();
+ return MessageStatus.TRANSMITTED == status
+ || MessageStatus.RECEIVED == status
+ || MessageStatus.DISPLAYED == status;
+ }
+
+ public static void setAndSaveStatusForFileDeleted(Message message) {
+ message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
+ //FIXME: File Status needs to be changed
+ MessageStatus msgStatus = message.getMessageStatus();
+ if (MessageUtil.isOutgoingMessage(message)
+ && (MessageStatus.TRANSMITTING == msgStatus || MessageStatus.WAITING == msgStatus)) {
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.FAILED); // FIXME: add some information why this is failed
}
}
public static void setAndSaveFileStatus(Message message, FileStatus fileStatus) {
+ if (setFileStatus(message, fileStatus)) {
+ DatabaseBackend.getInstance().updateMessage(message);
+ UiUpdateHelper.updateConversationUi();
+ }
+ }
+
+ public static boolean setFileStatus(Message message, FileStatus fileStatus) {
message.getFileParams().setFileStatus(fileStatus);
- DatabaseBackend.getInstance().updateMessage(message);
- UiUpdateHelper.updateConversationUi();
+ if (FileStatus.DOWNLOAD_FAILED == fileStatus
+ || FileStatus.UPLOAD_FAILED == fileStatus) {
+ setMessageStatus(message, MessageStatus.FAILED);
+ } else if (FileStatus.DOWNLOADED == fileStatus) {
+ setMessageStatus(message, MessageStatus.TRANSMITTED);
+ }
+ return true;
+ }
+
+ public static boolean markMessageAsReceived(Account account, Jid from, String id) {
+ Message message = XmppConnectionServiceAccessor.xmppConnectionService.getMessage(account, from, id);
+ if (null != message) {
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.RECEIVED);
+ return true;
+ }
+ return false;
}
- public static boolean markMessage(Conversation conversation, String uuid, int status) {
+ public static boolean setAndSaveMessageStatus(Conversation conversation, String uuid, MessageStatus newStatus) {
if (uuid == null) {
return false;
} else {
Message message = conversation.findSentMessageWithUuid(uuid);
if (message != null) {
- markMessage(message, status);
+ setAndSaveMessageStatus(message, newStatus);
return true;
} else {
return false;
@@ -222,15 +163,38 @@ public final class MessageUtil {
}
}
- public static void markMessage(Message message, int status) {
- if (status == Message.STATUS_SEND_FAILED
- && (message.getStatus() == Message.STATUS_SEND_RECEIVED || message
- .getStatus() == Message.STATUS_SEND_DISPLAYED)) {
- return;
+ public static void setAndSaveMessageStatus(Message message, MessageStatus newStatus) {
+ if (setMessageStatus(message, newStatus)) {
+ DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateMessage(message);
+ UiUpdateHelper.updateConversationUi();
+ }
+ }
+
+ private static boolean sendReceipt(Message message) {
+ return Settings.CONFIRM_MESSAGE_RECEIVED // Only if user allows to send message confirmations
+ && isIncomingMessage(message) // Only if a message is incoming
+ && MessageConfirmation.NONE != message.getConfirmation() // Only if message contained an confirmation request
+ && message.getRemoteMsgId() != null // Only if there is an remote id
+ && !message.isMamReceived() // Only if it is not received using MAM
+ && !message.isCarbon() // Only if it is not received with carbons
+ && !message.isRead(); // Only if the message is not read yet
+ }
+
+ public static boolean setMessageStatus(Message message, MessageStatus newStatus) {
+ MessageStatus currentStatus = message.getMessageStatus();
+ if ((MessageStatus.FAILED == newStatus && (MessageStatus.RECEIVED == currentStatus || MessageStatus.DISPLAYED == currentStatus))) {
+ return false;
+ }
+ message.setMessageStatus(newStatus);
+ if (MessageStatus.TRANSMITTED == newStatus) {
+ if (sendReceipt(message)) {
+ Logging.d("message-util", "Send message receipt from setMessageStatus");
+ MessageReceiptUtil.sendMessageReceipts(message);
+ }
+ Logging.d("message-util", "Push notification for message");
+ XmppConnectionServiceAccessor.xmppConnectionService.getNotificationService().push(message);
}
- message.setStatus(status);
- DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateMessage(message);
- UiUpdateHelper.updateConversationUi();
+ return true;
}
public static boolean wasHighlightedOrPrivate(final Message message) {
@@ -302,6 +266,29 @@ public final class MessageUtil {
}
}
+ 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;
+ }
+
+ public static Message createOutgoingMessage(Conversation conversation, String body) {
+ int encryption = conversation.getNextEncryption();
+ if (encryption == Message.ENCRYPTION_PGP) {
+ encryption = Message.ENCRYPTION_DECRYPTED;
+ }
+ Message message = new Message(conversation, body, encryption);
+ message.setMessageStatus(MessageStatus.WAITING);
+ message.setDirection(MessageDirection.OUT);
+ if (conversation.getNextCounterpart() != null) {
+ message.setCounterpart(conversation.getNextCounterpart());
+ }
+
+ return message;
+ }
+
private MessageUtil() {
// Static helper class
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java
index d27481df..31089a62 100644
--- a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java
@@ -20,6 +20,7 @@ import de.thedevstack.conversationsplus.entities.Conversation;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Presence;
import de.thedevstack.conversationsplus.entities.Transferable;
+import de.thedevstack.conversationsplus.enums.FileStatus;
import de.thedevstack.conversationsplus.xmpp.jid.Jid;
public class UIHelper {
@@ -89,8 +90,8 @@ public class UIHelper {
return sameDay(date,new Date(System.currentTimeMillis()));
}
- public static boolean sameDay(long timestamp1, long timestamp2) {
- return sameDay(new Date(timestamp1),new Date(timestamp2));
+ public static boolean sameDay(long timestamp, long timestamp2) {
+ return sameDay(new Date(timestamp),new Date(timestamp2));
}
private static boolean sameDay(Date a, Date b) {
@@ -147,8 +148,7 @@ public class UIHelper {
getFileDescriptionString(context,message)),true);
case Transferable.STATUS_DOWNLOADING:
return new Pair<>(context.getString(R.string.receiving_x_file,
- getFileDescriptionString(context,message),
- d.getProgress()),true);
+ getFileDescriptionString(context,message)),true);
case Transferable.STATUS_OFFER:
case Transferable.STATUS_OFFER_CHECK_FILESIZE:
return new Pair<>(context.getString(R.string.x_file_offered_for_download,
@@ -158,7 +158,7 @@ public class UIHelper {
case Transferable.STATUS_FAILED:
return new Pair<>(context.getString(R.string.file_transmission_failed),true);
case Transferable.STATUS_UPLOADING:
- if (message.getStatus() == Message.STATUS_OFFERED) {
+ if (message.getFileParams().getFileStatus() == FileStatus.NEEDS_UPLOAD) {
return new Pair<>(context.getString(R.string.offering_x_file,
getFileDescriptionString(context, message)), true);
} else {
@@ -173,7 +173,7 @@ public class UIHelper {
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
return new Pair<>(context.getString(R.string.decryption_failed), true);
} else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
- if (message.getStatus() == Message.STATUS_RECEIVED) {
+ if (MessageUtil.isMessageReceived(message)) {// Todo: Check if Transmitted && Displayed && Received should be used here
return new Pair<>(context.getString(R.string.received_x_file,
getFileDescriptionString(context, message)), true);
} else {
@@ -183,7 +183,7 @@ public class UIHelper {
if (message.hasMeCommand()) {
return new Pair<>(message.getBodyReplacedMeCommand(UIHelper.getMessageDisplayName(message)), false);
} else if (GeoHelper.isGeoUri(message.getBody())) {
- if (message.getStatus() == Message.STATUS_RECEIVED) {
+ if (MessageUtil.isMessageReceived(message)) { // Todo: Check if Transmitted && Displayed && Received should be used here
return new Pair<>(context.getString(R.string.received_location), true);
} else {
return new Pair<>(context.getString(R.string.location), true);
@@ -233,7 +233,7 @@ public class UIHelper {
public static String getMessageDisplayName(final Message message) {
final Conversation conversation = message.getConversation();
- if (message.getStatus() == Message.STATUS_RECEIVED) {
+ if (MessageUtil.isIncomingMessage(message)) {
final Contact contact = message.getContact();
if (conversation.getMode() == Conversation.MODE_MULTI) {
if (contact != null) {
@@ -304,7 +304,7 @@ public class UIHelper {
public static boolean receivedLocationQuestion(Message message) {
if (message == null
- || message.getStatus() != Message.STATUS_RECEIVED
+ || MessageUtil.isOutgoingMessage(message)
|| message.getType() != Message.TYPE_TEXT) {
return false;
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/messaging/MessageReceiptUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/messaging/MessageReceiptUtil.java
new file mode 100644
index 00000000..a3ac1c3a
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/utils/messaging/MessageReceiptUtil.java
@@ -0,0 +1,41 @@
+package de.thedevstack.conversationsplus.utils.messaging;
+
+import java.util.ArrayList;
+
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Conversation;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.enums.MessageConfirmation;
+import de.thedevstack.conversationsplus.utils.XmppSendUtil;
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xmpp.chatmarkers.ChatMarkers;
+import de.thedevstack.conversationsplus.xmpp.receipts.MessageDeliveryReceipts;
+import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
+
+/**
+ */
+public final class MessageReceiptUtil {
+ public static void sendMessageReceipts(Message message) {
+ MessageConfirmation confirmation = message.getConfirmation();
+ ArrayList<Element> receipts = new ArrayList<>();
+ if (MessageConfirmation.DELIVERY_RECEIPT_CHAT_MARKERS == confirmation) {
+ receipts.add(ChatMarkers.RECEIVED.getXmlElement());
+ receipts.add(MessageDeliveryReceipts.RECEIVED.getXmlElement());
+ } else if (MessageConfirmation.CHAT_MARKERS == confirmation) {
+ receipts.add(ChatMarkers.RECEIVED.getXmlElement());
+ } else {
+ receipts.add(MessageDeliveryReceipts.RECEIVED.getXmlElement());
+ }
+
+ Account account = message.getConversation().getAccount();
+ MessagePacket receivedPacket = new MessagePacket();
+ receivedPacket.setType((message.getConversation().getMode() == Conversation.MODE_SINGLE ? MessagePacket.TYPE_CHAT : MessagePacket.TYPE_GROUPCHAT));
+ receivedPacket.setTo(message.getCounterpart());
+ receivedPacket.setFrom(account.getJid());
+ for (Element receipt : receipts) {
+ receivedPacket.addChild(receipt).setAttribute("id", message.getRemoteMsgId());
+ }
+
+ XmppSendUtil.sendMessagePacket(account, receivedPacket);
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xml/Element.java b/src/main/java/de/thedevstack/conversationsplus/xml/Element.java
index 23836365..0b8c2035 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xml/Element.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xml/Element.java
@@ -74,6 +74,22 @@ public class Element {
return null;
}
+ public Element findChildRecursive(String name, String xmlns) {
+ Element foundChild = null;
+ for (Element child : this.children) {
+ if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) {
+ foundChild = child;
+ break;
+ } else {
+ foundChild = findChildRecursive(name, xmlns);
+ if (null != foundChild) {
+ break;
+ }
+ }
+ }
+ return foundChild;
+ }
+
public Element findChild(XmlElementContainer container) {
Element element = container.getXmlElement();
return findChild(element.getName(), element.getNamespace());
@@ -92,9 +108,18 @@ public class Element {
return findChild(name, xmlns) != null;
}
- public boolean hasChild(XmlElementContainer container) {
+ public boolean hasChildRecursive(String name, String xmlns) {
+ return null != findChildRecursive(name, xmlns);
+ }
+
+ public boolean hasChildRecursive(XmlElementContainer container) {
+ Element element = container.getXmlElement();
+ return hasChildRecursive(element.getName(), element.getNamespace());
+ }
+
+ public boolean hasChild(XmlElementContainer container) {
Element element = container.getXmlElement();
- return hasChild(element.getName(), element.getNamespace());
+ return hasChild(element.getName(), element.getNamespace());
}
public List<Element> getChildren() {
diff --git a/src/main/java/de/thedevstack/conversationsplus/xml/XmlHelper.java b/src/main/java/de/thedevstack/conversationsplus/xml/XmlHelper.java
index 1287f73f..36a536aa 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xml/XmlHelper.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xml/XmlHelper.java
@@ -1,4 +1,4 @@
-package de.thedevstack.conversationsplus.utils;
+package de.thedevstack.conversationsplus.xml;
public class XmlHelper {
public static String encodeEntities(String content) {
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/Forwarded.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/Forwarded.java
new file mode 100644
index 00000000..1880d5e9
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/Forwarded.java
@@ -0,0 +1,26 @@
+package de.thedevstack.conversationsplus.xmpp;
+
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xml.XmlElementContainer;
+
+/**
+ */
+public enum Forwarded implements XmlElementContainer {
+ FORWARDED("forwarded");
+
+ private Element xmlElement;
+
+ Forwarded(String elementName) {
+ this.xmlElement = new Element(elementName, NAMESPACE);
+ }
+
+ @Override
+ public Element getXmlElement() {
+ return xmlElement;
+ }
+
+ /**
+ * The namespace of Forwarded....
+ */
+ public static final String NAMESPACE = "urn:xmpp:forward:0";
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/PacketReceived.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/PacketReceived.java
index 6274aaa2..de5e2b20 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/PacketReceived.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/PacketReceived.java
@@ -1,5 +1,5 @@
package de.thedevstack.conversationsplus.xmpp;
-public abstract interface PacketReceived {
+public interface PacketReceived {
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java
index 2b366dbb..f6e6edb8 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java
@@ -62,9 +62,11 @@ import de.thedevstack.conversationsplus.crypto.sasl.ScramSha1;
import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.generator.IqGenerator;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.utils.DNSHelper;
+import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.SSLSocketHelper;
import de.thedevstack.conversationsplus.utils.Xmlns;
import de.thedevstack.conversationsplus.xml.Element;
@@ -492,10 +494,10 @@ public class XmppConnection implements Runnable {
for(AbstractAcknowledgeableStanza packet : failedStanzas) {
if (packet instanceof MessagePacket) {
MessagePacket message = (MessagePacket) packet;
- mXmppConnectionService.markMessage(account,
+ Message msg = mXmppConnectionService.getMessage(account,
message.getTo().toBareJid(),
- message.getId(),
- Message.STATUS_UNSEND);
+ message.getId());
+ MessageUtil.setAndSaveMessageStatus(msg, MessageStatus.TRANSMITTING);
}
sendPacket(packet);
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkers.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkers.java
new file mode 100644
index 00000000..a1822b7d
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkers.java
@@ -0,0 +1,28 @@
+package de.thedevstack.conversationsplus.xmpp.chatmarkers;
+
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xml.XmlElementContainer;
+
+/**
+ * Representation of the ChatMarkers hint.
+ * <pre>
+ * <markable xmlns="urn:xmpp:chat-markers:0"/>
+ * </pre>
+ */
+public enum ChatMarkers implements XmlElementContainer {
+ MARKABLE("markable"),
+ RECEIVED("received"),
+ DISPLAYED("displayed"),
+ ACKNOWLEDGED("acknowledged");
+
+ private final Element xmlElement;
+
+ ChatMarkers(String elementName) {
+ this.xmlElement = new Element(elementName, ChatMarkersXep.NAMESPACE);
+ }
+
+ @Override
+ public Element getXmlElement() {
+ return xmlElement;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersHandler.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersHandler.java
new file mode 100644
index 00000000..a8277a04
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersHandler.java
@@ -0,0 +1,11 @@
+package de.thedevstack.conversationsplus.xmpp.chatmarkers;
+
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+
+/**
+ */
+public interface ChatMarkersHandler {
+ void messageReceived(Account account, Jid from, String messageId);
+ void messageDisplayed(Account account, Jid from, String messageId);
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersPacketHandler.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersPacketHandler.java
new file mode 100644
index 00000000..89fd945a
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersPacketHandler.java
@@ -0,0 +1,24 @@
+package de.thedevstack.conversationsplus.xmpp.chatmarkers;
+
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Message;
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket;
+
+/**
+ */
+public class ChatMarkersPacketHandler {
+ private ChatMarkersHandler handler;
+
+ public void handleMessagePacket(Account account, MessagePacket packet, Message message) {
+ message.markable = packet.hasChild("markable", ChatMarkersXep.NAMESPACE);
+ Element received = packet.findChild("received", ChatMarkersXep.NAMESPACE);
+ if (null != received && null != handler) {
+ handler.messageReceived(account, packet.getFrom().toBareJid(), received.getAttribute("id"));
+ }
+ Element displayed = packet.findChild("displayed", ChatMarkersXep.NAMESPACE);
+ if (null != displayed && null != handler) {
+ handler.messageDisplayed(account, packet.getFrom().toBareJid(), displayed.getAttribute("id"));
+ }
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersXep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersXep.java
new file mode 100644
index 00000000..58f3f66e
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/chatmarkers/ChatMarkersXep.java
@@ -0,0 +1,49 @@
+package de.thedevstack.conversationsplus.xmpp.chatmarkers;
+
+import de.thedevstack.conversationsplus.xmpp.AbstractXep;
+import de.thedevstack.conversationsplus.xmpp.IqPacketHandler;
+
+/**
+ */
+public class ChatMarkersXep extends AbstractXep {
+ public static final String NAMESPACE = "urn:xmpp:chat-markers:0";
+
+ public ChatMarkersXep(boolean enabled) {
+ super(enabled);
+ }
+
+ @Override
+ public String xepNumber() {
+ return "0333";
+ }
+
+ @Override
+ public String shortName() {
+ return "";
+ }
+
+ @Override
+ public String name() {
+ return "Chat Markers";
+ }
+
+ @Override
+ public String namespace() {
+ return NAMESPACE;
+ }
+
+ @Override
+ public String featureNamespace() {
+ return namespace();
+ }
+
+ @Override
+ public String elementName() {
+ return null;
+ }
+
+ @Override
+ public IqPacketHandler handler() {
+ return null;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java
index 1c266545..cb53e993 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java
@@ -34,7 +34,6 @@ public final class FeatureRegistry {
ChatState.NAMESPACE,
AxolotlService.PEP_DEVICE_LIST+"+notify"};
private static final String[] LEGACY_MESSAGE_CONFIRMATION_FEATURES = {
- "urn:xmpp:chat-markers:0",
"urn:xmpp:receipts"
};
private final List<String> features = new ArrayList<>();
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java
index 13fb5ad1..1330b18d 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/httpuploadim/HttpUploadHint.java
@@ -1,6 +1,7 @@
package de.thedevstack.conversationsplus.xmpp.httpuploadim;
import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xml.XmlElementContainer;
/**
* Representation of the HttpUploadHint.
@@ -8,18 +9,23 @@ import de.thedevstack.conversationsplus.xml.Element;
* <httpupload xmlns="urn:xmpp:hints"/>
* </pre>
*/
-public class HttpUploadHint extends Element {
+public enum HttpUploadHint implements XmlElementContainer {
+ HTTP_UPLOAD_HINT("httpupload");
+
+ private Element xmlElement;
+
+ HttpUploadHint(String elementName) {
+ this.xmlElement = new Element(elementName, NAMESPACE);
+ }
+
+ @Override
+ public Element getXmlElement() {
+ return xmlElement;
+ }
+
/**
* The namespace of message processing hints as defined in XEP-0334.
* @see <a href="http://xmpp.org/extensions/xep-0334.html">http://xmpp.org/extensions/xep-0334.html</a>
*/
public static final String NAMESPACE = "urn:xmpp:hints";
- /**
- * The element name of the hint for an http upload.
- */
- public static final String ELEMENT_NAME = "httpupload";
-
- public HttpUploadHint() {
- super(ELEMENT_NAME, NAMESPACE);
- }
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
index 49a10684..29e83c34 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnection.java
@@ -17,6 +17,8 @@ import java.util.concurrent.ConcurrentHashMap;
import de.thedevstack.android.logcat.Logging;
import de.thedevstack.conversationsplus.ConversationsPlusPreferences;
+import de.thedevstack.conversationsplus.enums.MessageDirection;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.utils.MessageUtil;
import de.thedevstack.conversationsplus.utils.StreamUtil;
import de.thedevstack.conversationsplus.Config;
@@ -105,7 +107,7 @@ public class JingleConnection implements Transferable {
sendSuccess();
MessageUtil.updateFileParams(message);
mXmppConnectionService.databaseBackend.createMessage(message);
- MessageUtil.markMessage(message,Message.STATUS_RECEIVED);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTED);
if (acceptedAutomatically) {
message.markUnread();
JingleConnection.this.mXmppConnectionService.getNotificationService().push(message);
@@ -297,7 +299,8 @@ public class JingleConnection implements Transferable {
.findOrCreateConversation(account,
packet.getFrom().toBareJid(), false);
this.message = new Message(conversation, "", Message.ENCRYPTION_NONE);
- this.message.setStatus(Message.STATUS_RECEIVED);
+ MessageUtil.setMessageStatus(this.message, MessageStatus.TRANSMITTING);
+ this.message.setDirection(MessageDirection.IN);
this.mStatus = Transferable.STATUS_OFFER;
this.message.setTransferable(this);
final Jid from = packet.getFrom();
@@ -458,7 +461,7 @@ public class JingleConnection implements Transferable {
if (packet.getType() == IqPacket.TYPE.RESULT) {
Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": other party received offer");
mJingleStatus = JINGLE_STATUS_INITIATED;
- MessageUtil.markMessage(message, Message.STATUS_OFFERED);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTING);
} else {
fail();
}
@@ -551,7 +554,7 @@ public class JingleConnection implements Transferable {
mergeCandidates(JingleCandidate.parse(content.socks5transport()
.getChildren()));
this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
- MessageUtil.markMessage(message, Message.STATUS_UNSEND);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.TRANSMITTING);
this.connectNextCandidate();
return true;
}
@@ -711,7 +714,7 @@ public class JingleConnection implements Transferable {
this.sendJinglePacket(packet);
this.disconnectSocks5Connections();
this.mJingleStatus = JINGLE_STATUS_FINISHED;
- this.message.setStatus(Message.STATUS_RECEIVED);
+ MessageUtil.setMessageStatus(message, MessageStatus.TRANSMITTED);
this.message.setTransferable(null);
this.mXmppConnectionService.updateMessage(message);
this.mJingleConnectionManager.finishConnection(this);
@@ -783,7 +786,7 @@ public class JingleConnection implements Transferable {
private void receiveSuccess() {
this.mJingleStatus = JINGLE_STATUS_FINISHED;
- MessageUtil.markMessage(this.message,Message.STATUS_SEND_RECEIVED);
+ MessageUtil.setAndSaveMessageStatus(this.message, MessageStatus.RECEIVED);
this.disconnectSocks5Connections();
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
this.transport.disconnect();
@@ -806,7 +809,7 @@ public class JingleConnection implements Transferable {
}
this.mXmppConnectionService.updateConversationUi();
} else {
- MessageUtil.markMessage(this.message, Message.STATUS_SEND_FAILED);
+ MessageUtil.setAndSaveMessageStatus(this.message, MessageStatus.CANCELED);
this.message.setTransferable(null);
}
}
@@ -827,7 +830,7 @@ public class JingleConnection implements Transferable {
}
this.mXmppConnectionService.updateConversationUi();
} else {
- MessageUtil.markMessage(this.message, Message.STATUS_SEND_FAILED);
+ MessageUtil.setAndSaveMessageStatus(this.message, MessageStatus.FAILED);
this.message.setTransferable(null);
}
}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java
index 30ed9024..46fa02b0 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/jingle/JingleConnectionManager.java
@@ -13,6 +13,7 @@ import de.thedevstack.conversationsplus.Config;
import de.thedevstack.conversationsplus.entities.Account;
import de.thedevstack.conversationsplus.entities.Message;
import de.thedevstack.conversationsplus.entities.Transferable;
+import de.thedevstack.conversationsplus.enums.MessageStatus;
import de.thedevstack.conversationsplus.services.AbstractConnectionManager;
import de.thedevstack.conversationsplus.services.XmppConnectionService;
import de.thedevstack.conversationsplus.utils.MessageUtil;
@@ -62,7 +63,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
old.cancel();
}
JingleConnection connection = new JingleConnection(this);
- MessageUtil.markMessage(message,Message.STATUS_WAITING);
+ MessageUtil.setAndSaveMessageStatus(message, MessageStatus.WAITING);
connection.init(message);
this.connections.add(connection);
return connection;
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/muc/MucPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/muc/MucPacketParser.java
new file mode 100644
index 00000000..d7ec6b38
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/muc/MucPacketParser.java
@@ -0,0 +1,62 @@
+package de.thedevstack.conversationsplus.xmpp.muc;
+
+import de.thedevstack.conversationsplus.entities.Account;
+import de.thedevstack.conversationsplus.entities.Conversation;
+import de.thedevstack.conversationsplus.persistance.DatabaseBackend;
+import de.thedevstack.conversationsplus.utils.UiUpdateHelper;
+import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor;
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xmpp.jid.Jid;
+
+/**
+ */
+public class MucPacketParser {
+ private static class Invite {
+ Jid jid;
+ String password;
+ Invite(Jid jid, String password) {
+ this.jid = jid;
+ this.password = password;
+ }
+
+ public boolean execute(Account account) {
+ if (jid != null) {
+ Conversation conversation = XmppConnectionServiceAccessor.xmppConnectionService.findOrCreateConversation(account, jid, true);
+ if (!conversation.getMucOptions().online()) {
+ conversation.getMucOptions().setPassword(password);
+ DatabaseBackend.getInstance().updateConversation(conversation);
+ XmppConnectionServiceAccessor.xmppConnectionService.joinMuc(conversation);
+ UiUpdateHelper.updateConversationUi();
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private static Invite extractInvite(Element message) {
+ Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
+ if (x != null) {
+ Element invite = x.findChild("invite");
+ if (invite != null) {
+ Element pw = x.findChild("password");
+ return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent(): null);
+ }
+ } else {
+ x = message.findChild("x","jabber:x:conference");
+ if (x != null) {
+ return new Invite(x.getAttributeAsJid("jid"),x.getAttribute("password"));
+ }
+ }
+ return null;
+ }
+
+ public static boolean extractAndExecuteInvite(Account account, Element packet) {
+ Invite invite = extractInvite(packet);
+ if (invite != null && invite.execute(account)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/receipts/MessageDeliveryReceipts.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/receipts/MessageDeliveryReceipts.java
new file mode 100644
index 00000000..1bc86e37
--- /dev/null
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/receipts/MessageDeliveryReceipts.java
@@ -0,0 +1,24 @@
+package de.thedevstack.conversationsplus.xmpp.receipts;
+
+import de.thedevstack.conversationsplus.xml.Element;
+import de.thedevstack.conversationsplus.xml.XmlElementContainer;
+
+/**
+ */
+public enum MessageDeliveryReceipts implements XmlElementContainer {
+ REQUEST("request"),
+ RECEIVED("received");
+
+ public static final String NAMESPACE = "urn:xmpp:receipts";
+
+ private final Element xmlElement;
+
+ MessageDeliveryReceipts(String elementName) {
+ this.xmlElement = new Element(elementName, NAMESPACE);
+ }
+
+ @Override
+ public Element getXmlElement() {
+ return xmlElement;
+ }
+}
diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java
index e14cdacd..5e9d5a9d 100644
--- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java
+++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/MessagePacket.java
@@ -84,7 +84,7 @@ public class MessagePacket extends AbstractAcknowledgeableStanza {
return null;
}
Long timestamp = AbstractParser.getTimestamp(forwarded,null);
- return new Pair(packet,timestamp);
+ return new Pair<>(packet,timestamp);
}
public static MessagePacket create(Element element) {
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index b762f420..3340bb05 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -364,7 +364,7 @@
<string name="pref_export_logs_summary">Chats auf SD-Karte schreiben</string>
<string name="notification_export_logs_title">Chats werden auf SD-Karte geschrieben</string>
<string name="choose_file">Datei auswählen</string>
- <string name="receiving_x_file">Empfange %1$s (%2$d%% abgeschlossen)</string>
+ <string name="receiving_x_file">Empfange %1$s</string>
<string name="download_x_file">%s herunterladen</string>
<string name="delete_x_file">%s löschen</string>
<string name="file">Datei</string>
@@ -648,4 +648,5 @@
<string name="pref_software_info">Send client information</string>
<string name="pref_send_os_info_summary">Sends information about your android version</string>
<string name="pref_send_os_info">Send your android version</string>
+ <string name="receiving_file">Empfange (%1$d%% abgeschlossen)</string>
</resources>
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 8e9a1dee..52af529d 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -399,7 +399,7 @@
<string name="pref_export_logs_summary">Write logs to SD card</string>
<string name="notification_export_logs_title">Writing logs to SD card</string>
<string name="choose_file">Choose file</string>
- <string name="receiving_x_file">Receiving %1$s (%2$d%% completed)</string>
+ <string name="receiving_x_file">Receiving %1$s</string>
<string name="download_x_file">Download %s</string>
<string name="delete_x_file">Delete %s</string>
<string name="file">file</string>
@@ -562,7 +562,7 @@
<string name="dlg_msg_details_msg_status_sent">Sent</string>
<string name="dlg_msg_details_msg_status_received">Received</string>
<string name="dlg_msg_details_msg_status_waiting">Waiting</string>
- <string name="dlg_msg_details_msg_status_unsend">Unsend</string>
+ <string name="dlg_msg_details_msg_status_unsend">Sending</string>
<string name="dlg_msg_details_msg_status_offered">Offered</string>
<string name="dlg_msg_details_msg_status_failed">Failed</string>
<string name="pref_resize_picture_ask">ask</string>
@@ -700,4 +700,7 @@
<string name="pref_send_os_info_summary">Sends information about your android version</string>
<string name="pref_send_os_info">Send your android version</string>
<string name="send_canceled">delivery canceled</string>
+ <string name="receiving_file">receiving (%1$d%% completed)</string>
+ <string name="dlg_msg_details_msg_status_receiving">Receiving</string>
+ <string name="receiving">Receiving...</string>
</resources>