diff options
60 files changed, 1280 insertions, 400 deletions
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<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + Element item = IqPacketReceiver.getInstance().getLegacyIqParser().getItem(packet); + Set<Integer> 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<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); + PreKeyBundle bundle = IqPacketReceiver.getInstance().getLegacyIqParser().bundle(packet); + Map<Integer, ECPublicKey> 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<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet); + Pair<X509Certificate[],byte[]> 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<PreKeyBundle> 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<String> getFeatures() { - ArrayList<String> 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<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + Set<Integer> 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/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<String> 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/MessageArchiveService.java b/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java index 6402f4ed..6ad413b4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/mam/MessageArchiveService.java @@ -1,4 +1,4 @@ -package de.thedevstack.conversationsplus.services; +package de.thedevstack.conversationsplus.services.mam; import android.util.Pair; @@ -15,6 +15,7 @@ import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.generator.AbstractGenerator; +import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.xml.Element; import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; @@ -101,7 +102,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return this.query(conversation, start, end, null); } - public Query query(Conversation conversation, long start, long end, XmppConnectionService.OnMoreMessagesLoaded callback) { + public Query query(Conversation conversation, long start, long end, OnMoreMessagesLoaded callback) { synchronized (this.queries) { if (start > end) { return null; @@ -186,7 +187,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } - public boolean queryInProgress(Conversation conversation, XmppConnectionService.OnMoreMessagesLoaded callback) { + public boolean queryInProgress(Conversation conversation, OnMoreMessagesLoaded callback) { synchronized (this.queries) { for(Query query : queries) { if (query.conversation == conversation) { @@ -274,7 +275,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { private Account account; private Conversation conversation; private PagingOrder pagingOrder = PagingOrder.NORMAL; - private XmppConnectionService.OnMoreMessagesLoaded callback = null; + private OnMoreMessagesLoaded callback = null; public Query(Conversation conversation, long start, long end) { @@ -339,7 +340,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { return start; } - public void setCallback(XmppConnectionService.OnMoreMessagesLoaded callback) { + public void setCallback(OnMoreMessagesLoaded callback) { this.callback = callback; } 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<String> 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<Message> 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<String, Xep> 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<String> 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<String> 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 <a href="http://xmpp.org/extensions/xep-0092.html">http://xmpp.org/extensions/xep-0092.html</a> - */ -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<String, IqPacketHandler> 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 <a href="http://xmpp.org/extensions/xep-0202.html">http://xmpp.org/extensions/xep-0202.html</a> - */ -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 @@ <string name="pref_log_stanzas">Stanzas protokollieren</string> <string name="contact_paused_typing">...macht eine Schreibpause</string> <string name="is_typing">...schreibt</string> + <string name="pref_privacy_summary">Einstellungen zum Datenschutz</string> + <string name="pref_privacy">Datenschutz</string> + <string name="pref_privacy_security">Datenschutz & Sicherheit</string> + <string name="pref_entity_time_summary">Sends your current time</string> + <string name="pref_entity_time">Send Entity Time</string> + <string name="pref_software_info_summary">Sends information about this client</string> + <string name="pref_software_info">Send client information</string> + <string name="pref_send_os_info_summary">Sends information about your android version</string> + <string name="pref_send_os_info">Send your android version</string> </resources> 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 @@ <string name="pref_log_stanzas">Log stanzas</string> <string name="contact_paused_typing">...has stopped typing</string> <string name="is_typing">...is writing</string> + <string name="pref_privacy_summary">Privacy Settings</string> + <string name="pref_privacy">Privacy</string> + <string name="pref_privacy_security">Privacy and Security</string> + <string name="pref_entity_time_summary">Sends your current time</string> + <string name="pref_entity_time">Send Entity Time</string> + <string name="pref_software_info_summary">Sends information about this client</string> + <string name="pref_software_info">Send client information</string> + <string name="pref_send_os_info_summary">Sends information about your android version</string> + <string name="pref_send_os_info">Send your android version</string> </resources> 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"/> - <ListPreference - android:defaultValue="2" - android:entries="@array/confirm_strings" - android:entryValues="@array/confirm_values" - android:key="confirm_messages_list" - android:summary="@string/pref_confirm_messages_summary" - android:title="@string/pref_confirm_messages" /> - - <CheckBoxPreference - android:defaultValue="false" - android:key="chat_states" - android:summary="@string/pref_chat_states_summary" - android:title="@string/pref_chat_states"/> <CheckBoxPreference android:defaultValue="true" android:key="parse_emoticons" android:summary="@string/pref_parse_emoticons_summary" android:title="@string/pref_parse_emoticons"/> </PreferenceCategory> + <PreferenceCategory android:title="@string/pref_privacy_security"> + <PreferenceScreen + android:summary="@string/pref_privacy_summary" + android:title="@string/pref_privacy"> + <PreferenceCategory android:title="@string/pref_general"> + <ListPreference + android:defaultValue="2" + android:entries="@array/confirm_strings" + android:entryValues="@array/confirm_values" + android:key="confirm_messages_list" + android:summary="@string/pref_confirm_messages_summary" + android:title="@string/pref_confirm_messages" /> + + <CheckBoxPreference + android:defaultValue="false" + android:key="chat_states" + android:summary="@string/pref_chat_states_summary" + android:title="@string/pref_chat_states"/> + <CheckBoxPreference + android:defaultValue="true" + android:key="send_entity_time" + android:summary="@string/pref_entity_time_summary" + android:title="@string/pref_entity_time"/> + <CheckBoxPreference + android:defaultValue="true" + android:key="send_software_info" + android:summary="@string/pref_software_info_summary" + android:title="@string/pref_software_info"/> + <CheckBoxPreference + android:defaultValue="true" + android:key="send_os_info" + android:dependency="send_software_info" + android:summary="@string/pref_send_os_info_summary" + android:title="@string/pref_send_os_info"/> + </PreferenceCategory> + <PreferenceCategory android:title="@string/pref_presence_settings"> + <CheckBoxPreference + android:defaultValue="false" + android:key="away_when_screen_off" + android:summary="@string/pref_away_when_screen_off_summary" + android:title="@string/pref_away_when_screen_off"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="xa_on_silent_mode" + android:summary="@string/pref_xa_on_silent_mode_summary" + android:title="@string/pref_xa_on_silent_mode"/> + <CheckBoxPreference + android:dependency="xa_on_silent_mode" + android:defaultValue="false" + android:key="treat_vibrate_as_silent" + android:title="@string/pref_treat_vibrate_as_silent" + android:summary="@string/pref_treat_vibrate_as_silent_summary"/> + </PreferenceCategory> + </PreferenceScreen> + <PreferenceScreen android:title="@string/pref_security_settings"> + <PreferenceCategory android:title="@string/pref_security_settings"> + <CheckBoxPreference + android:defaultValue="false" + android:key="dont_save_encrypted" + android:summary="@string/pref_dont_save_encrypted_summary" + android:title="@string/pref_dont_save_encrypted"/> + <CheckBoxPreference + android:defaultValue="false" + android:key="dont_trust_system_cas" + android:summary="@string/pref_dont_trust_system_cas_summary" + android:title="@string/pref_dont_trust_system_cas_title"/> + <Preference + android:key="remove_trusted_certificates" + android:summary="@string/pref_remove_trusted_certificates_summary" + android:title="@string/pref_remove_trusted_certificates_title"/> + </PreferenceCategory> + </PreferenceScreen> + </PreferenceCategory> <PreferenceCategory android:title="@string/pref_file_transfer_category"> <PreferenceScreen android:summary="@string/pref_accept_files_summary" @@ -184,22 +244,6 @@ android:key="expert" android:summary="@string/pref_expert_options_summary" android:title="@string/pref_expert_options"> - <PreferenceCategory android:title="@string/pref_security_settings"> - <CheckBoxPreference - android:defaultValue="false" - android:key="dont_save_encrypted" - android:summary="@string/pref_dont_save_encrypted_summary" - android:title="@string/pref_dont_save_encrypted"/> - <CheckBoxPreference - android:defaultValue="false" - android:key="dont_trust_system_cas" - android:summary="@string/pref_dont_trust_system_cas_summary" - android:title="@string/pref_dont_trust_system_cas_title"/> - <Preference - android:key="remove_trusted_certificates" - android:summary="@string/pref_remove_trusted_certificates_summary" - android:title="@string/pref_remove_trusted_certificates_title"/> - </PreferenceCategory> <PreferenceCategory android:key="connection_options" android:title="@string/pref_connection_options"> @@ -221,24 +265,6 @@ android:summary="@string/pref_display_enter_key_summary" android:title="@string/pref_display_enter_key"/> </PreferenceCategory> - <PreferenceCategory android:title="@string/pref_presence_settings"> - <CheckBoxPreference - android:defaultValue="false" - android:key="away_when_screen_off" - android:summary="@string/pref_away_when_screen_off_summary" - android:title="@string/pref_away_when_screen_off"/> - <CheckBoxPreference - android:defaultValue="false" - android:key="xa_on_silent_mode" - android:summary="@string/pref_xa_on_silent_mode_summary" - android:title="@string/pref_xa_on_silent_mode"/> - <CheckBoxPreference - android:dependency="xa_on_silent_mode" - android:defaultValue="false" - android:key="treat_vibrate_as_silent" - android:title="@string/pref_treat_vibrate_as_silent" - android:summary="@string/pref_treat_vibrate_as_silent_summary"/> - </PreferenceCategory> <PreferenceCategory android:key="other_expert_settings" android:title="@string/pref_expert_options_other"> <CheckBoxPreference android:key="autojoin" |