From 34558cc2779e5fd3cb8a2afa39be0efcc5314d88 Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Thu, 4 Dec 2014 16:20:28 +0100 Subject: [PATCH 1/7] store last message received date in conversation --- .../conversations/entities/Conversation.java | 9 +++ .../generator/AbstractGenerator.java | 10 ++++ .../conversations/parser/AbstractParser.java | 58 ++++++++----------- .../conversations/parser/MessageParser.java | 3 +- .../services/XmppConnectionService.java | 24 ++------ 5 files changed, 49 insertions(+), 55 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index a7da0bc29..725ed27b6 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -43,6 +43,7 @@ public class Conversation extends AbstractEntity { public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; + public static final String ATTRIBUTE_LAST_MESSAGE_RECEIVED = "last_message_received"; private String name; private String contactUuid; @@ -470,6 +471,14 @@ public class Conversation extends AbstractEntity { } } + public void setLastMessageReceived(long value) { + this.setAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED, String.valueOf(value)); + } + + public long getLastMessageReceived() { + return getLongAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED,0); + } + public void setMutedTill(long value) { this.setAttribute(ATTRIBUTE_MUTED_TILL, String.valueOf(value)); } diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index f46e7ba42..b200b2a11 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -4,9 +4,12 @@ import android.util.Base64; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.TimeZone; import eu.siacs.conversations.services.XmppConnectionService; @@ -23,6 +26,8 @@ public abstract class AbstractGenerator { public final String IDENTITY_NAME = "Conversations 0.9.3"; public final String IDENTITY_TYPE = "phone"; + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + protected XmppConnectionService mXmppConnectionService; protected AbstractGenerator(XmppConnectionService service) { @@ -46,4 +51,9 @@ public abstract class AbstractGenerator { byte[] sha1 = md.digest(s.toString().getBytes()); return new String(Base64.encode(sha1, Base64.DEFAULT)).trim(); } + + public static String getTimestamp(long time) { + DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + return DATE_FORMAT.format(time); + } } diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java index eedfca16e..c80346b7d 100644 --- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java +++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java @@ -24,40 +24,31 @@ public abstract class AbstractParser { protected long getTimestamp(Element packet) { long now = System.currentTimeMillis(); - ArrayList stamps = new ArrayList<>(); - for (Element child : packet.getChildren()) { - if (child.getName().equals("delay")) { - stamps.add(child.getAttribute("stamp").replace("Z", "+0000")); - } - } - Collections.sort(stamps); - if (stamps.size() >= 1) { - try { - String stamp = stamps.get(stamps.size() - 1); - if (stamp.contains(".")) { - Date date = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US) - .parse(stamp); - if (now < date.getTime()) { - return now; - } else { - return date.getTime(); - } - } else { - Date date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", - Locale.US).parse(stamp); - if (now < date.getTime()) { - return now; - } else { - return date.getTime(); - } - } - } catch (ParseException e) { - return now; - } - } else { + Element delay = packet.findChild("delay"); + if (delay == null) { return now; } + String stamp = delay.getAttribute("stamp"); + if (stamp == null) { + return now; + } + try { + long time = parseTimestamp(stamp).getTime(); + return now < time ? now : time; + } catch (ParseException e) { + return now; + } + } + + public static Date parseTimestamp(String timestamp) throws ParseException { + timestamp = timestamp.replace("Z", "+0000"); + SimpleDateFormat dateFormat; + if (timestamp.contains(".")) { + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); + } else { + dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ",Locale.US); + } + return dateFormat.parse(timestamp); } protected void updateLastseen(final Element packet, final Account account, @@ -66,8 +57,7 @@ public abstract class AbstractParser { try { from = Jid.fromString(packet.getAttribute("from")).toBareJid(); } catch (final InvalidJidException e) { - // TODO: Handle this? - from = null; + return; } String presence = from == null || from.isBareJid() ? "" : from.getResourcepart(); Contact contact = account.getRoster().getContact(from); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3ae82e48c..fd9e1b6c0 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -14,7 +14,6 @@ import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnMessagePacketReceived; -import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.MessagePacket; @@ -487,6 +486,8 @@ public class MessageParser extends AbstractParser implements } Conversation conversation = message.getConversation(); conversation.add(message); + conversation.setLastMessageReceived(System.currentTimeMillis()); + mXmppConnectionService.updateConversation(conversation); if (message.getStatus() == Message.STATUS_RECEIVED && conversation.getOtrSession() != null diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 2f44375ec..b9da4a81c 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -32,20 +32,14 @@ import net.java.otr4j.session.SessionStatus; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStream; import java.math.BigInteger; import java.security.SecureRandom; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Locale; -import java.util.TimeZone; import java.util.concurrent.CopyOnWriteArrayList; import de.duenndns.ssl.MemorizingTrustManager; @@ -1251,27 +1245,17 @@ public class XmppConnectionService extends Service { PresencePacket packet = new PresencePacket(); packet.setFrom(conversation.getAccount().getJid()); packet.setTo(joinJid); - Element x = new Element("x"); - x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + Element x = packet.addChild("x","http://jabber.org/protocol/muc"); if (conversation.getMucOptions().getPassword() != null) { - Element password = x.addChild("password"); - password.setContent(conversation.getMucOptions().getPassword()); + x.addChild("password").setContent(conversation.getMucOptions().getPassword()); } + x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageReceived())); String sig = account.getPgpSignature(); if (sig != null) { packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } - if (conversation.getMessages().size() != 0) { - final SimpleDateFormat mDateFormat = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); - mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - Date date = new Date(conversation.getLatestMessage() - .getTimeSent() + 1000); - x.addChild("history").setAttribute("since", - mDateFormat.format(date)); - } - packet.addChild(x); + Log.d(Config.LOGTAG,packet.toString()); sendPresencePacket(account, packet); if (!joinJid.equals(conversation.getContactJid())) { conversation.setContactJid(joinJid); From 4a94389f052afab8b0424bd56af3e0741a9ee430 Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Fri, 5 Dec 2014 01:54:16 +0100 Subject: [PATCH 2/7] very basic mam support --- .../conversations/entities/Bookmark.java | 4 +- .../conversations/generator/IqGenerator.java | 18 +++ .../conversations/parser/MessageParser.java | 66 ++++++++- .../services/MessageArchiveService.java | 137 ++++++++++++++++++ .../services/XmppConnectionService.java | 30 +++- .../eu/siacs/conversations/xml/Element.java | 5 + .../OnAdvancedStreamFeaturesAvailable.java | 7 + .../conversations/xmpp/XmppConnection.java | 8 + .../siacs/conversations/xmpp/forms/Data.java | 10 ++ 9 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/services/MessageArchiveService.java create mode 100644 src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java diff --git a/src/main/java/eu/siacs/conversations/entities/Bookmark.java b/src/main/java/eu/siacs/conversations/entities/Bookmark.java index 559e2f2dd..70d852fe8 100644 --- a/src/main/java/eu/siacs/conversations/entities/Bookmark.java +++ b/src/main/java/eu/siacs/conversations/entities/Bookmark.java @@ -102,9 +102,7 @@ public class Bookmark extends Element implements ListItem { } public boolean autojoin() { - String autojoin = this.getAttribute("autojoin"); - return (autojoin != null && (autojoin.equalsIgnoreCase("true") || autojoin - .equalsIgnoreCase("1"))); + return this.getAttributeAsBoolean("autojoin"); } public String getPassword() { diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 5d674748a..56a0776f0 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -4,8 +4,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; @@ -94,4 +96,20 @@ public class IqGenerator extends AbstractGenerator { } return packet; } + + public IqPacket queryMessageArchiveManagement(MessageArchiveService.Query mam) { + final IqPacket packet = new IqPacket(IqPacket.TYPE_SET); + Element query = packet.query("urn:xmpp:mam:0"); + query.setAttribute("queryid",mam.getQueryId()); + Data data = new Data(); + data.setFormType("urn:xmpp:mam:0"); + data.put("with",mam.getWith().toString()); + data.put("start",getTimestamp(mam.getStart())); + data.put("end",getTimestamp(mam.getEnd())); + query.addChild(data); + if (mam.getAfter() != null) { + query.addChild("set", "http://jabber.org/protocol/rsm").addChild("after").setContent(mam.getAfter()); + } + return packet; + } } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index fd9e1b6c0..b902db510 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -272,6 +272,58 @@ public class MessageParser extends AbstractParser implements return finishedMessage; } + private Message parseMamMessage(MessagePacket packet, final Account account) { + final Element result = packet.findChild("result","urn:xmpp:mam:0"); + if (result == null ) { + return null; + } + final Element forwarded = result.findChild("forwarded","urn:xmpp:forward:0"); + if (forwarded == null) { + return null; + } + final Element message = forwarded.findChild("message"); + if (message == null) { + return null; + } + final Element body = message.findChild("body"); + if (body == null || message.hasChild("private","urn:xmpp:carbons:2") || message.hasChild("no-copy","urn:xmpp:hints")) { + return null; + } + int encryption; + String content = getPgpBody(message); + if (content != null) { + encryption = Message.ENCRYPTION_PGP; + } else { + encryption = Message.ENCRYPTION_NONE; + content = body.getContent(); + } + if (content == null) { + return null; + } + final long timestamp = getTimestamp(forwarded); + final Jid to = message.getAttributeAsJid("to"); + final Jid from = message.getAttributeAsJid("from"); + Jid counterpart; + int status; + Conversation conversation; + if (from!=null && to != null && from.toBareJid().equals(account.getJid().toBareJid())) { + status = Message.STATUS_SEND; + conversation = this.mXmppConnectionService.findOrCreateConversation(account,to.toBareJid(),false); + counterpart = to; + } else if (from !=null && to != null) { + status = Message.STATUS_RECEIVED; + conversation = this.mXmppConnectionService.findOrCreateConversation(account,from.toBareJid(),false); + counterpart = from; + } else { + return null; + } + Message finishedMessage = new Message(conversation,content,encryption,status); + finishedMessage.setTime(timestamp); + finishedMessage.setCounterpart(counterpart); + Log.d(Config.LOGTAG,"received mam message "+content); + return finishedMessage; + } + private void parseError(final MessagePacket packet, final Account account) { final Jid from = packet.getFrom(); mXmppConnectionService.markMessage(account, from.toBareJid(), @@ -445,6 +497,17 @@ public class MessageParser extends AbstractParser implements message.markUnread(); } } + } else if (packet.hasChild("result","urn:xmpp:mam:0")) { + message = parseMamMessage(packet, account); + if (message != null) { + Conversation conversation = message.getConversation(); + conversation.add(message); + mXmppConnectionService.databaseBackend.createMessage(message); + } + return; + } else if (packet.hasChild("fin","urn:xmpp:mam:0")) { + Element fin = packet.findChild("fin","urn:xmpp:mam:0"); + mXmppConnectionService.getMessageArchiveService().processFin(fin); } else { parseNonMessage(packet, account); } @@ -493,7 +556,6 @@ public class MessageParser extends AbstractParser implements && conversation.getOtrSession() != null && !conversation.getOtrSession().getSessionID().getUserID() .equals(message.getCounterpart().getResourcepart())) { - Log.d(Config.LOGTAG, "ending because of reasons"); conversation.endOtrIfNeeded(); } @@ -506,7 +568,7 @@ public class MessageParser extends AbstractParser implements if (message.trusted() && message.bodyContainsDownloadable()) { this.mXmppConnectionService.getHttpConnectionManager() .createNewConnection(message); - } else { + } else if (!message.isRead()) { mXmppConnectionService.getNotificationService().push(message); } mXmppConnectionService.updateConversationUi(); diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java new file mode 100644 index 000000000..4f47cdbe4 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -0,0 +1,137 @@ +package eu.siacs.conversations.services; + +import android.util.Log; + +import java.math.BigInteger; +import java.util.HashSet; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.jid.Jid; +import eu.siacs.conversations.xmpp.stanzas.IqPacket; + +public class MessageArchiveService { + + private final XmppConnectionService mXmppConnectionService; + + private final HashSet queries = new HashSet(); + + public MessageArchiveService(final XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + public void query(final Conversation conversation) { + synchronized (this.queries) { + final Account account = conversation.getAccount(); + long start = conversation.getLastMessageReceived(); + long end = account.getXmppConnection().getLastSessionEstablished(); + final Query query = new Query(conversation, start, end); + this.queries.add(query); + IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG, packet.toString()); + } + }); + } + } + + public void processFin(Element fin) { + if (fin == null) { + return; + } + Query query = findQuery(fin.getAttribute("queryid")); + if (query == null) { + return; + } + Log.d(Config.LOGTAG,"fin "+fin.toString()); + boolean complete = fin.getAttributeAsBoolean("complete"); + Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); + Element last = set == null ? null : set.findChild("last"); + if (complete || last == null) { + Log.d(Config.LOGTAG,"completed mam query for "+query.getWith().toString()); + synchronized (this.queries) { + this.queries.remove(query); + } + } else { + Query nextQuery = query.next(last == null ? null : last.getContent()); + IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(nextQuery); + synchronized (this.queries) { + this.queries.remove(query); + this.queries.add(nextQuery); + } + Log.d(Config.LOGTAG,packet.toString()); + this.mXmppConnectionService.sendIqPacket(query.getConversation().getAccount(),packet,new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Log.d(Config.LOGTAG,packet.toString()); + } + }); + } + } + + private Query findQuery(String id) { + if (id == null) { + return null; + } + synchronized (this.queries) { + for(Query query : this.queries) { + if (query.getQueryId().equals(id)) { + return query; + } + } + return null; + } + } + + public class Query { + private long start; + private long end; + private Jid with; + private String queryId; + private String after = null; + private Conversation conversation; + + public Query(Conversation conversation, long start, long end) { + this.conversation = conversation; + this.with = conversation.getContactJid().toBareJid(); + this.start = start; + this.end = end; + this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); + } + + public Query next(String after) { + Query query = new Query(this.conversation,this.start,this.end); + query.after = after; + return query; + } + + public String getAfter() { + return after; + } + + public String getQueryId() { + return queryId; + } + + public Jid getWith() { + return with; + } + + public long getStart() { + return start; + } + + public long getEnd() { + return end; + } + + public Conversation getConversation() { + return conversation; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index b9da4a81c..9d73868c8 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -73,6 +73,7 @@ import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PRNGFixes; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesAvailable; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; @@ -141,6 +142,7 @@ public class XmppConnectionService extends Service { private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager( this); private AvatarService mAvatarService = new AvatarService(this); + private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); private OnConversationUpdate mOnConversationUpdate = null; private Integer convChangedListenerCount = 0; private OnAccountUpdate mOnAccountUpdate = null; @@ -203,6 +205,12 @@ public class XmppConnectionService extends Service { getNotificationService().updateErrorNotification(); } }; + private OnAdvancedStreamFeaturesAvailable onAdvancedStreamFeaturesAvailable = new OnAdvancedStreamFeaturesAvailable() { + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + queryMessagesFromArchive(account); + } + }; private int accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; private int rosterChangedListenerCount = 0; @@ -583,8 +591,8 @@ public class XmppConnectionService extends Service { connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); - connection - .setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); + connection.setOnAdvancedStreamFeaturesAvailableListener(this.onAdvancedStreamFeaturesAvailable); return connection; } @@ -1231,6 +1239,19 @@ public class XmppConnectionService extends Service { } } + private void queryMessagesFromArchive(final Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + List conversations = getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) { + this.mMessageArchiveService.query(conversation); + } + } + } else { + Log.d(Config.LOGTAG,"no mam available"); + } + } + public void joinMuc(Conversation conversation) { Account account = conversation.getAccount(); account.pendingConferenceJoins.remove(conversation); @@ -1255,7 +1276,6 @@ public class XmppConnectionService extends Service { packet.addChild("status").setContent("online"); packet.addChild("x", "jabber:x:signed").setContent(sig); } - Log.d(Config.LOGTAG,packet.toString()); sendPresencePacket(account, packet); if (!joinJid.equals(conversation.getContactJid())) { conversation.setContactJid(joinJid); @@ -2033,6 +2053,10 @@ public class XmppConnectionService extends Service { return this.mJingleConnectionManager; } + public MessageArchiveService getMessageArchiveService() { + return this.mMessageArchiveService; + } + public List findContacts(Jid jid) { ArrayList contacts = new ArrayList<>(); for (Account account : getAccounts()) { diff --git a/src/main/java/eu/siacs/conversations/xml/Element.java b/src/main/java/eu/siacs/conversations/xml/Element.java index 02c3e6951..c25b90175 100644 --- a/src/main/java/eu/siacs/conversations/xml/Element.java +++ b/src/main/java/eu/siacs/conversations/xml/Element.java @@ -159,4 +159,9 @@ public class Element { public void setAttribute(String name, int value) { this.setAttribute(name, Integer.toString(value)); } + + public boolean getAttributeAsBoolean(String name) { + String attr = getAttribute(name); + return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1"))); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java new file mode 100644 index 000000000..a41bce86a --- /dev/null +++ b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java @@ -0,0 +1,7 @@ +package eu.siacs.conversations.xmpp; + +import eu.siacs.conversations.entities.Account; + +public interface OnAdvancedStreamFeaturesAvailable { + public void onAdvancedStreamFeaturesAvailable(final Account account); +} diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index adb96fa25..0e5d26ed5 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -107,6 +107,7 @@ public class XmppConnection implements Runnable { private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; private OnBindListener bindListener = null; + private OnAdvancedStreamFeaturesAvailable advancedStreamFeaturesAvailableListener = null; private OnMessageAcknowledged acknowledgedListener = null; private XmppConnectionService mXmppConnectionService = null; @@ -771,6 +772,9 @@ public class XmppConnection implements Runnable { if (account.getServer().equals(server.toDomainJid())) { enableAdvancedStreamFeatures(); + if (advancedStreamFeaturesAvailableListener != null) { + advancedStreamFeaturesAvailableListener.onAdvancedStreamFeaturesAvailable(account); + } } } }); @@ -943,6 +947,10 @@ public class XmppConnection implements Runnable { this.acknowledgedListener = listener; } + public void setOnAdvancedStreamFeaturesAvailableListener(OnAdvancedStreamFeaturesAvailable listener) { + this.advancedStreamFeaturesAvailableListener = listener; + } + public void disconnect(boolean force) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": disconnecting"); try { diff --git a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java index ff9acb3f5..44794c80c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java +++ b/src/main/java/eu/siacs/conversations/xmpp/forms/Data.java @@ -37,6 +37,7 @@ public class Data extends Element { Field field = getFieldByName(name); if (field == null) { field = new Field(name); + this.addChild(field); } field.setValue(value); } @@ -45,6 +46,7 @@ public class Data extends Element { Field field = getFieldByName(name); if (field == null) { field = new Field(name); + this.addChild(field); } field.setValues(values); } @@ -72,4 +74,12 @@ public class Data extends Element { data.setChildren(element.getChildren()); return data; } + + public void setFormType(String formType) { + this.put("FORM_TYPE",formType); + } + + public String getFormType() { + return this.getAttribute("FORM_TYPE"); + } } From 0ab530932ae559d9b2a0dd8cbc57530fa8552319 Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Mon, 8 Dec 2014 21:59:14 +0100 Subject: [PATCH 3/7] added max history age (default 1w). automatically sort newly added mam messages --- .../java/eu/siacs/conversations/Config.java | 2 + .../entities/AbstractEntity.java | 1 - .../conversations/entities/Conversation.java | 39 ++++++++++++++- .../siacs/conversations/entities/Message.java | 5 ++ .../conversations/parser/MessageParser.java | 7 ++- .../services/MessageArchiveService.java | 48 +++++++++++++++---- .../services/XmppConnectionService.java | 25 ++-------- ...va => OnAdvancedStreamFeaturesLoaded.java} | 2 +- .../conversations/xmpp/XmppConnection.java | 16 +++++-- 9 files changed, 103 insertions(+), 42 deletions(-) rename src/main/java/eu/siacs/conversations/xmpp/{OnAdvancedStreamFeaturesAvailable.java => OnAdvancedStreamFeaturesLoaded.java} (75%) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index d777e5cc0..e9e73db9e 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -22,6 +22,8 @@ public final class Config { public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb + public static final long MAX_HISTORY_AGE = 7 * 24 * 60 * 60 * 1000; + private Config() { } diff --git a/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java index 92b8a7298..957b0a146 100644 --- a/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java +++ b/src/main/java/eu/siacs/conversations/entities/AbstractEntity.java @@ -17,5 +17,4 @@ public abstract class AbstractEntity { public boolean equals(AbstractEntity entity) { return this.getUuid().equals(entity.getUuid()); } - } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 725ed27b6..63f341e72 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -16,6 +16,8 @@ import org.json.JSONObject; import java.security.interfaces.DSAPublicKey; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import eu.siacs.conversations.xmpp.jid.InvalidJidException; @@ -471,12 +473,25 @@ public class Conversation extends AbstractEntity { } } - public void setLastMessageReceived(long value) { + public boolean setLastMessageReceived(long value) { + long before = getLastMessageReceived(); this.setAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED, String.valueOf(value)); + return (value - before > 1000); } public long getLastMessageReceived() { - return getLongAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED,0); + long timestamp = getLongAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED,0); + if (timestamp == 0) { + 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) { + return message.getTimeSent(); + } + } + } + } + return timestamp; } public void setMutedTill(long value) { @@ -544,6 +559,26 @@ public class Conversation extends AbstractEntity { } } + public void sort() { + synchronized (this.messages) { + for(Message message : this.messages) { + message.untie(); + } + Collections.sort(this.messages,new Comparator() { + @Override + public int compare(Message left, Message right) { + if (left.getTimeSent() < right.getTimeSent()) { + return -1; + } else if (left.getTimeSent() > right.getTimeSent()) { + return 1; + } else { + return 0; + } + } + }); + } + } + public class Smp { public static final int STATUS_NONE = 0; public static final int STATUS_CONTACT_REQUESTED = 1; diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 47861d06d..1213f66ab 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -493,6 +493,11 @@ public class Message extends AbstractEntity { } } + public void untie() { + this.mNextMessage = null; + this.mPreviousMessage = null; + } + public class ImageParams { public URL url; public long size = 0; diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index b902db510..3714ac904 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -549,8 +549,11 @@ public class MessageParser extends AbstractParser implements } Conversation conversation = message.getConversation(); conversation.add(message); - conversation.setLastMessageReceived(System.currentTimeMillis()); - mXmppConnectionService.updateConversation(conversation); + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) { + if (conversation.setLastMessageReceived(System.currentTimeMillis())) { + mXmppConnectionService.updateConversation(conversation); + } + } if (message.getStatus() == Message.STATUS_RECEIVED && conversation.getOtrSession() != null diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 4f47cdbe4..c77262cb4 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -4,16 +4,18 @@ import android.util.Log; import java.math.BigInteger; import java.util.HashSet; +import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.IqPacket; -public class MessageArchiveService { +public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private final XmppConnectionService mXmppConnectionService; @@ -28,18 +30,31 @@ public class MessageArchiveService { final Account account = conversation.getAccount(); long start = conversation.getLastMessageReceived(); long end = account.getXmppConnection().getLastSessionEstablished(); + if (end - start >= Config.MAX_HISTORY_AGE) { + start = end - Config.MAX_HISTORY_AGE; + } final Query query = new Query(conversation, start, end); this.queries.add(query); IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, packet.toString()); + if (packet.getType() == IqPacket.TYPE_ERROR) { + finalizeQuery(query); + } } }); } } + private void finalizeQuery(Query query) { + synchronized (this.queries) { + this.queries.remove(query); + } + query.getConversation().sort(); + this.mXmppConnectionService.updateConversationUi(); + } + public void processFin(Element fin) { if (fin == null) { return; @@ -48,27 +63,26 @@ public class MessageArchiveService { if (query == null) { return; } - Log.d(Config.LOGTAG,"fin "+fin.toString()); boolean complete = fin.getAttributeAsBoolean("complete"); Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); Element last = set == null ? null : set.findChild("last"); if (complete || last == null) { - Log.d(Config.LOGTAG,"completed mam query for "+query.getWith().toString()); - synchronized (this.queries) { - this.queries.remove(query); - } + final Account account = query.getConversation().getAccount(); + Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": completed mam query for "+query.getWith().toString()); + this.finalizeQuery(query); } else { - Query nextQuery = query.next(last == null ? null : last.getContent()); + final Query nextQuery = query.next(last == null ? null : last.getContent()); IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(nextQuery); synchronized (this.queries) { this.queries.remove(query); this.queries.add(nextQuery); } - Log.d(Config.LOGTAG,packet.toString()); this.mXmppConnectionService.sendIqPacket(query.getConversation().getAccount(),packet,new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG,packet.toString()); + if (packet.getType() == IqPacket.TYPE_ERROR) { + finalizeQuery(nextQuery); + } } }); } @@ -88,6 +102,20 @@ public class MessageArchiveService { } } + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + List conversations = mXmppConnectionService.getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) { + this.query(conversation); + } + } + } else { + Log.d(Config.LOGTAG,"no mam available"); + } + } + public class Query { private long start; private long end; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 9d73868c8..3e2c1b8b5 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -73,7 +73,7 @@ import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PRNGFixes; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesAvailable; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; @@ -205,12 +205,7 @@ public class XmppConnectionService extends Service { getNotificationService().updateErrorNotification(); } }; - private OnAdvancedStreamFeaturesAvailable onAdvancedStreamFeaturesAvailable = new OnAdvancedStreamFeaturesAvailable() { - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - queryMessagesFromArchive(account); - } - }; + private int accountChangedListenerCount = 0; private OnRosterUpdate mOnRosterUpdate = null; private int rosterChangedListenerCount = 0; @@ -592,7 +587,7 @@ public class XmppConnectionService extends Service { connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); - connection.setOnAdvancedStreamFeaturesAvailableListener(this.onAdvancedStreamFeaturesAvailable); + connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); return connection; } @@ -1027,6 +1022,7 @@ public class XmppConnectionService extends Service { } this.databaseBackend.createConversation(conversation); } + this.mMessageArchiveService.query(conversation); this.conversations.add(conversation); updateConversationUi(); return conversation; @@ -1239,19 +1235,6 @@ public class XmppConnectionService extends Service { } } - private void queryMessagesFromArchive(final Account account) { - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { - List conversations = getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) { - this.mMessageArchiveService.query(conversation); - } - } - } else { - Log.d(Config.LOGTAG,"no mam available"); - } - } - public void joinMuc(Conversation conversation) { Account account = conversation.getAccount(); account.pendingConferenceJoins.remove(conversation); diff --git a/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java similarity index 75% rename from src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java rename to src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java index a41bce86a..e45eba73e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesAvailable.java +++ b/src/main/java/eu/siacs/conversations/xmpp/OnAdvancedStreamFeaturesLoaded.java @@ -2,6 +2,6 @@ package eu.siacs.conversations.xmpp; import eu.siacs.conversations.entities.Account; -public interface OnAdvancedStreamFeaturesAvailable { +public interface OnAdvancedStreamFeaturesLoaded { public void onAdvancedStreamFeaturesAvailable(final Account account); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 0e5d26ed5..af0499c64 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -107,7 +107,7 @@ public class XmppConnection implements Runnable { private OnMessagePacketReceived messageListener = null; private OnStatusChanged statusListener = null; private OnBindListener bindListener = null; - private OnAdvancedStreamFeaturesAvailable advancedStreamFeaturesAvailableListener = null; + private ArrayList advancedStreamFeaturesLoadedListeners = new ArrayList<>(); private OnMessageAcknowledged acknowledgedListener = null; private XmppConnectionService mXmppConnectionService = null; @@ -772,8 +772,8 @@ public class XmppConnection implements Runnable { if (account.getServer().equals(server.toDomainJid())) { enableAdvancedStreamFeatures(); - if (advancedStreamFeaturesAvailableListener != null) { - advancedStreamFeaturesAvailableListener.onAdvancedStreamFeaturesAvailable(account); + for(OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { + listener.onAdvancedStreamFeaturesAvailable(account); } } } @@ -947,8 +947,10 @@ public class XmppConnection implements Runnable { this.acknowledgedListener = listener; } - public void setOnAdvancedStreamFeaturesAvailableListener(OnAdvancedStreamFeaturesAvailable listener) { - this.advancedStreamFeaturesAvailableListener = listener; + public void addOnAdvancedStreamFeaturesAvailableListener(OnAdvancedStreamFeaturesLoaded listener) { + if (!this.advancedStreamFeaturesLoadedListeners.contains(listener)) { + this.advancedStreamFeaturesLoadedListeners.add(listener); + } } public void disconnect(boolean force) { @@ -1095,6 +1097,10 @@ public class XmppConnection implements Runnable { return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0"); } + public boolean advancedStreamFeaturesLoaded() { + return disco.containsKey(account.getServer().toString()); + } + public boolean rosterVersioning() { return connection.streamFeatures != null && connection.streamFeatures.hasChild("ver"); } From ccdb0fd9717fcc79374b55e741bc03eb02fe526d Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Tue, 9 Dec 2014 21:41:49 +0100 Subject: [PATCH 4/7] save server id from mam messages. check for dups before adding mam --- .../siacs/conversations/entities/Message.java | 29 +++++++++++++++++-- .../conversations/parser/MessageParser.java | 9 +++++- .../persistance/DatabaseBackend.java | 7 ++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 1213f66ab..2cced3b39 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -45,6 +45,7 @@ public class Message extends AbstractEntity { public static String STATUS = "status"; public static String TYPE = "type"; public static String REMOTE_MSG_ID = "remoteMsgId"; + public static String SERVER_MSG_ID = "serverMsgId"; public static String RELATIVE_FILE_PATH = "relativeFilePath"; public boolean markable = false; protected String conversationUuid; @@ -59,6 +60,7 @@ public class Message extends AbstractEntity { protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; + protected String serverMsgId = null; protected Conversation conversation = null; protected Downloadable downloadable = null; private Message mNextMessage = null; @@ -83,13 +85,15 @@ public class Message extends AbstractEntity { status, TYPE_TEXT, null, + null, null); 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 String remoteMsgId, final String relativeFilePath) { + final int encryption, final int status, final int type, final String remoteMsgId, + final String relativeFilePath, final String serverMsgId) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -101,6 +105,7 @@ public class Message extends AbstractEntity { this.type = type; this.remoteMsgId = remoteMsgId; this.relativeFilePath = relativeFilePath; + this.serverMsgId = serverMsgId; } public static Message fromCursor(Cursor cursor) { @@ -136,7 +141,8 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), - cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH))); + cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), + cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID))); } public static Message createStatusMessage(Conversation conversation) { @@ -168,6 +174,7 @@ public class Message extends AbstractEntity { values.put(TYPE, type); values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); + values.put(SERVER_MSG_ID,serverMsgId); return values; } @@ -248,6 +255,14 @@ public class Message extends AbstractEntity { this.remoteMsgId = id; } + public String getServerMsgId() { + return this.serverMsgId; + } + + public void setServerMsgId(String id) { + this.serverMsgId = id; + } + public boolean isRead() { return this.read; } @@ -293,7 +308,15 @@ public class Message extends AbstractEntity { } public boolean equals(Message message) { - return (this.remoteMsgId != null) && (this.body != null) && (this.counterpart != null) && this.remoteMsgId.equals(message.getRemoteMsgId()) && this.body.equals(message.getBody()) && this.counterpart.equals(message.getCounterpart()); + if (this.serverMsgId != null && message.getServerMsgId() != null) { + return this.serverMsgId.equals(message.getServerMsgId()); + } else { + return this.body != null + && this.counterpart != null + && ((this.remoteMsgId != null && this.remoteMsgId.equals(message.getRemoteMsgId())) + || this.uuid.equals(message.getRemoteMsgId())) && this.body.equals(message.getBody()) + && this.counterpart.equals(message.getCounterpart()); + } } public Message next() { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3714ac904..9944f364f 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -320,7 +320,14 @@ public class MessageParser extends AbstractParser implements Message finishedMessage = new Message(conversation,content,encryption,status); finishedMessage.setTime(timestamp); finishedMessage.setCounterpart(counterpart); - Log.d(Config.LOGTAG,"received mam message "+content); + finishedMessage.setRemoteMsgId(message.getAttribute("id")); + finishedMessage.setServerMsgId(result.getAttribute("id")); + if (conversation.hasDuplicateMessage(finishedMessage)) { + Log.d(Config.LOGTAG, "received mam message " + content+ " (duplicate)"); + return null; + } else { + Log.d(Config.LOGTAG, "received mam message " + content); + } return finishedMessage; } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 55fcff2e6..aa07d9c03 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -22,7 +22,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 11; + private static final int DATABASE_VERSION = 12; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -65,6 +65,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.BODY + " TEXT, " + Message.ENCRYPTION + " NUMBER, " + Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, " + Message.RELATIVE_FILE_PATH + " TEXT, " + + Message.SERVER_MSG_ID + " TEXT, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID @@ -121,6 +122,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("delete from "+Contact.TABLENAME); db.execSQL("update "+Account.TABLENAME+" set "+Account.ROSTERVERSION+" = NULL"); } + if (oldVersion < 12 && newVersion >= 12) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.SERVER_MSG_ID + " TEXT"); + } } public static synchronized DatabaseBackend getInstance(Context context) { From 1dcdc79a71ae36aadba4c8a82aded46cca9dcf61 Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Tue, 9 Dec 2014 22:50:53 +0100 Subject: [PATCH 5/7] changed lastMessageReceived into lastMessageTransmitted to account for sent messages as well. (will trigger on sm ack) --- .../conversations/entities/Conversation.java | 18 +++++++------ .../conversations/parser/MessageParser.java | 2 +- .../services/MessageArchiveService.java | 8 ++++-- .../services/XmppConnectionService.java | 25 ++++++++++--------- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 63f341e72..e254cfc28 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -45,7 +45,7 @@ public class Conversation extends AbstractEntity { public static final String ATTRIBUTE_NEXT_ENCRYPTION = "next_encryption"; public static final String ATTRIBUTE_MUC_PASSWORD = "muc_password"; public static final String ATTRIBUTE_MUTED_TILL = "muted_till"; - public static final String ATTRIBUTE_LAST_MESSAGE_RECEIVED = "last_message_received"; + public static final String ATTRIBUTE_LAST_MESSAGE_TRANSMITTED = "last_message_transmitted"; private String name; private String contactUuid; @@ -473,14 +473,18 @@ public class Conversation extends AbstractEntity { } } - public boolean setLastMessageReceived(long value) { - long before = getLastMessageReceived(); - this.setAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED, String.valueOf(value)); - return (value - before > 1000); + public boolean setLastMessageTransmitted(long value) { + long before = getLastMessageTransmitted(); + if (value - before > 1000) { + this.setAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED, String.valueOf(value)); + return true; + } else { + return false; + } } - public long getLastMessageReceived() { - long timestamp = getLongAttribute(ATTRIBUTE_LAST_MESSAGE_RECEIVED,0); + public long getLastMessageTransmitted() { + long timestamp = getLongAttribute(ATTRIBUTE_LAST_MESSAGE_TRANSMITTED,0); if (timestamp == 0) { synchronized (this.messages) { for(int i = this.messages.size() - 1; i >= 0; --i) { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 9944f364f..74d38ce44 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -557,7 +557,7 @@ public class MessageParser extends AbstractParser implements Conversation conversation = message.getConversation(); conversation.add(message); if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) { - if (conversation.setLastMessageReceived(System.currentTimeMillis())) { + if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { mXmppConnectionService.updateConversation(conversation); } } diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index c77262cb4..c93a6e759 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -28,7 +28,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { public void query(final Conversation conversation) { synchronized (this.queries) { final Account account = conversation.getAccount(); - long start = conversation.getLastMessageReceived(); + long start = conversation.getLastMessageTransmitted(); long end = account.getXmppConnection().getLastSessionEstablished(); if (end - start >= Config.MAX_HISTORY_AGE) { start = end - Config.MAX_HISTORY_AGE; @@ -51,7 +51,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { synchronized (this.queries) { this.queries.remove(query); } - query.getConversation().sort(); + final Conversation conversation = query.getConversation(); + conversation.sort(); + if (conversation.setLastMessageTransmitted(query.getEnd())) { + this.mXmppConnectionService.databaseBackend.updateConversation(conversation); + } this.mXmppConnectionService.updateConversationUi(); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 3e2c1b8b5..86b6be568 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -73,7 +73,6 @@ import eu.siacs.conversations.utils.OnPhoneContactsLoadedListener; import eu.siacs.conversations.utils.PRNGFixes; import eu.siacs.conversations.utils.PhoneHelper; import eu.siacs.conversations.xml.Element; -import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnBindListener; import eu.siacs.conversations.xmpp.OnContactStatusChanged; import eu.siacs.conversations.xmpp.OnIqPacketReceived; @@ -257,15 +256,17 @@ public class XmppConnectionService extends Service { @Override public void onMessageAcknowledged(Account account, String uuid) { - for (Conversation conversation : getConversations()) { + for (final Conversation conversation : getConversations()) { if (conversation.getAccount() == account) { - for (Message message : conversation.getMessages()) { - if ((message.getStatus() == Message.STATUS_UNSEND || message - .getStatus() == Message.STATUS_WAITING) - && message.getUuid().equals(uuid)) { + for (final Message message : conversation.getMessages()) { + final int s = message.getStatus(); + if ((s == Message.STATUS_UNSEND || s == Message.STATUS_WAITING) && message.getUuid().equals(uuid)) { markMessage(message, Message.STATUS_SEND); + if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) { + databaseBackend.updateConversation(conversation); + } return; - } + } } } } @@ -854,11 +855,11 @@ public class XmppConnectionService extends Service { break; } final Contact contact = account.getRoster() - .getContact(jid); + .getContact(jid); String systemAccount = phoneContact - .getInt("phoneid") - + "#" - + phoneContact.getString("lookup"); + .getInt("phoneid") + + "#" + + phoneContact.getString("lookup"); contact.setSystemAccount(systemAccount); contact.setPhotoUri(phoneContact .getString("photouri")); @@ -1253,7 +1254,7 @@ public class XmppConnectionService extends Service { if (conversation.getMucOptions().getPassword() != null) { x.addChild("password").setContent(conversation.getMucOptions().getPassword()); } - x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageReceived())); + x.addChild("history").setAttribute("since",PresenceGenerator.getTimestamp(conversation.getLastMessageTransmitted())); String sig = account.getPgpSignature(); if (sig != null) { packet.addChild("status").setContent("online"); From f2510ae9f6598e70308b7e2a543010b1660dc876 Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Wed, 10 Dec 2014 14:06:20 +0100 Subject: [PATCH 6/7] mark otr messages as no-store for mam --- src/main/java/eu/siacs/conversations/crypto/OtrEngine.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java index 642d0ed06..3894e2056 100644 --- a/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java +++ b/src/main/java/eu/siacs/conversations/crypto/OtrEngine.java @@ -180,6 +180,7 @@ public class OtrEngine implements OtrEngineHost { packet.setBody(body); packet.addChild("private", "urn:xmpp:carbons:2"); packet.addChild("no-copy", "urn:xmpp:hints"); + packet.addChild("no-store", "urn:xmpp:hints"); packet.setType(MessagePacket.TYPE_CHAT); account.getXmppConnection().sendMessagePacket(packet); } From b523518e4b5a98d5a30aed2ec246fc83c42f5f6c Mon Sep 17 00:00:00 2001 From: iNPUTmice Date: Sat, 13 Dec 2014 12:25:52 +0100 Subject: [PATCH 7/7] various mam improvments --- .../java/eu/siacs/conversations/Config.java | 4 +- .../conversations/entities/Conversation.java | 2 + .../conversations/generator/IqGenerator.java | 7 +- .../conversations/parser/MessageParser.java | 6 +- .../services/MessageArchiveService.java | 133 ++++++++++++++---- .../services/XmppConnectionService.java | 17 ++- 6 files changed, 130 insertions(+), 39 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index e9e73db9e..c491d632b 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -22,7 +22,9 @@ public final class Config { public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb - public static final long MAX_HISTORY_AGE = 7 * 24 * 60 * 60 * 1000; + private static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; + public static final long MAX_HISTORY_AGE = 7 * MILLISECONDS_IN_DAY; + public static final long MAX_CATCHUP = MILLISECONDS_IN_DAY / 2; private Config() { diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index e254cfc28..a30847b97 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.entities; import android.content.ContentValues; import android.database.Cursor; import android.os.SystemClock; +import android.util.Log; import net.java.otr4j.OtrException; import net.java.otr4j.crypto.OtrCryptoEngineImpl; @@ -20,6 +21,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 56a0776f0..4f87b2b04 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -1,9 +1,12 @@ package eu.siacs.conversations.generator; +import android.util.Log; + import java.util.Arrays; import java.util.Collections; import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Element; @@ -103,7 +106,9 @@ public class IqGenerator extends AbstractGenerator { query.setAttribute("queryid",mam.getQueryId()); Data data = new Data(); data.setFormType("urn:xmpp:mam:0"); - data.put("with",mam.getWith().toString()); + if (mam.getWith()!=null) { + data.put("with", mam.getWith().toString()); + } data.put("start",getTimestamp(mam.getStart())); data.put("end",getTimestamp(mam.getEnd())); query.addChild(data); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 74d38ce44..cd4c6401a 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -10,6 +10,7 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Message; +import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.xml.Element; @@ -303,16 +304,17 @@ public class MessageParser extends AbstractParser implements final long timestamp = getTimestamp(forwarded); final Jid to = message.getAttributeAsJid("to"); final Jid from = message.getAttributeAsJid("from"); + final MessageArchiveService.Query query = this.mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid")); Jid counterpart; int status; Conversation conversation; if (from!=null && to != null && from.toBareJid().equals(account.getJid().toBareJid())) { status = Message.STATUS_SEND; - conversation = this.mXmppConnectionService.findOrCreateConversation(account,to.toBareJid(),false); + conversation = this.mXmppConnectionService.findOrCreateConversation(account,to.toBareJid(),false,query); counterpart = to; } else if (from !=null && to != null) { status = Message.STATUS_RECEIVED; - conversation = this.mXmppConnectionService.findOrCreateConversation(account,from.toBareJid(),false); + conversation = this.mXmppConnectionService.findOrCreateConversation(account,from.toBareJid(),false,query); counterpart = from; } else { return null; diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index c93a6e759..3b84d4110 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -9,6 +9,8 @@ import java.util.List; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.generator.AbstractGenerator; +import eu.siacs.conversations.parser.AbstractParser; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnIqPacketReceived; @@ -25,26 +27,69 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.mXmppConnectionService = service; } + public void catchup(final Account account) { + long startCatchup = getLastMessageTransmitted(account); + long endCatchup = account.getXmppConnection().getLastSessionEstablished(); + if (startCatchup == 0) { + return; + } else if (endCatchup - startCatchup >= Config.MAX_CATCHUP) { + startCatchup = endCatchup - Config.MAX_CATCHUP; + List conversations = mXmppConnectionService.getConversations(); + for (Conversation conversation : conversations) { + if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account && startCatchup > conversation.getLastMessageTransmitted()) { + this.query(conversation,startCatchup); + } + } + } + final Query query = new Query(account, startCatchup, endCatchup); + this.queries.add(query); + this.execute(query); + } + + private long getLastMessageTransmitted(final Account account) { + long timestamp = 0; + for(final Conversation conversation : mXmppConnectionService.getConversations()) { + if (conversation.getAccount() == account) { + long tmp = conversation.getLastMessageTransmitted(); + if (tmp > timestamp) { + timestamp = tmp; + } + } + } + return timestamp; + } + public void query(final Conversation conversation) { + query(conversation,conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + } + + public void query(final Conversation conversation, long end) { synchronized (this.queries) { final Account account = conversation.getAccount(); long start = conversation.getLastMessageTransmitted(); - long end = account.getXmppConnection().getLastSessionEstablished(); - if (end - start >= Config.MAX_HISTORY_AGE) { + if (start > end) { + return; + } else if (end - start >= Config.MAX_HISTORY_AGE) { start = end - Config.MAX_HISTORY_AGE; } final Query query = new Query(conversation, start, end); this.queries.add(query); - IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); - this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + this.execute(query); + } + } + + private void execute(final Query query) { + Log.d(Config.LOGTAG,query.getAccount().getJid().toBareJid().toString()+": running mam query "+query.toString()); + IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(query.getAccount(), packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { if (packet.getType() == IqPacket.TYPE_ERROR) { + Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": error executing mam: "+packet.toString()); finalizeQuery(query); } } }); - } } private void finalizeQuery(Query query) { @@ -52,11 +97,22 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.queries.remove(query); } final Conversation conversation = query.getConversation(); - conversation.sort(); - if (conversation.setLastMessageTransmitted(query.getEnd())) { - this.mXmppConnectionService.databaseBackend.updateConversation(conversation); + if (conversation != null) { + conversation.sort(); + if (conversation.setLastMessageTransmitted(query.getEnd())) { + this.mXmppConnectionService.databaseBackend.updateConversation(conversation); + } + this.mXmppConnectionService.updateConversationUi(); + } else { + for(Conversation tmp : this.mXmppConnectionService.getConversations()) { + if (tmp.getAccount() == query.getAccount()) { + tmp.sort(); + if (tmp.setLastMessageTransmitted(query.getEnd())) { + this.mXmppConnectionService.databaseBackend.updateConversation(tmp); + } + } + } } - this.mXmppConnectionService.updateConversationUi(); } public void processFin(Element fin) { @@ -71,28 +127,18 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); Element last = set == null ? null : set.findChild("last"); if (complete || last == null) { - final Account account = query.getConversation().getAccount(); - Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": completed mam query for "+query.getWith().toString()); this.finalizeQuery(query); } else { final Query nextQuery = query.next(last == null ? null : last.getContent()); - IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(nextQuery); + this.execute(nextQuery); synchronized (this.queries) { this.queries.remove(query); this.queries.add(nextQuery); } - this.mXmppConnectionService.sendIqPacket(query.getConversation().getAccount(),packet,new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE_ERROR) { - finalizeQuery(nextQuery); - } - } - }); } } - private Query findQuery(String id) { + public Query findQuery(String id) { if (id == null) { return null; } @@ -109,36 +155,37 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { @Override public void onAdvancedStreamFeaturesAvailable(Account account) { if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { - List conversations = mXmppConnectionService.getConversations(); - for (Conversation conversation : conversations) { - if (conversation.getMode() == Conversation.MODE_SINGLE && conversation.getAccount() == account) { - this.query(conversation); - } - } - } else { - Log.d(Config.LOGTAG,"no mam available"); + this.catchup(account); } } public class Query { private long start; private long end; - private Jid with; + private Jid with = null; private String queryId; private String after = null; + private Account account; private Conversation conversation; public Query(Conversation conversation, long start, long end) { + this(conversation.getAccount(), start, end); this.conversation = conversation; this.with = conversation.getContactJid().toBareJid(); + } + + public Query(Account account, long start, long end) { + this.account = account; this.start = start; this.end = end; this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); } public Query next(String after) { - Query query = new Query(this.conversation,this.start,this.end); + Query query = new Query(this.account,this.start,this.end); query.after = after; + query.conversation = conversation; + query.with = with; return query; } @@ -165,5 +212,29 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { public Conversation getConversation() { return conversation; } + + public Account getAccount() { + return this.account; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("with="); + if (this.with==null) { + builder.append("*"); + } else { + builder.append(with.toString()); + } + builder.append(", start="); + builder.append(AbstractGenerator.getTimestamp(this.start)); + builder.append(", end="); + builder.append(AbstractGenerator.getTimestamp(this.end)); + if (this.after!=null) { + builder.append(", after="); + builder.append(this.after); + } + return builder.toString(); + } } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 86b6be568..7df97f5a4 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -988,8 +988,11 @@ public class XmppConnectionService extends Service { return null; } - public Conversation findOrCreateConversation(final Account account, final Jid jid, - final boolean muc) { + public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc) { + return this.findOrCreateConversation(account,jid,muc,null); + } + + public Conversation findOrCreateConversation(final Account account, final Jid jid,final boolean muc, final MessageArchiveService.Query query) { synchronized (this.conversations) { Conversation conversation = find(account, jid); if (conversation != null) { @@ -1023,7 +1026,13 @@ public class XmppConnectionService extends Service { } this.databaseBackend.createConversation(conversation); } - this.mMessageArchiveService.query(conversation); + if (query == null) { + this.mMessageArchiveService.query(conversation); + } else { + if (query.getConversation() == null) { + this.mMessageArchiveService.query(conversation,query.getStart()); + } + } this.conversations.add(conversation); updateConversationUi(); return conversation; @@ -1303,7 +1312,7 @@ public class XmppConnectionService extends Service { @Override public void onFailure() { - callback.error(R.string.nick_in_use,conversation); + callback.error(R.string.nick_in_use, conversation); } });