From a892bb6f1685b84b74ba37e5a6a24ee8fb45eee7 Mon Sep 17 00:00:00 2001 From: steckbrief Date: Mon, 7 Aug 2017 12:57:57 +0200 Subject: Simplifying introduction of new XEP implementations, implements FS#250 and FS#251 (Privacy and Security settings), refactoring of location of some interfaces --- .../ConversationsPlusPreferences.java | 12 + .../crypto/axolotl/AxolotlServiceImpl.java | 13 +- .../generator/AbstractGenerator.java | 63 --- .../conversationsplus/generator/IqGenerator.java | 20 +- .../generator/MessageGenerator.java | 5 +- .../generator/PresenceGenerator.java | 10 +- .../conversationsplus/parser/IqParser.java | 27 +- .../conversationsplus/parser/MessageParser.java | 8 +- .../conversationsplus/parser/PresenceParser.java | 5 +- .../services/MessageArchiveService.java | 421 -------------------- .../services/XmppConnectionService.java | 178 ++------- .../services/mam/MessageArchiveService.java | 422 +++++++++++++++++++++ .../services/mam/OnMoreMessagesLoaded.java | 13 + .../muc/OnConferenceConfigurationFetched.java | 10 + .../services/muc/OnConferenceJoined.java | 7 + .../listener/ConferenceConfigurationFetched.java | 96 +++++ .../ConferenceServiceDiscoveryReceived.java | 59 +++ .../conversationsplus/ui/SettingsActivity.java | 16 +- .../ConversationMoreMessagesLoadedListener.java | 3 +- .../ConversationSwipeRefreshListener.java | 2 +- .../conversationsplus/utils/UiUpdateHelper.java | 9 + .../conversationsplus/utils/XmppSendUtil.java | 6 +- .../conversationsplus/xmpp/AbstractXep.java | 30 ++ .../conversationsplus/xmpp/IqPacketHandler.java | 12 + .../de/thedevstack/conversationsplus/xmpp/Xep.java | 16 + .../conversationsplus/xmpp/XepRegistry.java | 51 +++ .../conversationsplus/xmpp/XmppConnection.java | 6 +- .../xmpp/disco/FeatureRegistry.java | 90 +++++ .../xmpp/disco/ServiceDiscovery.java | 8 + .../disco/ServiceDiscoveryIqPacketGenerator.java | 38 ++ .../disco/ServiceDiscoveryIqPacketHandler.java | 21 + .../xmpp/disco/ServiceDiscoveryIqPacketParser.java | 15 + .../xmpp/disco/ServiceDiscoveryXep.java | 51 +++ .../xmpp/exceptions/NotAllowedIqException.java | 11 + .../xmpp/iqversion/IqVersion.java | 9 + .../xmpp/iqversion/IqVersionPacket.java | 23 -- .../xmpp/iqversion/IqVersionPacketGenerator.java | 21 +- .../xmpp/iqversion/IqVersionPacketHandler.java | 21 + .../xmpp/iqversion/SoftwareVersionXep.java | 51 +++ .../conversationsplus/xmpp/openpgp/OpenPgpXep.java | 48 +++ .../conversationsplus/xmpp/ping/Ping.java | 8 + .../xmpp/ping/PingPacketHandler.java | 22 ++ .../conversationsplus/xmpp/ping/PingXep.java | 51 +++ .../xmpp/stanzas/ErrorIqPacket.java | 16 + .../xmpp/stanzas/IqErrorCondition.java | 48 +++ .../xmpp/stanzas/IqErrorType.java | 12 + .../conversationsplus/xmpp/stanzas/IqPacket.java | 4 + .../xmpp/stanzas/IqPacketGenerator.java | 17 +- .../xmpp/stanzas/IqPacketReceiver.java | 96 +++++ .../xmpp/stanzas/csi/ActivePacket.java | 2 +- .../conversationsplus/xmpp/stanzas/csi/Csi.java | 7 + .../xmpp/stanzas/csi/InactivePacket.java | 2 +- .../conversationsplus/xmpp/time/EntityTime.java | 8 + .../conversationsplus/xmpp/time/EntityTimeXep.java | 51 +++ .../xmpp/time/TimeIqPacketHandler.java | 21 + .../conversationsplus/xmpp/time/TimePacket.java | 35 -- .../xmpp/time/TimePacketGenerator.java | 31 +- .../xmpp/utils/ErrorIqPacketExceptionHelper.java | 16 +- src/main/res/values-de/strings.xml | 9 + src/main/res/values/strings.xml | 9 + src/main/res/xml/preferences.xml | 120 +++--- 61 files changed, 1696 insertions(+), 816 deletions(-) delete mode 100644 src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/mam/OnMoreMessagesLoaded.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceConfigurationFetched.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceJoined.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceConfigurationFetched.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceServiceDiscoveryReceived.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractXep.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketHandler.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/Xep.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/XepRegistry.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscovery.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketGenerator.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketHandler.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketParser.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryXep.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/NotAllowedIqException.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersion.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacket.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketHandler.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/SoftwareVersionXep.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/openpgp/OpenPgpXep.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/ping/Ping.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingPacketHandler.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingXep.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/ErrorIqPacket.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorCondition.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorType.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketReceiver.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/Csi.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTime.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTimeXep.java create mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimeIqPacketHandler.java delete mode 100644 src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacket.java diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java index 546ad7ee..e59f5b57 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java @@ -14,6 +14,18 @@ public class ConversationsPlusPreferences extends Settings { private static ConversationsPlusPreferences instance; private final SharedPreferences sharedPreferences; + public static boolean sendEntityTime() { + return getBoolean("send_entity_time", true); + } + + public static boolean sendSoftwareVersion() { + return getBoolean("send_software_info", true); + } + + public static boolean sendOsInformation() { + return getBoolean("send_os_info", true); + } + public static boolean logStanzas() { return getBoolean("log_stanzas", true); } 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 215e0995..9cf540a3 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java @@ -50,6 +50,7 @@ import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketReceiver; public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, AxolotlService { @@ -414,8 +415,8 @@ public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, Axolo if (packet.getType() == IqPacket.TYPE.TIMEOUT) { Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); } else { - Element item = mXmppConnectionService.getIqParser().getItem(packet); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + Element item = IqPacketReceiver.getInstance().getLegacyIqParser().getItem(packet); + Set deviceIds = IqPacketReceiver.getInstance().getLegacyIqParser().deviceIds(item); if (!deviceIds.contains(getOwnDeviceId())) { publishOwnDeviceId(deviceIds); } @@ -502,8 +503,8 @@ public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, Axolo } } - PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); - Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + PreKeyBundle bundle = IqPacketReceiver.getInstance().getLegacyIqParser().bundle(packet); + Map keys = IqPacketReceiver.getInstance().getLegacyIqParser().preKeyPublics(packet); boolean flush = false; if (bundle == null) { Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received invalid bundle:" + packet); @@ -662,7 +663,7 @@ public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, Axolo mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Pair verification = mXmppConnectionService.getIqParser().verification(packet); + Pair verification = IqPacketReceiver.getInstance().getLegacyIqParser().verification(packet); if (verification != null) { try { Signature verifier = Signature.getInstance("sha256WithRSA"); @@ -741,7 +742,7 @@ public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, Axolo fetchStatusMap.put(address, FetchStatus.TIMEOUT); } else if (packet.getType() == IqPacket.TYPE.RESULT) { Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Received preKey IQ packet, processing..."); - final IqParser parser = mXmppConnectionService.getIqParser(); + final IqParser parser = IqPacketReceiver.getInstance().getLegacyIqParser(); final List preKeyBundleList = parser.preKeys(packet); final PreKeyBundle bundle = parser.bundle(packet); if (preKeyBundleList.isEmpty() || bundle == null) { diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java index 7302b2df..a8447a32 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/AbstractGenerator.java @@ -1,78 +1,15 @@ package de.thedevstack.conversationsplus.generator; -import android.util.Base64; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.Locale; import java.util.TimeZone; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; -import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; -import de.thedevstack.conversationsplus.xmpp.iqversion.IqVersionPacket; -import de.thedevstack.conversationsplus.xmpp.time.TimePacket; -import de.tzur.conversations.Settings; -import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; - public abstract class AbstractGenerator { - private static final String[] FEATURES = { - "urn:xmpp:jingle:1", - "urn:xmpp:jingle:apps:file-transfer:3", - "urn:xmpp:jingle:transports:s5b:1", - "urn:xmpp:jingle:transports:ibb:1", - "http://jabber.org/protocol/muc", - "jabber:x:conference", - "http://jabber.org/protocol/caps", - "http://jabber.org/protocol/disco#info", - "urn:xmpp:avatar:metadata+notify", - "http://jabber.org/protocol/nick+notify", - "urn:xmpp:ping", - TimePacket.NAMESPACE, - IqVersionPacket.NAMESPACE, - ChatState.NAMESPACE, - AxolotlService.PEP_DEVICE_LIST+"+notify"}; - private static final String[] MESSAGE_CONFIRMATION_FEATURES = { - "urn:xmpp:chat-markers:0", - "urn:xmpp:receipts" - }; - protected static final String IDENTITY_TYPE = "phone"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); - public static String getCapHash() { - StringBuilder s = new StringBuilder(); - s.append("client/" + IDENTITY_TYPE + "//" + ConversationsPlusApplication.getNameAndVersion() + "<"); - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - return null; - } - - for (String feature : getFeatures()) { - s.append(feature + "<"); - } - byte[] sha1 = md.digest(s.toString().getBytes()); - return new String(Base64.encode(sha1, Base64.DEFAULT)); - } - public static String getTimestamp(long time) { DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); return DATE_FORMAT.format(time); } - - public static List getFeatures() { - ArrayList features = new ArrayList<>(); - features.addAll(Arrays.asList(FEATURES)); - if (Settings.CONFIRM_MESSAGE_RECEIVED) { - features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES)); - } - Collections.sort(features); - return features; - } } diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java index aa279e40..88652b39 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/IqGenerator.java @@ -15,12 +15,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.services.MessageArchiveService; +import de.thedevstack.conversationsplus.services.mam.MessageArchiveService; import de.thedevstack.conversationsplus.utils.Xmlns; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.forms.Data; @@ -30,23 +29,6 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; public class IqGenerator extends AbstractGenerator { - public IqPacket discoResponse(final IqPacket request) { - final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); - packet.setId(request.getId()); - packet.setTo(request.getFrom()); - final Element query = packet.addChild("query", - "http://jabber.org/protocol/disco#info"); - query.setAttribute("node", request.query().getAttribute("node")); - final Element identity = query.addChild("identity"); - identity.setAttribute("category", "client"); - identity.setAttribute("type", IDENTITY_TYPE); - identity.setAttribute("name", ConversationsPlusApplication.getNameAndVersion()); - for (final String feature : getFeatures()) { - query.addChild("feature").setAttribute("var", feature); - } - return packet; - } - protected IqPacket publish(final String node, final Element item) { final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Element pubsub = packet.addChild("pubsub", diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java index e76d25f6..2e580f09 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/MessageGenerator.java @@ -20,6 +20,7 @@ import de.thedevstack.conversationsplus.xmpp.carbons.Carbons; import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; import de.thedevstack.conversationsplus.xmpp.httpuploadim.HttpUploadHint; import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.openpgp.OpenPgpXep; import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; public class MessageGenerator extends AbstractGenerator { @@ -128,9 +129,9 @@ public class MessageGenerator extends AbstractGenerator { MessagePacket packet = preparePacket(message); packet.setBody("This is an XEP-0027 encrypted message"); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { - packet.addChild("x", "jabber:x:encrypted").setContent(message.getEncryptedBody()); + packet.addChild(OpenPgpXep.ENCRYPTED_ELEMENT, OpenPgpXep.ENCRYPTED_NAMESPACE).setContent(message.getEncryptedBody()); } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - packet.addChild("x", "jabber:x:encrypted").setContent(message.getBody()); + packet.addChild(OpenPgpXep.ENCRYPTED_ELEMENT, OpenPgpXep.ENCRYPTED_NAMESPACE).setContent(message.getBody()); } return packet; } diff --git a/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java b/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java index fbe7c911..bfede624 100644 --- a/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/generator/PresenceGenerator.java @@ -5,7 +5,9 @@ import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.MucOptions; import de.thedevstack.conversationsplus.entities.Presence; import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.disco.FeatureRegistry; import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.openpgp.OpenPgpXep; import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; public class PresenceGenerator extends AbstractGenerator { @@ -54,9 +56,9 @@ public class PresenceGenerator extends AbstractGenerator { packet.setFrom(account.getJid()); String sig = account.getPgpSignature(); if (sig != null) { - packet.addChild("x", "jabber:x:signed").setContent(sig); + packet.addChild(OpenPgpXep.SIGNED_ELEMENT, OpenPgpXep.SIGNED_NAMESPACE).setContent(sig); } - String capHash = getCapHash(); + String capHash = FeatureRegistry.getCapHash(); if (capHash != null) { Element cap = packet.addChild("c", "http://jabber.org/protocol/caps"); @@ -97,7 +99,7 @@ public class PresenceGenerator extends AbstractGenerator { } String sig = account.getPgpSignature(); if (sig != null) { - packet.addChild("x", "jabber:x:signed").setContent(sig); + packet.addChild(OpenPgpXep.SIGNED_ELEMENT, OpenPgpXep.SIGNED_NAMESPACE).setContent(sig); } return packet; @@ -109,7 +111,7 @@ public class PresenceGenerator extends AbstractGenerator { String sig = account.getPgpSignature(); if (sig != null) { packet.addChild("status").setContent("online"); - packet.addChild("x", "jabber:x:signed").setContent(sig); + packet.addChild(OpenPgpXep.SIGNED_ELEMENT, OpenPgpXep.SIGNED_NAMESPACE).setContent(sig); } return packet; diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java index 35b41d61..b3cfc813 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/IqParser.java @@ -28,19 +28,14 @@ import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.services.avatar.AvatarCache; -import de.thedevstack.conversationsplus.services.avatar.AvatarService; import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; import de.thedevstack.conversationsplus.utils.Xmlns; -import de.thedevstack.conversationsplus.utils.XmppSendUtil; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.OnUpdateBlocklist; -import de.thedevstack.conversationsplus.xmpp.iqversion.IqVersionPacket; -import de.thedevstack.conversationsplus.xmpp.iqversion.IqVersionPacketGenerator; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; -import de.thedevstack.conversationsplus.xmpp.time.TimePacket; -import de.thedevstack.conversationsplus.xmpp.time.TimePacketGenerator; public class IqParser extends AbstractParser implements OnIqPacketReceived { @@ -263,9 +258,9 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { - return; - } else if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { + if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { + return; + } else if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) { final Element query = packet.findChild("query"); // If this is in response to a query for the whole roster: if (packet.getType() == IqPacket.TYPE.RESULT) { @@ -300,7 +295,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { account.getBlocklist().addAll(jids); } // Update the UI - mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + UiUpdateHelper.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); } else if (packet.hasChild("unblock", Xmlns.BLOCKING) && packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { Logging.d(Config.LOGTAG, "Received unblock update from server"); @@ -320,21 +315,11 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived { } account.getBlocklist().removeAll(jids); } - mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + UiUpdateHelper.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") || packet.hasChild("data", "http://jabber.org/protocol/ibb")) { mXmppConnectionService.getJingleConnectionManager() .deliverIbbPacket(account, packet); - } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { - final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(packet); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("query", IqVersionPacket.NAMESPACE)) { - XmppSendUtil.sendIqPacket(account, IqVersionPacketGenerator.generateResponse(packet), null); - } else if (packet.hasChild("ping", "urn:xmpp:ping")) { - final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); - mXmppConnectionService.sendIqPacket(account, response, null); - } else if (packet.hasChild("time", TimePacket.NAMESPACE)) { - XmppSendUtil.sendIqPacket(account, TimePacketGenerator.generateResponse(packet), null); } else { if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { final IqPacket response = packet.generateResponse(IqPacket.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 52d35f58..4cc6f8b2 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java @@ -17,6 +17,8 @@ import de.thedevstack.conversationsplus.xmpp.avatar.AvatarPacketParser; import de.thedevstack.conversationsplus.xmpp.carbons.Carbons; import de.thedevstack.conversationsplus.xmpp.httpuploadim.HttpUploadHint; import de.thedevstack.conversationsplus.xmpp.mam.Mam; +import de.thedevstack.conversationsplus.xmpp.openpgp.OpenPgpXep; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketReceiver; import de.tzur.conversations.Settings; import net.java.otr4j.session.Session; @@ -40,7 +42,7 @@ import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.MucOptions; import de.thedevstack.conversationsplus.services.avatar.AvatarService; -import de.thedevstack.conversationsplus.services.MessageArchiveService; +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; @@ -231,7 +233,7 @@ public class MessageParser extends AbstractParser implements } else if (ConversationsPlusPreferences.omemoEnabled() && AxolotlService.PEP_DEVICE_LIST.equals(node)) { Log.d(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing..."); Element item = items.findChild("item"); - Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + Set deviceIds = IqPacketReceiver.getInstance().getLegacyIqParser().deviceIds(item); AxolotlService axolotlService = account.getAxolotlService(); axolotlService.registerDevices(from, deviceIds); UiUpdateHelper.updateAccountUi(); @@ -313,7 +315,7 @@ public class MessageParser extends AbstractParser implements } final String body = packet.getBody(); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); - final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); + 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); diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java index 5270eefe..ed366a2c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/PresenceParser.java @@ -28,6 +28,7 @@ import de.thedevstack.conversationsplus.xmpp.OnPresencePacketReceived; import de.thedevstack.conversationsplus.xmpp.avatar.AvatarVcardParser; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.dto.Avatar; +import de.thedevstack.conversationsplus.xmpp.openpgp.OpenPgpXep; import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; public class PresenceParser extends AbstractParser implements @@ -91,7 +92,7 @@ public class PresenceParser extends AbstractParser implements mucOptions.addUser(user); } if (mXmppConnectionService.getPgpEngine() != null) { - Element signed = packet.findChild("x", "jabber:x:signed"); + Element signed = packet.findChild(OpenPgpXep.SIGNED_ELEMENT, OpenPgpXep.SIGNED_NAMESPACE); if (signed != null) { Element status = packet.findChild("status"); String msg = status == null ? "" : status.getContent(); @@ -213,7 +214,7 @@ public class PresenceParser extends AbstractParser implements } PgpEngine pgp = mXmppConnectionService.getPgpEngine(); - Element x = packet.findChild("x", "jabber:x:signed"); + Element x = packet.findChild(OpenPgpXep.SIGNED_ELEMENT, OpenPgpXep.SIGNED_NAMESPACE); if (pgp != null && x != null) { Element status = packet.findChild("status"); String msg = status != null ? status.getContent() : ""; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java b/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java deleted file mode 100644 index 6402f4ed..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java +++ /dev/null @@ -1,421 +0,0 @@ -package de.thedevstack.conversationsplus.services; - -import android.util.Pair; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; - -import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; -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.xml.Element; -import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded; -import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; -import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; - -public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { - - private final XmppConnectionService mXmppConnectionService; - - private final HashSet queries = new HashSet<>(); - private final ArrayList pendingQueries = new ArrayList<>(); - - public enum PagingOrder { - NORMAL, - REVERSE - }; - - public MessageArchiveService(final XmppConnectionService service) { - this.mXmppConnectionService = service; - } - - private void catchup(final Account account) { - synchronized (this.queries) { - for(Iterator iterator = this.queries.iterator(); iterator.hasNext();) { - Query query = iterator.next(); - if (query.getAccount() == account) { - iterator.remove(); - } - } - } - Pair pair = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); - long startCatchup = pair == null ? 0 : pair.first; - long endCatchup = account.getXmppConnection().getLastSessionEstablished(); - final Query query; - if (startCatchup == 0) { - return; - } else if (endCatchup - startCatchup >= Config.MAM_MAX_CATCHUP) { - startCatchup = endCatchup - Config.MAM_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); - } - } - query = new Query(account, startCatchup, endCatchup); - } else { - query = new Query(account, startCatchup, endCatchup); - query.reference = pair.second; - } - this.queries.add(query); - this.execute(query); - } - - public void catchupMUC(final Conversation conversation) { - if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { - query(conversation, - 0, - System.currentTimeMillis()); - } else { - query(conversation, - conversation.getLastMessageTransmitted(), - System.currentTimeMillis()); - } - } - - public Query query(final Conversation conversation) { - if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { - return query(conversation, - 0, - System.currentTimeMillis()); - } else { - return query(conversation, - conversation.getLastMessageTransmitted(), - conversation.getAccount().getXmppConnection().getLastSessionEstablished()); - } - } - - public Query query(final Conversation conversation, long end) { - return this.query(conversation,conversation.getLastMessageTransmitted(),end); - } - - public Query query(Conversation conversation, long start, long end) { - return this.query(conversation, start, end, null); - } - - public Query query(Conversation conversation, long start, long end, XmppConnectionService.OnMoreMessagesLoaded callback) { - synchronized (this.queries) { - if (start > end) { - return null; - } - final Query query = new Query(conversation, start, end,PagingOrder.REVERSE); - query.reference = conversation.getFirstMamReference(); - this.queries.add(query); - if (null != callback) { - query.setCallback(callback); - } - this.execute(query); - return query; - } - } - - public void executePendingQueries(final Account account) { - List pending = new ArrayList<>(); - synchronized(this.pendingQueries) { - for(Iterator iterator = this.pendingQueries.iterator(); iterator.hasNext();) { - Query query = iterator.next(); - if (query.getAccount() == account) { - pending.add(query); - iterator.remove(); - } - } - } - for(Query query : pending) { - this.execute(query); - } - } - - private void execute(final Query query) { - final Account account= query.getAccount(); - if (account.getStatus() == Account.State.ONLINE) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString()); - IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); - this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.TIMEOUT) { - synchronized (MessageArchiveService.this.queries) { - MessageArchiveService.this.queries.remove(query); - if (query.hasCallback()) { - query.callback(); - } - } - } else if (packet.getType() != IqPacket.TYPE.RESULT) { - Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); - finalizeQuery(query, true); - } - } - }); - } else { - synchronized (this.pendingQueries) { - this.pendingQueries.add(query); - } - } - } - - private void finalizeQuery(Query query, boolean done) { - synchronized (this.queries) { - this.queries.remove(query); - } - 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(); - } else { - if (null != conversation) { - conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0); - } - this.mXmppConnectionService.updateConversationUi(); - } - } - - public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { - synchronized (this.queries) { - for(Query query : queries) { - if (query.conversation == conversation) { - if (!query.hasCallback() && callback != null) { - query.setCallback(callback); - } - return true; - } - } - return false; - } - } - - public boolean queryInProgress(Conversation conversation) { - return queryInProgress(conversation, null); - } - - public void processFin(Element fin, Jid from) { - if (fin == null) { - return; - } - Query query = findQuery(fin.getAttribute("queryid")); - if (query == null || !query.validFrom(from)) { - return; - } - boolean complete = fin.getAttributeAsBoolean("complete"); - Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); - Element last = set == null ? null : set.findChild("last"); - Element first = set == null ? null : set.findChild("first"); - Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; - boolean abort = (query.getStart() == 0 && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; - if (query.getConversation() != null) { - query.getConversation().setFirstMamReference(first == null ? null : first.getContent()); - } - if (complete || relevant == null || abort) { - final boolean done = (complete || query.getMessageCount() == 0) && query.getStart() == 0; - this.finalizeQuery(query, done); - Logging.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done)); - if (query.getWith() == null && query.getMessageCount() > 0) { - mXmppConnectionService.getNotificationService().finishBacklog(true); - } - } else { - final Query nextQuery; - if (query.getPagingOrder() == PagingOrder.NORMAL) { - nextQuery = query.next(last == null ? null : last.getContent()); - } else { - nextQuery = query.prev(first == null ? null : first.getContent()); - } - this.execute(nextQuery); - this.finalizeQuery(query, false); - synchronized (this.queries) { - this.queries.add(nextQuery); - } - } - } - - public 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; - } - } - - @Override - public void onAdvancedStreamFeaturesAvailable(Account account) { - if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { - this.catchup(account); - } - } - - public class Query { - private int totalCount = 0; - private int messageCount = 0; - private long start; - private long end; - private String queryId; - private String reference = null; - private Account account; - private Conversation conversation; - private PagingOrder pagingOrder = PagingOrder.NORMAL; - private XmppConnectionService.OnMoreMessagesLoaded callback = null; - - - public Query(Conversation conversation, long start, long end) { - this(conversation.getAccount(), start, end); - this.conversation = conversation; - } - - public Query(Conversation conversation, long start, long end, PagingOrder order) { - this(conversation,start,end); - this.pagingOrder = order; - } - - public Query(Account account, long start, long end) { - this.account = account; - this.start = start; - this.end = end; - this.queryId = new BigInteger(50, ConversationsPlusApplication.getSecureRandom()).toString(32); - } - - private Query page(String reference) { - Query query = new Query(this.account,this.start,this.end); - query.reference = reference; - query.conversation = conversation; - query.totalCount = totalCount; - query.callback = callback; - return query; - } - - public Query next(String reference) { - Query query = page(reference); - query.pagingOrder = PagingOrder.NORMAL; - return query; - } - - public Query prev(String reference) { - Query query = page(reference); - query.pagingOrder = PagingOrder.REVERSE; - return query; - } - - public String getReference() { - return reference; - } - - public PagingOrder getPagingOrder() { - return this.pagingOrder; - } - - public String getQueryId() { - return queryId; - } - - public Jid getWith() { - return conversation == null ? null : conversation.getJid().toBareJid(); - } - - public boolean muc() { - return conversation != null && conversation.getMode() == Conversation.MODE_MULTI; - } - - public long getStart() { - return start; - } - - public void setCallback(XmppConnectionService.OnMoreMessagesLoaded callback) { - this.callback = callback; - } - - public void callback() { - if (this.callback != null) { - this.callback.onMoreMessagesLoaded(messageCount,conversation); - if (messageCount <= 0) { - this.callback.informUser(R.string.no_more_history_on_server); - } - } - } - - public long getEnd() { - return end; - } - - public Conversation getConversation() { - return conversation; - } - - public Account getAccount() { - return this.account; - } - - public void incrementMessageCount() { - this.messageCount++; - this.totalCount++; - } - - public int getTotalCount() { - return this.totalCount; - } - - public int getMessageCount() { - return this.messageCount; - } - - public boolean validFrom(Jid from) { - if (muc()) { - return getWith().equals(from); - } else { - return (from == null) || account.getJid().toBareJid().equals(from.toBareJid()); - } - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - if (this.muc()) { - builder.append("to="); - builder.append(this.getWith().toString()); - } else { - builder.append("with="); - if (this.getWith() == null) { - builder.append("*"); - } else { - builder.append(getWith().toString()); - } - } - builder.append(", start="); - builder.append(AbstractGenerator.getTimestamp(this.start)); - builder.append(", end="); - builder.append(AbstractGenerator.getTimestamp(this.end)); - if (this.reference!=null) { - if (this.pagingOrder == PagingOrder.NORMAL) { - builder.append(", after="); - } else { - builder.append(", before="); - } - builder.append(this.reference); - } - return builder.toString(); - } - - public boolean hasCallback() { - return this.callback != null; - } - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java index ac6ef464..963adfe4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java @@ -58,13 +58,28 @@ import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.services.avatar.AvatarCache; import de.thedevstack.conversationsplus.services.avatar.AvatarService; import de.thedevstack.conversationsplus.services.filetransfer.FileTransferManager; +import de.thedevstack.conversationsplus.services.mam.MessageArchiveService; +import de.thedevstack.conversationsplus.services.mam.OnMoreMessagesLoaded; +import de.thedevstack.conversationsplus.services.muc.OnConferenceConfigurationFetched; +import de.thedevstack.conversationsplus.services.muc.OnConferenceJoined; +import de.thedevstack.conversationsplus.services.muc.listener.ConferenceConfigurationFetched; import de.thedevstack.conversationsplus.utils.AccountUtil; import de.thedevstack.conversationsplus.utils.ImageUtil; import de.thedevstack.conversationsplus.utils.MessageUtil; import de.thedevstack.conversationsplus.utils.UiUpdateHelper; 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.disco.ServiceDiscoveryIqPacketGenerator; +import de.thedevstack.conversationsplus.xmpp.disco.ServiceDiscoveryIqPacketParser; +import de.thedevstack.conversationsplus.xmpp.disco.ServiceDiscoveryXep; +import de.thedevstack.conversationsplus.xmpp.iqversion.SoftwareVersionXep; import de.thedevstack.conversationsplus.xmpp.mam.Mam; +import de.thedevstack.conversationsplus.xmpp.openpgp.OpenPgpXep; +import de.thedevstack.conversationsplus.xmpp.ping.PingXep; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketReceiver; +import de.thedevstack.conversationsplus.xmpp.time.EntityTimeXep; import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; @@ -87,7 +102,6 @@ import de.thedevstack.conversationsplus.entities.TransferablePlaceholder; import de.thedevstack.conversationsplus.generator.IqGenerator; import de.thedevstack.conversationsplus.generator.MessageGenerator; import de.thedevstack.conversationsplus.generator.PresenceGenerator; -import de.thedevstack.conversationsplus.parser.IqParser; import de.thedevstack.conversationsplus.parser.MessageParser; import de.thedevstack.conversationsplus.parser.PresenceParser; import de.thedevstack.conversationsplus.persistance.DatabaseBackend; @@ -150,7 +164,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa this); private OnMessagePacketReceived mMessageParser = new MessageParser(this); private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private IqParser mIqParser = new IqParser(this); + private OnIqPacketReceived mDefaultIqHandler = new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -695,11 +709,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public XmppConnection createConnection(final Account account) { account.setResource(ConversationsPlusPreferences.resource().toLowerCase(Locale.getDefault())); + + // TODO Find better place for initialization + IqPacketReceiver iqPacketReceiver = IqPacketReceiver.getInstance(); + XepRegistry.add(new EntityTimeXep(ConversationsPlusPreferences.sendEntityTime())); + XepRegistry.add(new SoftwareVersionXep(ConversationsPlusPreferences.sendSoftwareVersion())); + XepRegistry.add(new ServiceDiscoveryXep()); + XepRegistry.add(new PingXep()); + XepRegistry.add(new OpenPgpXep()); + final XmppConnection connection = new XmppConnection(account, this); connection.setOnMessagePacketReceivedListener(this.mMessageParser); connection.setOnStatusChangedListener(this.statusListener); connection.setOnPresencePacketReceivedListener(this.mPresenceParser); - connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser); + connection.setOnUnregisteredIqPacketReceivedListener(iqPacketReceiver); connection.setOnJinglePacketReceivedListener(this.jingleListener); connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); @@ -875,7 +898,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private void sendUnsentMessages(final Conversation conversation) { + public void sendUnsentMessages(final Conversation conversation) { conversation.findWaitingMessages(new Conversation.OnMessageFound() { @Override @@ -898,7 +921,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetching roster"); } iqPacket.query(Xmlns.ROSTER).setAttribute("ver", account.getRosterVersion()); - sendIqPacket(account, iqPacket, mIqParser); + XmppSendUtil.sendIqPacket(account, iqPacket, IqPacketReceiver.getInstance().getLegacyIqParser()); //TODO -> replace with RosterIqHandler } public void fetchBookmarks(final Account account) { @@ -1657,44 +1680,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (account.getStatus() == Account.State.ONLINE) { conversation.resetMucOptions(); conversation.setHasMessagesLeftOnServer(false); - fetchConferenceConfiguration(conversation, new OnConferenceConfigurationFetched() { - - private void join(Conversation conversation) { - Account account = conversation.getAccount(); - final MucOptions mucOptions = conversation.getMucOptions(); - final Jid joinJid = mucOptions.getSelf().getFullJid(); - Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); - long lastMessageTransmitted = conversation.getLastMessageTransmitted(); - PresencePacket packet = PresenceGenerator.generateMucJoin(account, joinJid, mucOptions, lastMessageTransmitted); - XmppSendUtil.sendPresencePacket(account, packet); - if (onConferenceJoined != null) { - onConferenceJoined.onConferenceJoined(conversation); - } - if (!joinJid.equals(conversation.getJid())) { - conversation.setContactJid(joinJid); - databaseBackend.updateConversation(conversation); - } - - if (mucOptions.mamSupport()) { - getMessageArchiveService().catchupMUC(conversation); - } - if (mucOptions.membersOnly() && mucOptions.nonanonymous()) { - fetchConferenceMembers(conversation); - } - sendUnsentMessages(conversation); - } - - @Override - public void onConferenceConfigurationFetched(Conversation conversation) { - join(conversation); - } - - @Override - public void onFetchFailed(final Conversation conversation, Element error) { - join(conversation); - fetchConferenceConfiguration(conversation); - } - }); + fetchConferenceConfiguration(conversation, new ConferenceConfigurationFetched(onConferenceJoined)); updateConversationUi(); } else { account.pendingConferenceJoins.add(conversation); @@ -1704,37 +1690,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } - private void fetchConferenceMembers(final Conversation conversation) { - final Account account = conversation.getAccount(); - final String[] affiliations = {"member","admin","owner"}; - OnIqPacketReceived callback = new OnIqPacketReceived() { - - private int i = 0; - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element query = packet.query("http://jabber.org/protocol/muc#admin"); - if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { - for(Element child : query.getChildren()) { - if ("item".equals(child.getName())) { - conversation.getMucOptions().putMember(child.getAttributeAsJid("jid")); - } - } - } else { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not request affiliation "+affiliations[i]+" in "+conversation.getJid().toBareJid()); - } - ++i; - if (i >= affiliations.length) { - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": retrieved members for "+conversation.getJid().toBareJid()+": "+conversation.getMucOptions().getMembers()); - } - } - }; - for(String affiliation : affiliations) { - sendIqPacket(account, mIqGenerator.queryAffiliation(conversation, affiliation), callback); - } - Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching members for "+conversation.getName()); - } - public void providePasswordForMuc(Conversation conversation, String password) { if (conversation.getMode() == Conversation.MODE_MULTI) { conversation.getMucOptions().setPassword(password); @@ -1889,40 +1844,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void fetchConferenceConfiguration(final Conversation conversation, final OnConferenceConfigurationFetched callback) { - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(conversation.getJid().toBareJid()); - request.query("http://jabber.org/protocol/disco#info"); - sendIqPacket(conversation.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Element query = packet.findChild("query","http://jabber.org/protocol/disco#info"); - if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { - ArrayList features = new ArrayList<>(); - for (Element child : query.getChildren()) { - if (child != null && child.getName().equals("feature")) { - String var = child.getAttribute("var"); - if (var != null) { - features.add(var); - } - } - } - Element form = query.findChild("x", "jabber:x:data"); - if (form != null) { - conversation.getMucOptions().updateFormData(Data.parse(form)); - } - conversation.getMucOptions().updateFeatures(features); - if (callback != null) { - callback.onConferenceConfigurationFetched(conversation); - } - Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetched muc configuration for " + conversation.getJid().toBareJid() + " - " + features.toString()); - updateConversationUi(); - } else if (packet.getType() == IqPacket.TYPE.ERROR) { - if (callback != null) { - callback.onFetchFailed(conversation, packet.getError()); - } - } - } - }); + IqPacket request = ServiceDiscoveryIqPacketGenerator.generateRequest(conversation.getJid().toBareJid()); + XmppSendUtil.sendIqPacket(conversation.getAccount(), request, new ConferenceServiceDiscoveryReceived(conversation, callback)); } public void pushConferenceConfiguration(final Conversation conversation, final Bundle options, final OnConferenceOptionsPushed callback) { @@ -2466,10 +2389,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return this.mIqGenerator; } - public IqParser getIqParser() { - return this.mIqParser; - } - public JingleConnectionManager getJingleConnectionManager() { return this.mJingleConnectionManager; } @@ -2529,7 +2448,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void onIqPacketReceived(final Account account, final IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { account.getBlocklist().add(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + UiUpdateHelper.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); } } }); @@ -2544,7 +2463,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void onIqPacketReceived(final Account account, final IqPacket packet) { if (packet.getType() == IqPacket.TYPE.RESULT) { account.getBlocklist().remove(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + UiUpdateHelper.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); } } }); @@ -2587,15 +2506,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { if (!account.inProgressDiscoFetches.contains(key)) { account.inProgressDiscoFetches.add(key); - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(jid); - request.query("http://jabber.org/protocol/disco#info"); + IqPacket request = ServiceDiscoveryIqPacketGenerator.generateRequest(jid); Log.d(Config.LOGTAG,account.getJid().toBareJid()+": making disco request for "+key.second+" to "+jid); sendIqPacket(account, request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket discoPacket) { - if (discoPacket.getType() == IqPacket.TYPE.RESULT) { - ServiceDiscoveryResult disco = new ServiceDiscoveryResult(discoPacket); + ServiceDiscoveryResult disco = ServiceDiscoveryIqPacketParser.parse(discoPacket); + if (null != disco) { if (presence.getVer().equals(disco.getVer())) { databaseBackend.insertDiscoveryResult(disco); injectServiceDiscorveryResult(account.getRoster(), presence.getHash(), presence.getVer(), disco); @@ -2657,15 +2574,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa void informUser(int r); } - public interface OnMoreMessagesLoaded { - void onMoreMessagesLoaded(int count, Conversation conversation); - - void informUser(int r); - - void setLoadingInProgress(); - boolean isLoadingInProgress(); - } public interface OnAccountPasswordChanged { void onPasswordChangeSucceeded(); @@ -2694,10 +2603,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public interface OnCaptchaRequested { - void onCaptchaRequested(Account account, - String id, - Data data, - Bitmap captcha); + void onCaptchaRequested(Account account, String id, Data data, Bitmap captcha); } public interface OnRosterUpdate { @@ -2708,16 +2614,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa void onMucRosterUpdate(); } - public interface OnConferenceConfigurationFetched { - void onConferenceConfigurationFetched(Conversation conversation); - - void onFetchFailed(Conversation conversation, Element error); - } - - public interface OnConferenceJoined { - void onConferenceJoined(Conversation conversation); - } - public interface OnConferenceOptionsPushed { void onPushSucceeded(); diff --git a/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java b/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java new file mode 100644 index 00000000..6ad413b4 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java @@ -0,0 +1,422 @@ +package de.thedevstack.conversationsplus.services.mam; + +import android.util.Pair; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +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.services.XmppConnectionService; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { + + private final XmppConnectionService mXmppConnectionService; + + private final HashSet queries = new HashSet<>(); + private final ArrayList pendingQueries = new ArrayList<>(); + + public enum PagingOrder { + NORMAL, + REVERSE + }; + + public MessageArchiveService(final XmppConnectionService service) { + this.mXmppConnectionService = service; + } + + private void catchup(final Account account) { + synchronized (this.queries) { + for(Iterator iterator = this.queries.iterator(); iterator.hasNext();) { + Query query = iterator.next(); + if (query.getAccount() == account) { + iterator.remove(); + } + } + } + Pair pair = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); + long startCatchup = pair == null ? 0 : pair.first; + long endCatchup = account.getXmppConnection().getLastSessionEstablished(); + final Query query; + if (startCatchup == 0) { + return; + } else if (endCatchup - startCatchup >= Config.MAM_MAX_CATCHUP) { + startCatchup = endCatchup - Config.MAM_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); + } + } + query = new Query(account, startCatchup, endCatchup); + } else { + query = new Query(account, startCatchup, endCatchup); + query.reference = pair.second; + } + this.queries.add(query); + this.execute(query); + } + + public void catchupMUC(final Conversation conversation) { + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + query(conversation, + 0, + System.currentTimeMillis()); + } else { + query(conversation, + conversation.getLastMessageTransmitted(), + System.currentTimeMillis()); + } + } + + public Query query(final Conversation conversation) { + if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { + return query(conversation, + 0, + System.currentTimeMillis()); + } else { + return query(conversation, + conversation.getLastMessageTransmitted(), + conversation.getAccount().getXmppConnection().getLastSessionEstablished()); + } + } + + public Query query(final Conversation conversation, long end) { + return this.query(conversation,conversation.getLastMessageTransmitted(),end); + } + + public Query query(Conversation conversation, long start, long end) { + return this.query(conversation, start, end, null); + } + + public Query query(Conversation conversation, long start, long end, OnMoreMessagesLoaded callback) { + synchronized (this.queries) { + if (start > end) { + return null; + } + final Query query = new Query(conversation, start, end,PagingOrder.REVERSE); + query.reference = conversation.getFirstMamReference(); + this.queries.add(query); + if (null != callback) { + query.setCallback(callback); + } + this.execute(query); + return query; + } + } + + public void executePendingQueries(final Account account) { + List pending = new ArrayList<>(); + synchronized(this.pendingQueries) { + for(Iterator iterator = this.pendingQueries.iterator(); iterator.hasNext();) { + Query query = iterator.next(); + if (query.getAccount() == account) { + pending.add(query); + iterator.remove(); + } + } + } + for(Query query : pending) { + this.execute(query); + } + } + + private void execute(final Query query) { + final Account account= query.getAccount(); + if (account.getStatus() == Account.State.ONLINE) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": running mam query " + query.toString()); + IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); + this.mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.TIMEOUT) { + synchronized (MessageArchiveService.this.queries) { + MessageArchiveService.this.queries.remove(query); + if (query.hasCallback()) { + query.callback(); + } + } + } else if (packet.getType() != IqPacket.TYPE.RESULT) { + Logging.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": error executing mam: " + packet.toString()); + finalizeQuery(query, true); + } + } + }); + } else { + synchronized (this.pendingQueries) { + this.pendingQueries.add(query); + } + } + } + + private void finalizeQuery(Query query, boolean done) { + synchronized (this.queries) { + this.queries.remove(query); + } + 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(); + } else { + if (null != conversation) { + conversation.setHasMessagesLeftOnServer(query.getMessageCount() > 0); + } + this.mXmppConnectionService.updateConversationUi(); + } + } + + public boolean queryInProgress(Conversation conversation, OnMoreMessagesLoaded callback) { + synchronized (this.queries) { + for(Query query : queries) { + if (query.conversation == conversation) { + if (!query.hasCallback() && callback != null) { + query.setCallback(callback); + } + return true; + } + } + return false; + } + } + + public boolean queryInProgress(Conversation conversation) { + return queryInProgress(conversation, null); + } + + public void processFin(Element fin, Jid from) { + if (fin == null) { + return; + } + Query query = findQuery(fin.getAttribute("queryid")); + if (query == null || !query.validFrom(from)) { + return; + } + boolean complete = fin.getAttributeAsBoolean("complete"); + Element set = fin.findChild("set","http://jabber.org/protocol/rsm"); + Element last = set == null ? null : set.findChild("last"); + Element first = set == null ? null : set.findChild("first"); + Element relevant = query.getPagingOrder() == PagingOrder.NORMAL ? last : first; + boolean abort = (query.getStart() == 0 && query.getTotalCount() >= Config.PAGE_SIZE) || query.getTotalCount() >= Config.MAM_MAX_MESSAGES; + if (query.getConversation() != null) { + query.getConversation().setFirstMamReference(first == null ? null : first.getContent()); + } + if (complete || relevant == null || abort) { + final boolean done = (complete || query.getMessageCount() == 0) && query.getStart() == 0; + this.finalizeQuery(query, done); + Logging.d(Config.LOGTAG,query.getAccount().getJid().toBareJid()+": finished mam after "+query.getTotalCount()+" messages. messages left="+Boolean.toString(!done)); + if (query.getWith() == null && query.getMessageCount() > 0) { + mXmppConnectionService.getNotificationService().finishBacklog(true); + } + } else { + final Query nextQuery; + if (query.getPagingOrder() == PagingOrder.NORMAL) { + nextQuery = query.next(last == null ? null : last.getContent()); + } else { + nextQuery = query.prev(first == null ? null : first.getContent()); + } + this.execute(nextQuery); + this.finalizeQuery(query, false); + synchronized (this.queries) { + this.queries.add(nextQuery); + } + } + } + + public 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; + } + } + + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().mam()) { + this.catchup(account); + } + } + + public class Query { + private int totalCount = 0; + private int messageCount = 0; + private long start; + private long end; + private String queryId; + private String reference = null; + private Account account; + private Conversation conversation; + private PagingOrder pagingOrder = PagingOrder.NORMAL; + private OnMoreMessagesLoaded callback = null; + + + public Query(Conversation conversation, long start, long end) { + this(conversation.getAccount(), start, end); + this.conversation = conversation; + } + + public Query(Conversation conversation, long start, long end, PagingOrder order) { + this(conversation,start,end); + this.pagingOrder = order; + } + + public Query(Account account, long start, long end) { + this.account = account; + this.start = start; + this.end = end; + this.queryId = new BigInteger(50, ConversationsPlusApplication.getSecureRandom()).toString(32); + } + + private Query page(String reference) { + Query query = new Query(this.account,this.start,this.end); + query.reference = reference; + query.conversation = conversation; + query.totalCount = totalCount; + query.callback = callback; + return query; + } + + public Query next(String reference) { + Query query = page(reference); + query.pagingOrder = PagingOrder.NORMAL; + return query; + } + + public Query prev(String reference) { + Query query = page(reference); + query.pagingOrder = PagingOrder.REVERSE; + return query; + } + + public String getReference() { + return reference; + } + + public PagingOrder getPagingOrder() { + return this.pagingOrder; + } + + public String getQueryId() { + return queryId; + } + + public Jid getWith() { + return conversation == null ? null : conversation.getJid().toBareJid(); + } + + public boolean muc() { + return conversation != null && conversation.getMode() == Conversation.MODE_MULTI; + } + + public long getStart() { + return start; + } + + public void setCallback(OnMoreMessagesLoaded callback) { + this.callback = callback; + } + + public void callback() { + if (this.callback != null) { + this.callback.onMoreMessagesLoaded(messageCount,conversation); + if (messageCount <= 0) { + this.callback.informUser(R.string.no_more_history_on_server); + } + } + } + + public long getEnd() { + return end; + } + + public Conversation getConversation() { + return conversation; + } + + public Account getAccount() { + return this.account; + } + + public void incrementMessageCount() { + this.messageCount++; + this.totalCount++; + } + + public int getTotalCount() { + return this.totalCount; + } + + public int getMessageCount() { + return this.messageCount; + } + + public boolean validFrom(Jid from) { + if (muc()) { + return getWith().equals(from); + } else { + return (from == null) || account.getJid().toBareJid().equals(from.toBareJid()); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (this.muc()) { + builder.append("to="); + builder.append(this.getWith().toString()); + } else { + builder.append("with="); + if (this.getWith() == null) { + builder.append("*"); + } else { + builder.append(getWith().toString()); + } + } + builder.append(", start="); + builder.append(AbstractGenerator.getTimestamp(this.start)); + builder.append(", end="); + builder.append(AbstractGenerator.getTimestamp(this.end)); + if (this.reference!=null) { + if (this.pagingOrder == PagingOrder.NORMAL) { + builder.append(", after="); + } else { + builder.append(", before="); + } + builder.append(this.reference); + } + return builder.toString(); + } + + public boolean hasCallback() { + return this.callback != null; + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/mam/OnMoreMessagesLoaded.java b/src/main/java/de/thedevstack/conversationsplus/services/mam/OnMoreMessagesLoaded.java new file mode 100644 index 00000000..72e4b516 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/mam/OnMoreMessagesLoaded.java @@ -0,0 +1,13 @@ +package de.thedevstack.conversationsplus.services.mam; + +import de.thedevstack.conversationsplus.entities.Conversation; + +public interface OnMoreMessagesLoaded { + void onMoreMessagesLoaded(int count, Conversation conversation); + + void informUser(int r); + + void setLoadingInProgress(); + + boolean isLoadingInProgress(); +} \ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceConfigurationFetched.java b/src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceConfigurationFetched.java new file mode 100644 index 00000000..7e7222fd --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceConfigurationFetched.java @@ -0,0 +1,10 @@ +package de.thedevstack.conversationsplus.services.muc; + +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.xml.Element; + +public interface OnConferenceConfigurationFetched { + void onConferenceConfigurationFetched(Conversation conversation); + + void onFetchFailed(Conversation conversation, Element error); +} \ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceJoined.java b/src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceJoined.java new file mode 100644 index 00000000..5e9a0af5 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/muc/OnConferenceJoined.java @@ -0,0 +1,7 @@ +package de.thedevstack.conversationsplus.services.muc; + +import de.thedevstack.conversationsplus.entities.Conversation; + +public interface OnConferenceJoined { + void onConferenceJoined(Conversation conversation); +} \ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceConfigurationFetched.java b/src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceConfigurationFetched.java new file mode 100644 index 00000000..a386db88 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceConfigurationFetched.java @@ -0,0 +1,96 @@ +package de.thedevstack.conversationsplus.services.muc.listener; + +import android.util.Log; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.entities.MucOptions; +import de.thedevstack.conversationsplus.generator.PresenceGenerator; +import de.thedevstack.conversationsplus.persistance.DatabaseBackend; +import de.thedevstack.conversationsplus.services.muc.OnConferenceConfigurationFetched; +import de.thedevstack.conversationsplus.services.muc.OnConferenceJoined; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; + +/** + */ +public class ConferenceConfigurationFetched implements OnConferenceConfigurationFetched { + private OnConferenceJoined onConferenceJoined; + + public ConferenceConfigurationFetched(OnConferenceJoined onConferenceJoined) { + this.onConferenceJoined = onConferenceJoined; + } + + private void join(Conversation conversation) { + Account account = conversation.getAccount(); + final MucOptions mucOptions = conversation.getMucOptions(); + final Jid joinJid = mucOptions.getSelf().getFullJid(); + Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": joining conversation " + joinJid.toString()); + long lastMessageTransmitted = conversation.getLastMessageTransmitted(); + PresencePacket packet = PresenceGenerator.generateMucJoin(account, joinJid, mucOptions, lastMessageTransmitted); + XmppSendUtil.sendPresencePacket(account, packet); + if (onConferenceJoined != null) { + onConferenceJoined.onConferenceJoined(conversation); + } + if (!joinJid.equals(conversation.getJid())) { + conversation.setContactJid(joinJid); + DatabaseBackend.getInstance().updateConversation(conversation); + } + + if (mucOptions.mamSupport()) { + XmppConnectionServiceAccessor.xmppConnectionService.getMessageArchiveService().catchupMUC(conversation); + } + if (mucOptions.membersOnly() && mucOptions.nonanonymous()) { + fetchConferenceMembers(conversation); + } + XmppConnectionServiceAccessor.xmppConnectionService.sendUnsentMessages(conversation); + } + + @Override + public void onConferenceConfigurationFetched(Conversation conversation) { + join(conversation); + } + + @Override + public void onFetchFailed(final Conversation conversation, Element error) { + join(conversation); + XmppConnectionServiceAccessor.xmppConnectionService.fetchConferenceConfiguration(conversation); + } + + private void fetchConferenceMembers(final Conversation conversation) { + final Account account = conversation.getAccount(); + final String[] affiliations = {"member","admin","owner"}; + OnIqPacketReceived callback = new OnIqPacketReceived() { + + private int i = 0; + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element query = packet.query("http://jabber.org/protocol/muc#admin"); + if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { + for(Element child : query.getChildren()) { + if ("item".equals(child.getName())) { + conversation.getMucOptions().putMember(child.getAttributeAsJid("jid")); + } + } + } else { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not request affiliation "+affiliations[i]+" in "+conversation.getJid().toBareJid()); + } + ++i; + if (i >= affiliations.length) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": retrieved members for "+conversation.getJid().toBareJid()+": "+conversation.getMucOptions().getMembers()); + } + } + }; + for(String affiliation : affiliations) { + XmppSendUtil.sendIqPacket(account, XmppConnectionServiceAccessor.xmppConnectionService.getIqGenerator().queryAffiliation(conversation, affiliation), callback); + } + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching members for "+conversation.getName()); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceServiceDiscoveryReceived.java b/src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceServiceDiscoveryReceived.java new file mode 100644 index 00000000..5d1b8c8d --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/services/muc/listener/ConferenceServiceDiscoveryReceived.java @@ -0,0 +1,59 @@ +package de.thedevstack.conversationsplus.services.muc.listener; + +import android.util.Log; + +import java.util.ArrayList; + +import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.entities.Conversation; +import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.services.muc.OnConferenceConfigurationFetched; +import de.thedevstack.conversationsplus.utils.UiUpdateHelper; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.disco.ServiceDiscovery; +import de.thedevstack.conversationsplus.xmpp.forms.Data; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + */ +public class ConferenceServiceDiscoveryReceived implements OnIqPacketReceived { + private Conversation conversation; + private OnConferenceConfigurationFetched callback; + + public ConferenceServiceDiscoveryReceived(Conversation conversation, OnConferenceConfigurationFetched callback) { + this.conversation = conversation; + this.callback = callback; + } + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + Element query = packet.findChild(ServiceDiscovery.ELEMENT, ServiceDiscovery.NAMESPACE); + if (packet.getType() == IqPacket.TYPE.RESULT && query != null) { + ArrayList features = new ArrayList<>(); + for (Element child : query.getChildren()) { + if (child != null && child.getName().equals("feature")) { + String var = child.getAttribute("var"); + if (var != null) { + features.add(var); + } + } + } + Element form = query.findChild("x", "jabber:x:data"); + if (form != null) { + conversation.getMucOptions().updateFormData(Data.parse(form)); + } + conversation.getMucOptions().updateFeatures(features); + if (callback != null) { + callback.onConferenceConfigurationFetched(conversation); + } + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": fetched muc configuration for " + conversation.getJid().toBareJid() + " - " + features.toString()); + UiUpdateHelper.updateConversationUi(); + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + if (callback != null) { + callback.onFetchFailed(conversation, packet.getError()); + } + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java index 296f8f4f..53c3892d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/SettingsActivity.java @@ -24,7 +24,9 @@ import java.util.Locale; import de.duenndns.ssl.MemorizingTrustManager; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.services.ExportLogsService; +import de.thedevstack.conversationsplus.xmpp.XepRegistry; import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; @@ -187,7 +189,19 @@ public class SettingsActivity extends XmppActivity implements FileBackend.onFileTransferFolderChanged(); } else if ("img_transfer_folder".equals(name)) { FileBackend.onImageTransferFolderChanged(); - } + } else if ("send_entity_time".equals(name)) { + if (ConversationsPlusPreferences.sendEntityTime()) { + XepRegistry.enable("time"); + } else { + XepRegistry.disable("time"); + } + } else if ("send_software_info".equals(name)) { + if (ConversationsPlusPreferences.sendSoftwareVersion()) { + XepRegistry.enable("iq-version"); + } else { + XepRegistry.disable("iq-version"); + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationMoreMessagesLoadedListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationMoreMessagesLoadedListener.java index 8e2909ad..fd8b5782 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationMoreMessagesLoadedListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationMoreMessagesLoadedListener.java @@ -11,6 +11,7 @@ import java.util.List; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.services.mam.OnMoreMessagesLoaded; import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.ui.ConversationFragment; import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter; @@ -18,7 +19,7 @@ import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter; /** * This listener updates the UI when messages are loaded from the server. */ -public class ConversationMoreMessagesLoadedListener implements XmppConnectionService.OnMoreMessagesLoaded { +public class ConversationMoreMessagesLoadedListener implements OnMoreMessagesLoaded { private SwipyRefreshLayout swipeLayout; private List messageList; private ConversationFragment fragment; diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java index 6dc9f4a4..d401251d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/ConversationSwipeRefreshListener.java @@ -13,7 +13,7 @@ import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.services.MessageArchiveService; +import de.thedevstack.conversationsplus.services.mam.MessageArchiveService; import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.ui.ConversationFragment; import de.thedevstack.conversationsplus.ui.adapter.MessageAdapter; diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java index 7638caad..4477f074 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/UiUpdateHelper.java @@ -2,6 +2,7 @@ package de.thedevstack.conversationsplus.utils; import de.thedevstack.android.logcat.Logging; import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.xmpp.OnUpdateBlocklist; /** * Helper class to avoid passing the xmppConnectionService to everywhere just to update the UI. @@ -49,4 +50,12 @@ public class UiUpdateHelper { Logging.e("UiUpdateHelper", "XMPP Connection Service not initialized. MUC Roster Ui not updated."); } } + + public static void updateBlocklistUi(final OnUpdateBlocklist.Status status) { + if (null != UiUpdateHelper.xmppConnectionService) { + UiUpdateHelper.xmppConnectionService.updateBlocklistUi(status); + } else { + Logging.e("UiUpdateHelper", "XMPP Connection Service not initialized.Blocklist Ui not updated."); + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java index 11d05832..67b70ef6 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java @@ -8,9 +8,13 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; /** - * Created by tzur on 09.01.2016. + * */ public class XmppSendUtil { + public static void sendIqPacket(Account account, IqPacket packet) { + sendIqPacket(account, packet, null); + } + public static void sendIqPacket(Account account, IqPacket packet, OnIqPacketReceived callback) { final XmppConnection connection = account.getXmppConnection(); if (connection != null) { diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractXep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractXep.java new file mode 100644 index 00000000..96cbeb3e --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/AbstractXep.java @@ -0,0 +1,30 @@ +package de.thedevstack.conversationsplus.xmpp; + +/** + */ +public abstract class AbstractXep implements Xep { + protected boolean enabled = true; + + public AbstractXep() { + this(true); + } + + public AbstractXep(boolean enabled) { + this.enabled = enabled; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public final void enable() { + this.enabled = true; + } + + @Override + public final void disable() { + this.enabled = false; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketHandler.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketHandler.java new file mode 100644 index 00000000..cfd5172e --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/IqPacketHandler.java @@ -0,0 +1,12 @@ +package de.thedevstack.conversationsplus.xmpp; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + * + */ +public interface IqPacketHandler { + void handleIqPacket(Account account, IqPacket packet) throws IqPacketErrorException; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/Xep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/Xep.java new file mode 100644 index 00000000..058f345e --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/Xep.java @@ -0,0 +1,16 @@ +package de.thedevstack.conversationsplus.xmpp; + +/** + */ +public interface Xep { + boolean isEnabled(); + void enable(); + void disable(); + String xepNumber(); + String shortName(); + String name(); + String namespace(); + String featureNamespace(); + String elementName(); + IqPacketHandler handler(); +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/XepRegistry.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/XepRegistry.java new file mode 100644 index 00000000..89174c70 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/XepRegistry.java @@ -0,0 +1,51 @@ +package de.thedevstack.conversationsplus.xmpp; + +import java.util.Hashtable; + +import de.thedevstack.conversationsplus.xmpp.disco.FeatureRegistry; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketReceiver; + +/** + */ +public class XepRegistry { + private final Hashtable xeps = new Hashtable<>(); + private static final XepRegistry INSTANCE = new XepRegistry(); + + public static void enable(String shortName) { + if (INSTANCE.xeps.containsKey(shortName)) { + Xep xep = INSTANCE.xeps.get(shortName); + xep.enable(); + FeatureRegistry.remove(xep); + IqPacketReceiver.getInstance().registerIqPacketHandler(xep); + } + } + + public static void disable(String shortName) { + if (INSTANCE.xeps.containsKey(shortName)) { + Xep xep = INSTANCE.xeps.get(shortName); + xep.disable(); + FeatureRegistry.remove(xep); + IqPacketReceiver.getInstance().unregisterIqPacketHandler(xep); + } + } + + public static void add(Xep xep) { + INSTANCE.xeps.put(xep.shortName(), xep); + if (xep.isEnabled()) { + FeatureRegistry.add(xep); + IqPacketReceiver.getInstance().registerIqPacketHandler(xep); + } + } + + public static void remove(Xep xep) { + INSTANCE.xeps.remove(xep.shortName()); + FeatureRegistry.remove(xep); + IqPacketReceiver.getInstance().unregisterIqPacketHandler(xep); + } + + public static XepRegistry getInstance() { + return INSTANCE; + } + + private XepRegistry() {} +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java index 54db5346..2b366dbb 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java @@ -84,9 +84,11 @@ import de.thedevstack.conversationsplus.xmpp.mam.Mam; import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractAcknowledgeableStanza; import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractStanza; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketReceiver; import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; import de.thedevstack.conversationsplus.xmpp.stanzas.csi.ActivePacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.csi.Csi; import de.thedevstack.conversationsplus.xmpp.stanzas.csi.InactivePacket; import de.thedevstack.conversationsplus.xmpp.stanzas.streammgmt.AckPacket; import de.thedevstack.conversationsplus.xmpp.stanzas.streammgmt.EnablePacket; @@ -1092,7 +1094,7 @@ public class XmppConnection implements Runnable { } if (getFeatures().blocking() && !features.blockListRequested) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": Requesting block list"); - this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser()); + this.sendIqPacket(getIqGenerator().generateGetBlockList(), IqPacketReceiver.getInstance().getLegacyIqParser()); } for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) { listener.onAdvancedStreamFeaturesAvailable(account); @@ -1509,7 +1511,7 @@ public class XmppConnection implements Runnable { } public boolean csi() { - return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0"); + return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", Csi.NAMESPACE); } public boolean pep() { diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java new file mode 100644 index 00000000..1c266545 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/FeatureRegistry.java @@ -0,0 +1,90 @@ +package de.thedevstack.conversationsplus.xmpp.disco; + +import android.util.Base64; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.crypto.axolotl.AxolotlService; +import de.thedevstack.conversationsplus.xmpp.Xep; +import de.thedevstack.conversationsplus.xmpp.chatstate.ChatState; +import de.tzur.conversations.Settings; + +/** + */ +public final class FeatureRegistry { + public static final String IDENTITY_TYPE = "phone"; + + private static final String[] LEGACY_FEATURES = { + "urn:xmpp:jingle:1", + "urn:xmpp:jingle:apps:file-transfer:3", + "urn:xmpp:jingle:transports:s5b:1", + "urn:xmpp:jingle:transports:ibb:1", + "http://jabber.org/protocol/muc", + "jabber:x:conference", + "http://jabber.org/protocol/caps", + "http://jabber.org/protocol/disco#info", + "urn:xmpp:avatar:metadata+notify", + "http://jabber.org/protocol/nick+notify", + 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 features = new ArrayList<>(); + private static final FeatureRegistry INSTANCE = new FeatureRegistry(); + + public static void add(String featureNamespace) { + if (null != featureNamespace) { + INSTANCE.features.add(featureNamespace); + } + } + + public static void remove(String featureNamespace) { + if (null != featureNamespace && INSTANCE.features.contains(featureNamespace)) { + INSTANCE.features.remove(featureNamespace); + } + } + public static void add(Xep xep) { + add(xep.featureNamespace()); + } + + public static void remove(Xep xep) { + remove(xep.featureNamespace()); + } + + public static List getFeatures() { + Collections.sort(INSTANCE.features); + return INSTANCE.features; + } + + public static String getCapHash() { + StringBuilder s = new StringBuilder(); + s.append("client/" + IDENTITY_TYPE + "//" + ConversationsPlusApplication.getNameAndVersion() + "<"); + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + return null; + } + + for (String feature : getFeatures()) { + s.append(feature + "<"); + } + byte[] sha1 = md.digest(s.toString().getBytes()); + return new String(Base64.encode(sha1, Base64.DEFAULT)); + } + + private FeatureRegistry() { + this.features.addAll(Arrays.asList(LEGACY_FEATURES)); + if (Settings.CONFIRM_MESSAGE_RECEIVED) { + this.features.addAll(Arrays.asList(LEGACY_MESSAGE_CONFIRMATION_FEATURES)); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscovery.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscovery.java new file mode 100644 index 00000000..59a4212b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscovery.java @@ -0,0 +1,8 @@ +package de.thedevstack.conversationsplus.xmpp.disco; + +/** + */ +public interface ServiceDiscovery { + String NAMESPACE = "http://jabber.org/protocol/disco#info"; + String ELEMENT = "query"; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketGenerator.java new file mode 100644 index 00000000..8e1fc075 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketGenerator.java @@ -0,0 +1,38 @@ +package de.thedevstack.conversationsplus.xmpp.disco; + +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketGenerator; + +/** + */ +public class ServiceDiscoveryIqPacketGenerator { + + public static IqPacket generateRequest(Jid jid) { + IqPacket request = IqPacketGenerator.generateIqGetPacket(); + request.setTo(jid); + request.query(ServiceDiscovery.NAMESPACE); + + return request; + } + + public static IqPacket generateResponse(IqPacket packet) { + IqPacket responsePacket = IqPacketGenerator.generateIqResultPacket(); + responsePacket.setTo(packet.getFrom()); + responsePacket.setId(packet.getId()); + + Element query = responsePacket.addChild(ServiceDiscovery.ELEMENT, ServiceDiscovery.NAMESPACE); + query.setAttribute("node", packet.query().getAttribute("node")); + final Element identity = query.addChild("identity"); + identity.setAttribute("category", "client"); + identity.setAttribute("type", FeatureRegistry.IDENTITY_TYPE); + identity.setAttribute("name", ConversationsPlusApplication.getNameAndVersion()); + for (final String feature : FeatureRegistry.getFeatures()) { + query.addChild("feature").setAttribute("var", feature); + } + + return responsePacket; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketHandler.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketHandler.java new file mode 100644 index 00000000..6f1b0676 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketHandler.java @@ -0,0 +1,21 @@ +package de.thedevstack.conversationsplus.xmpp.disco; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; +import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException; +import de.thedevstack.conversationsplus.xmpp.exceptions.NotAllowedIqException; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + */ +public class ServiceDiscoveryIqPacketHandler implements IqPacketHandler { + @Override + public void handleIqPacket(Account account, IqPacket packet) throws IqPacketErrorException { + if (packet.getType() == IqPacket.TYPE.GET) { + XmppSendUtil.sendIqPacket(account, ServiceDiscoveryIqPacketGenerator.generateResponse(packet)); + } else { + throw new NotAllowedIqException(packet, "only type=get allowed for processing an service discovery (XEP-0030)"); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketParser.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketParser.java new file mode 100644 index 00000000..0ea0d752 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryIqPacketParser.java @@ -0,0 +1,15 @@ +package de.thedevstack.conversationsplus.xmpp.disco; + +import de.thedevstack.conversationsplus.entities.ServiceDiscoveryResult; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + */ +public class ServiceDiscoveryIqPacketParser { + public static ServiceDiscoveryResult parse(IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT) { + return new ServiceDiscoveryResult(packet); + } + return null; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryXep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryXep.java new file mode 100644 index 00000000..6a21c71b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/disco/ServiceDiscoveryXep.java @@ -0,0 +1,51 @@ +package de.thedevstack.conversationsplus.xmpp.disco; + +import de.thedevstack.conversationsplus.xmpp.AbstractXep; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; + +/** + */ +public class ServiceDiscoveryXep extends AbstractXep { + public ServiceDiscoveryXep() { + super(); + } + + public ServiceDiscoveryXep(boolean enabled) { + super(enabled); + } + + @Override + public String xepNumber() { + return "0030"; + } + + @Override + public String shortName() { + return "disco"; + } + + @Override + public String name() { + return "Service Discovery"; + } + + @Override + public String namespace() { + return ServiceDiscovery.NAMESPACE; + } + + @Override + public String featureNamespace() { + return ServiceDiscovery.NAMESPACE; + } + + @Override + public String elementName() { + return ServiceDiscovery.ELEMENT; + } + + @Override + public IqPacketHandler handler() { + return new ServiceDiscoveryIqPacketHandler(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/NotAllowedIqException.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/NotAllowedIqException.java new file mode 100644 index 00000000..45bf231a --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/exceptions/NotAllowedIqException.java @@ -0,0 +1,11 @@ +package de.thedevstack.conversationsplus.xmpp.exceptions; + +import de.thedevstack.conversationsplus.xml.Element; + +/** + */ +public class NotAllowedIqException extends IqPacketErrorException { + public NotAllowedIqException(Element context, String errorMessage) { + super(context, errorMessage); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersion.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersion.java new file mode 100644 index 00000000..e90ccb5a --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersion.java @@ -0,0 +1,9 @@ +package de.thedevstack.conversationsplus.xmpp.iqversion; + +/** + * + */ +public interface IqVersion { + String NAMESPACE = "jabber:iq:version"; + String ELEMENT = "query"; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacket.java deleted file mode 100644 index a3cecdac..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacket.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.thedevstack.conversationsplus.xmpp.iqversion; - -import android.os.Build; - -import de.thedevstack.conversationsplus.ConversationsPlusApplication; -import de.thedevstack.conversationsplus.xml.Element; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; - -/** - * Representation of an software version packet as defined in XEP-0092. - * @see http://xmpp.org/extensions/xep-0092.html - */ -public class IqVersionPacket extends IqPacket { - public static final String NAMESPACE = "jabber:iq:version"; - - IqVersionPacket() { - super(IqPacket.TYPE.RESULT); - Element query = this.addChild("query", NAMESPACE); - query.addChild("name").setContent(ConversationsPlusApplication.getName()); - query.addChild("version").setContent(ConversationsPlusApplication.getVersion()); - query.addChild("os").setContent("Android " + Build.VERSION.RELEASE); - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketGenerator.java index 97a7e90d..c4961c1d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketGenerator.java @@ -1,6 +1,12 @@ package de.thedevstack.conversationsplus.xmpp.iqversion; +import android.os.Build; + +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketGenerator; /** * Generates the IQ Packets for Software Version @@ -26,12 +32,17 @@ public final class IqVersionPacketGenerator { * @param packet the packet to respond to * @return */ - public static IqVersionPacket generateResponse(IqPacket packet) { - IqVersionPacket iqVersionPacket = new IqVersionPacket(); - iqVersionPacket.setTo(packet.getFrom()); - iqVersionPacket.setId(packet.getId()); + public static IqPacket generateResponse(IqPacket packet) { + IqPacket responsePacket = IqPacketGenerator.generateIqResultResponse(packet); + + Element query = responsePacket.addChild(IqVersion.ELEMENT, IqVersion.NAMESPACE); + query.addChild("name").setContent(ConversationsPlusApplication.getName()); + query.addChild("version").setContent(ConversationsPlusApplication.getVersion()); + if (ConversationsPlusPreferences.sendOsInformation()) { + query.addChild("os").setContent("Android " + Build.VERSION.RELEASE); + } - return iqVersionPacket; + return responsePacket; } private IqVersionPacketGenerator() {} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketHandler.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketHandler.java new file mode 100644 index 00000000..7462d0cb --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/IqVersionPacketHandler.java @@ -0,0 +1,21 @@ +package de.thedevstack.conversationsplus.xmpp.iqversion; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; +import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException; +import de.thedevstack.conversationsplus.xmpp.exceptions.NotAllowedIqException; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + */ +public class IqVersionPacketHandler implements IqPacketHandler { + @Override + public void handleIqPacket(Account account, IqPacket packet) throws IqPacketErrorException { + if (packet.getType() == IqPacket.TYPE.GET) { + XmppSendUtil.sendIqPacket(account, IqVersionPacketGenerator.generateResponse(packet)); + } else { + throw new NotAllowedIqException(packet, "only type=get allowed for processing an Software Version (XEP-0092)"); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/SoftwareVersionXep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/SoftwareVersionXep.java new file mode 100644 index 00000000..9cd69bb2 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/iqversion/SoftwareVersionXep.java @@ -0,0 +1,51 @@ +package de.thedevstack.conversationsplus.xmpp.iqversion; + +import de.thedevstack.conversationsplus.xmpp.AbstractXep; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; + +/** + */ +public class SoftwareVersionXep extends AbstractXep { + public SoftwareVersionXep() { + super(); + } + + public SoftwareVersionXep(boolean enabled) { + super(enabled); + } + + @Override + public String xepNumber() { + return "0092"; + } + + @Override + public String shortName() { + return "iq-version"; + } + + @Override + public String name() { + return "Software Version"; + } + + @Override + public String namespace() { + return IqVersion.NAMESPACE; + } + + @Override + public String featureNamespace() { + return IqVersion.NAMESPACE; + } + + @Override + public String elementName() { + return IqVersion.ELEMENT; + } + + @Override + public IqPacketHandler handler() { + return new IqVersionPacketHandler(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/openpgp/OpenPgpXep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/openpgp/OpenPgpXep.java new file mode 100644 index 00000000..15229bf9 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/openpgp/OpenPgpXep.java @@ -0,0 +1,48 @@ +package de.thedevstack.conversationsplus.xmpp.openpgp; + +import de.thedevstack.conversationsplus.xmpp.AbstractXep; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; + +/** + */ +public class OpenPgpXep extends AbstractXep { + public static final String ENCRYPTED_NAMESPACE = "jabber:x:encrypted"; + public static final String ENCRYPTED_ELEMENT = "x"; + public static final String SIGNED_NAMESPACE = "jabber:x:signed"; + public static final String SIGNED_ELEMENT = "x"; + + @Override + public String xepNumber() { + return "0027"; + } + + @Override + public String shortName() { + return "openpgp"; + } + + @Override + public String name() { + return "Current Jabber OpenPGP Usage"; + } + + @Override + public String namespace() { + return null; + } + + @Override + public String featureNamespace() { + return null; + } + + @Override + public String elementName() { + return null; + } + + @Override + public IqPacketHandler handler() { + return null; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/Ping.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/Ping.java new file mode 100644 index 00000000..ac201c01 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/Ping.java @@ -0,0 +1,8 @@ +package de.thedevstack.conversationsplus.xmpp.ping; + +/** + */ +public interface Ping { + String NAMESPACE = "urn:xmpp:ping"; + String ELEMENT = "ping"; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingPacketHandler.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingPacketHandler.java new file mode 100644 index 00000000..14e8f57c --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingPacketHandler.java @@ -0,0 +1,22 @@ +package de.thedevstack.conversationsplus.xmpp.ping; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; +import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException; +import de.thedevstack.conversationsplus.xmpp.exceptions.NotAllowedIqException; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketGenerator; + +/** + */ +public class PingPacketHandler implements IqPacketHandler { + @Override + public void handleIqPacket(Account account, IqPacket packet) throws IqPacketErrorException { + if (packet.getType() == IqPacket.TYPE.GET) { + XmppSendUtil.sendIqPacket(account, IqPacketGenerator.generateIqResultResponse(packet)); + } else { + throw new NotAllowedIqException(packet, "only type=get allowed for processing an XMPP Ping (XEP-0199)"); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingXep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingXep.java new file mode 100644 index 00000000..6036a64f --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/ping/PingXep.java @@ -0,0 +1,51 @@ +package de.thedevstack.conversationsplus.xmpp.ping; + +import de.thedevstack.conversationsplus.xmpp.AbstractXep; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; + +/** + */ +public class PingXep extends AbstractXep { + public PingXep() { + super(); + } + + public PingXep(boolean enabled) { + super(enabled); + } + + @Override + public String xepNumber() { + return "0199"; + } + + @Override + public String shortName() { + return "ping"; + } + + @Override + public String name() { + return "XMPP Ping"; + } + + @Override + public String namespace() { + return Ping.NAMESPACE; + } + + @Override + public String featureNamespace() { + return Ping.NAMESPACE; + } + + @Override + public String elementName() { + return Ping.ELEMENT; + } + + @Override + public IqPacketHandler handler() { + return new PingPacketHandler(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/ErrorIqPacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/ErrorIqPacket.java new file mode 100644 index 00000000..3f87202e --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/ErrorIqPacket.java @@ -0,0 +1,16 @@ +package de.thedevstack.conversationsplus.xmpp.stanzas; + +import de.thedevstack.conversationsplus.xml.Element; + +/** + */ +public class ErrorIqPacket extends IqPacket { + public final static String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas"; + + public ErrorIqPacket(IqErrorCondition condition) { + super(IqPacket.TYPE.ERROR); + final Element error = this.addChild("error"); + error.setAttribute("type", condition.getType().toString()); + error.addChild(condition.toString(), NAMESPACE); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorCondition.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorCondition.java new file mode 100644 index 00000000..688ea007 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorCondition.java @@ -0,0 +1,48 @@ +package de.thedevstack.conversationsplus.xmpp.stanzas; + +/** + */ +public enum IqErrorCondition { + BAD_REQUEST(IqErrorType.MODIFY), + CONFLICT(IqErrorType.CANCEL), + FEATURE_NOT_IMPLEMENTED(IqErrorType.CANCEL), + FORBIDDEN(IqErrorType.AUTH), + GONE(IqErrorType.MODIFY), + INTERNAL_SERVER_ERROR(IqErrorType.WAIT), + ITEM_NOT_FOUND(IqErrorType.CANCEL), + JID_MALFORMED(IqErrorType.MODIFY), + NOT_ACCEPTABLE(IqErrorType.MODIFY), + NOT_ALLOWED(IqErrorType.CANCEL), + NOT_AUTHORIZED(IqErrorType.AUTH), + PAYMENT_REQUIRED(IqErrorType.AUTH), + RECIPIENT_UNAVAILABLE(IqErrorType.WAIT), + REDIRECT(IqErrorType.MODIFY), + REGISTRATION_REQUIRED(IqErrorType.AUTH), + REMOTE_SERVER_NOT_FOUND(IqErrorType.CANCEL), + REMOTE_SERVER_TIMEOUT(IqErrorType.WAIT), + RESOURCE_CONSTRAINT(IqErrorType.WAIT), + SERVICE_UNAVAILABLE(IqErrorType.CANCEL), + SUBSCRIPTION_REQUIRED(IqErrorType.AUTH), + UNDEFINED_CONDITION(IqErrorType.CANCEL), + UNEXPECTED_REQUEST(IqErrorType.WAIT) + ; + + private IqErrorType type; + + IqErrorCondition(IqErrorType type) { + this.type = type; + } + + public IqErrorType getType() { + return type; + } + + public static IqErrorCondition fromString(String condition) { + return (null != condition) ? Enum.valueOf(IqErrorCondition.class, condition.toUpperCase().replaceAll("-", "_")) : null; + } + + @Override + public String toString() { + return super.toString().toLowerCase().replaceAll("_", "-"); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorType.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorType.java new file mode 100644 index 00000000..4ebe0f41 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqErrorType.java @@ -0,0 +1,12 @@ +package de.thedevstack.conversationsplus.xmpp.stanzas; + +/** + */ +public enum IqErrorType { + CANCEL, MODIFY, AUTH, WAIT; + + @Override + public String toString() { + return super.toString().toLowerCase(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java index d86831e0..415d5de3 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacket.java @@ -59,6 +59,10 @@ public class IqPacket extends AbstractAcknowledgeableStanza { } } + public Element getChildElement() { + return this.children.get(0); + } + public IqPacket generateResponse(final TYPE type) { final IqPacket packet = new IqPacket(type); packet.setTo(this.getFrom()); diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java index 27e6607f..01204a96 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketGenerator.java @@ -1,5 +1,7 @@ package de.thedevstack.conversationsplus.xmpp.stanzas; +import de.thedevstack.conversationsplus.xml.Element; + /** * Created by tzur on 15.01.2016. */ @@ -21,8 +23,19 @@ public final class IqPacketGenerator { return generateIqPacket(IqPacket.TYPE.RESULT); } - public static IqPacket generateIqErrorPacket() { - return generateIqPacket(IqPacket.TYPE.ERROR); + public static IqPacket generateIqResultResponse(IqPacket packet) { + IqPacket responsePacket = generateIqResultPacket(); + responsePacket.setTo(packet.getFrom()); + responsePacket.setId(packet.getId()); + + return responsePacket; + } + + public static ErrorIqPacket generateIqErrorPacketResponse(IqErrorCondition condition, IqPacket packet) { + ErrorIqPacket errorPacket = new ErrorIqPacket(condition); + errorPacket.setTo(packet.getFrom()); + errorPacket.setId(packet.getId()); + return errorPacket; } private IqPacketGenerator() { diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketReceiver.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketReceiver.java new file mode 100644 index 00000000..bfae0355 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/IqPacketReceiver.java @@ -0,0 +1,96 @@ +package de.thedevstack.conversationsplus.xmpp.stanzas; + +import java.util.Hashtable; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.parser.IqParser; +import de.thedevstack.conversationsplus.utils.XmppConnectionServiceAccessor; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; +import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.Xep; +import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException; +import de.thedevstack.conversationsplus.xmpp.exceptions.NotAllowedIqException; + +/** + */ +public final class IqPacketReceiver implements OnIqPacketReceived { + private final Hashtable iqPacketHandlers = new Hashtable<>(); + private final IqParser legacyIqParser; + private static IqPacketReceiver INSTANCE; + + public synchronized static IqPacketReceiver getInstance() { + if (null == INSTANCE) { + IqPacketReceiver.INSTANCE = new IqPacketReceiver(); + } + return INSTANCE; + } + + private IqPacketReceiver() { + this.legacyIqParser = new IqParser(XmppConnectionServiceAccessor.xmppConnectionService); + } + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + String key = generateKey(packet); + if (null != key && this.iqPacketHandlers.containsKey(key)) { + try { + IqPacketHandler iqPacketHandler = this.iqPacketHandlers.get(key); + iqPacketHandler.handleIqPacket(account, packet); + } catch (NotAllowedIqException e) { + ErrorIqPacket errorIqPacket = IqPacketGenerator.generateIqErrorPacketResponse(IqErrorCondition.NOT_ALLOWED, packet); + XmppSendUtil.sendIqPacket(account, errorIqPacket); + } catch (IqPacketErrorException e) { + + } + } else { + this.legacyIqParser.onIqPacketReceived(account, packet); + } + } + + public static String generateKey(IqPacket packet) { + Element childElement = packet.getChildElement(); + return generateKey(childElement.getName(), childElement.getNamespace()); + } + + public static String generateKey(String elementName, String xmlns) { + if (null == elementName && null == xmlns) { + return null; + } + return ((null != elementName) ? elementName : "") + ((null != xmlns) ? ("-" + xmlns) : ""); + } + + public void registerIqPacketHandler(String elementName, String xmlns, IqPacketHandler iqPacketHandler) { + String key = generateKey(elementName, xmlns); + if (null != key) { + this.iqPacketHandlers.put(key, iqPacketHandler); + } + } + + public void unregisterIqPacketHandler(String elementName, String xmlns) { + String key = generateKey(elementName, xmlns); + if (null != key && this.iqPacketHandlers.containsKey(key)) { + this.iqPacketHandlers.remove(key); + } + } + + public void registerIqPacketHandler(Xep xep) { + String key = generateKey(xep.elementName(), xep.namespace()); + if (null != key && null != xep.handler()) { + this.iqPacketHandlers.put(key, xep.handler()); + } + } + + public void unregisterIqPacketHandler(Xep xep) { + String key = generateKey(xep.elementName(), xep.namespace()); + if (null != key && this.iqPacketHandlers.containsKey(key)) { + this.iqPacketHandlers.remove(key); + } + } + + @Deprecated + public IqParser getLegacyIqParser() { + return this.legacyIqParser; + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/ActivePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/ActivePacket.java index 47538a64..52b2e04d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/ActivePacket.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/ActivePacket.java @@ -5,6 +5,6 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractStanza; public class ActivePacket extends AbstractStanza { public ActivePacket() { super("active"); - setAttribute("xmlns", "urn:xmpp:csi:0"); + setAttribute("xmlns", Csi.NAMESPACE); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/Csi.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/Csi.java new file mode 100644 index 00000000..e002b093 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/Csi.java @@ -0,0 +1,7 @@ +package de.thedevstack.conversationsplus.xmpp.stanzas.csi; + +/** + */ +public interface Csi { + String NAMESPACE = "urn:xmpp:csi:0"; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/InactivePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/InactivePacket.java index ca5904a5..35486858 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/InactivePacket.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/stanzas/csi/InactivePacket.java @@ -5,6 +5,6 @@ import de.thedevstack.conversationsplus.xmpp.stanzas.AbstractStanza; public class InactivePacket extends AbstractStanza { public InactivePacket() { super("inactive"); - setAttribute("xmlns", "urn:xmpp:csi:0"); + setAttribute("xmlns", Csi.NAMESPACE); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTime.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTime.java new file mode 100644 index 00000000..7382f37a --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTime.java @@ -0,0 +1,8 @@ +package de.thedevstack.conversationsplus.xmpp.time; + +/** + */ +public interface EntityTime { + String NAMESPACE = "urn:xmpp:time"; + String ELEMENT = "time"; +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTimeXep.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTimeXep.java new file mode 100644 index 00000000..fb721bce --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/EntityTimeXep.java @@ -0,0 +1,51 @@ +package de.thedevstack.conversationsplus.xmpp.time; + +import de.thedevstack.conversationsplus.xmpp.AbstractXep; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; + +/** + */ +public class EntityTimeXep extends AbstractXep { + public EntityTimeXep() { + super(); + } + + public EntityTimeXep(boolean enabled) { + super(enabled); + } + + @Override + public String xepNumber() { + return "0202"; + } + + @Override + public String shortName() { + return "time"; + } + + @Override + public String name() { + return "Entity Time"; + } + + @Override + public String namespace() { + return EntityTime.NAMESPACE; + } + + @Override + public String featureNamespace() { + return EntityTime.NAMESPACE; + } + + @Override + public String elementName() { + return EntityTime.ELEMENT; + } + + @Override + public IqPacketHandler handler() { + return new TimeIqPacketHandler(); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimeIqPacketHandler.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimeIqPacketHandler.java new file mode 100644 index 00000000..961c649b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimeIqPacketHandler.java @@ -0,0 +1,21 @@ +package de.thedevstack.conversationsplus.xmpp.time; + +import de.thedevstack.conversationsplus.entities.Account; +import de.thedevstack.conversationsplus.utils.XmppSendUtil; +import de.thedevstack.conversationsplus.xmpp.IqPacketHandler; +import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException; +import de.thedevstack.conversationsplus.xmpp.exceptions.NotAllowedIqException; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; + +/** + */ +public class TimeIqPacketHandler implements IqPacketHandler { + @Override + public void handleIqPacket(Account account, IqPacket packet) throws IqPacketErrorException { + if (packet.getType() == IqPacket.TYPE.GET) { + XmppSendUtil.sendIqPacket(account, TimePacketGenerator.generateResponse(packet)); + } else { + throw new NotAllowedIqException(packet, "only type=get allowed for processing an Entity Time (XEP-0202)"); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacket.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacket.java deleted file mode 100644 index d8756d41..00000000 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacket.java +++ /dev/null @@ -1,35 +0,0 @@ -package de.thedevstack.conversationsplus.xmpp.time; - -import java.util.Locale; -import java.util.TimeZone; - -import de.thedevstack.conversationsplus.generator.AbstractGenerator; -import de.thedevstack.conversationsplus.xml.Element; -import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; - -/** - * Representation of an software version packet as defined in XEP-0202. - * @see http://xmpp.org/extensions/xep-0202.html - */ -public class TimePacket extends IqPacket { - public static final String NAMESPACE = "urn:xmpp:time"; - - TimePacket() { - super(IqPacket.TYPE.RESULT); - Element time = this.addChild("time", NAMESPACE); - final long now = System.currentTimeMillis(); - time.addChild("utc").setContent(AbstractGenerator.getTimestamp(now)); - TimeZone ourTimezone = TimeZone.getDefault(); - long offsetSeconds = ourTimezone.getOffset(now) / 1000; - long offsetMinutes = Math.abs((offsetSeconds % 3600) / 60); - long offsetHours = offsetSeconds / 3600; - String hours; - if (offsetHours < 0) { - hours = String.format(Locale.US, "%03d", offsetHours); - } else { - hours = String.format(Locale.US, "%02d", offsetHours); - } - String minutes = String.format(Locale.US, "%02d", offsetMinutes); - time.addChild("tzo").setContent(hours + ":" + minutes); - } -} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacketGenerator.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacketGenerator.java index 344beb9e..ef381d4d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacketGenerator.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/time/TimePacketGenerator.java @@ -1,7 +1,12 @@ package de.thedevstack.conversationsplus.xmpp.time; -import de.thedevstack.conversationsplus.xmpp.iqversion.IqVersionPacket; +import java.util.Locale; +import java.util.TimeZone; + +import de.thedevstack.conversationsplus.generator.AbstractGenerator; +import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacketGenerator; /** * Generates the IQ Packets for Entity Time @@ -26,12 +31,26 @@ public final class TimePacketGenerator { * @param packet * @return */ - public static TimePacket generateResponse(IqPacket packet) { - TimePacket timePacket = new TimePacket(); - timePacket.setTo(packet.getFrom()); - timePacket.setId(packet.getId()); + public static IqPacket generateResponse(IqPacket packet) { + IqPacket responsePacket = IqPacketGenerator.generateIqResultResponse(packet); + + Element time = responsePacket.addChild(EntityTime.ELEMENT, EntityTime.NAMESPACE); + final long now = System.currentTimeMillis(); + time.addChild("utc").setContent(AbstractGenerator.getTimestamp(now)); + TimeZone ourTimezone = TimeZone.getDefault(); + long offsetSeconds = ourTimezone.getOffset(now) / 1000; + long offsetMinutes = Math.abs((offsetSeconds % 3600) / 60); + long offsetHours = offsetSeconds / 3600; + String hours; + if (offsetHours < 0) { + hours = String.format(Locale.US, "%03d", offsetHours); + } else { + hours = String.format(Locale.US, "%02d", offsetHours); + } + String minutes = String.format(Locale.US, "%02d", offsetMinutes); + time.addChild("tzo").setContent(hours + ":" + minutes); - return timePacket; + return responsePacket; } private TimePacketGenerator() {} diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java index bcc5e9dd..f3ac92dc 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/utils/ErrorIqPacketExceptionHelper.java @@ -7,26 +7,26 @@ import de.thedevstack.conversationsplus.xmpp.exceptions.InternalServerErrorExcep import de.thedevstack.conversationsplus.xmpp.exceptions.IqPacketErrorException; import de.thedevstack.conversationsplus.xmpp.exceptions.ServiceUnavailableException; import de.thedevstack.conversationsplus.xmpp.exceptions.UndefinedConditionException; +import de.thedevstack.conversationsplus.xmpp.stanzas.ErrorIqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.IqErrorCondition; /** - * Created by steckbrief on 22.08.2016. */ public final class ErrorIqPacketExceptionHelper { - private final static String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas"; public static void throwIqErrorException(Element errorIqPacket) throws IqPacketErrorException { Element packet = IqPacketParser.findChild(errorIqPacket, "error", "jabber:client"); if (null != packet) { - if (hasErrorElement(packet, "bad-request")) { + if (hasErrorElement(packet, IqErrorCondition.BAD_REQUEST.toString())) { throw new BadRequestIqErrorException(errorIqPacket, getErrorText(packet)); } - if (hasErrorElement(packet, "service-unavailable")) { + if (hasErrorElement(packet, IqErrorCondition.SERVICE_UNAVAILABLE.toString())) { throw new ServiceUnavailableException(errorIqPacket, getErrorText(packet)); } - if (hasErrorElement(packet, "internal-server-error")) { + if (hasErrorElement(packet, IqErrorCondition.INTERNAL_SERVER_ERROR.toString())) { throw new InternalServerErrorException(errorIqPacket, getErrorText(packet)); } - if (hasErrorElement(packet, "undefined-condition")) { + if (hasErrorElement(packet, IqErrorCondition.UNDEFINED_CONDITION.toString())) { throw new UndefinedConditionException(errorIqPacket, getErrorText(packet)); } } @@ -34,11 +34,11 @@ public final class ErrorIqPacketExceptionHelper { } private static boolean hasErrorElement(Element packet, String elementName) { - return null != IqPacketParser.findChild(packet, elementName, ERROR_NAMESPACE); + return null != IqPacketParser.findChild(packet, elementName, ErrorIqPacket.NAMESPACE); } private static String getErrorText(Element packet) { - return IqPacketParser.findChildContent(packet, "text", ERROR_NAMESPACE); + return IqPacketParser.findChildContent(packet, "text", ErrorIqPacket.NAMESPACE); } private ErrorIqPacketExceptionHelper() { diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index d514f08f..b762f420 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -639,4 +639,13 @@ Stanzas protokollieren ...macht eine Schreibpause ...schreibt + Einstellungen zum Datenschutz + Datenschutz + Datenschutz & Sicherheit + Sends your current time + Send Entity Time + Sends information about this client + Send client information + Sends information about your android version + Send your android version diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 2055c5de..0ab2c02c 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -690,4 +690,13 @@ Log stanzas ...has stopped typing ...is writing + Privacy Settings + Privacy + Privacy and Security + Sends your current time + Send Entity Time + Sends information about this client + Send client information + Sends information about your android version + Send your android version diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 96be892e..4faf1df2 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -16,25 +16,85 @@ android:key="resource" android:summary="@string/pref_xmpp_resource_summary" android:title="@string/pref_xmpp_resource"/> - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - @@ -221,24 +265,6 @@ android:summary="@string/pref_display_enter_key_summary" android:title="@string/pref_display_enter_key"/> - - - - -