diff --git a/src/main/java/eu/siacs/conversations/entities/Blockable.java b/src/main/java/eu/siacs/conversations/entities/Blockable.java index 0d1ab6361198fad4df90651518fbe6d1119db669..98f53d9dddc407e0d07bbfc69a050ba19ddbd727 100644 --- a/src/main/java/eu/siacs/conversations/entities/Blockable.java +++ b/src/main/java/eu/siacs/conversations/entities/Blockable.java @@ -1,11 +1,17 @@ package eu.siacs.conversations.entities; +import androidx.annotation.NonNull; import eu.siacs.conversations.xmpp.Jid; public interface Blockable { - boolean isBlocked(); - boolean isDomainBlocked(); - Jid getBlockedJid(); - Jid getJid(); - Account getAccount(); + boolean isBlocked(); + + boolean isDomainBlocked(); + + @NonNull + Jid getBlockedJid(); + + Jid getJid(); + + Account getAccount(); } diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index f13ffa973ad35a5b69bcedeeb4fa14aea134c2ec..d9a215f41b5039f173acf4e5d2a8e5af7820cf0c 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -316,7 +316,7 @@ public class Contact implements ListItem, Blockable { this.systemAccount = lookupUri; } - private Collection getGroups(final boolean unique) { + public Collection getGroups(final boolean unique) { final Collection groups = unique ? new HashSet<>() : new ArrayList<>(); for (int i = 0; i < this.groups.length(); ++i) { try { @@ -428,18 +428,6 @@ public class Contact implements ListItem, Blockable { } } - public Element asElement() { - final Element item = new Element("item"); - item.setAttribute("jid", this.jid); - if (this.serverName != null) { - item.setAttribute("name", this.serverName); - } - for (String group : getGroups(false)) { - item.addChild("group").setContent(group); - } - return item; - } - @Override public int compareTo(@NonNull final ListItem another) { return this.getDisplayName().compareToIgnoreCase(another.getDisplayName()); @@ -490,6 +478,7 @@ public class Contact implements ListItem, Blockable { } @Override + @NonNull public Jid getBlockedJid() { if (isDomainBlocked()) { return getJid().getDomain(); diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 6394ee1a477e936b81a89fe19b0a46e38edcd1b4..e50d908b59e25d0eaaed5aacd9ca1a2828651d60 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -533,6 +533,7 @@ public class Conversation extends AbstractEntity } @Override + @NonNull public Jid getBlockedJid() { return getContact().getBlockedJid(); } diff --git a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java index 97f63d99cfe6eb4a13bb224687a28f0b3309dda4..24117a168956b4531aaf9720b3ccaa6dfde205fa 100644 --- a/src/main/java/eu/siacs/conversations/entities/RawBlockable.java +++ b/src/main/java/eu/siacs/conversations/entities/RawBlockable.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.entities; import android.content.Context; import android.text.TextUtils; +import androidx.annotation.NonNull; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xmpp.Jid; import java.util.Collections; @@ -13,7 +14,7 @@ public class RawBlockable implements ListItem, Blockable { private final Account account; private final Jid jid; - public RawBlockable(Account account, Jid jid) { + public RawBlockable(@NonNull Account account, @NonNull Jid jid) { this.account = account; this.jid = jid; } @@ -29,6 +30,7 @@ public class RawBlockable implements ListItem, Blockable { } @Override + @NonNull public Jid getBlockedJid() { return this.jid; } diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 163250e4cf7ece3676c2771981798b891eb9fccb..fcb8273cda6dc4fda32cb0e957151421cccf8cdf 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -316,31 +316,6 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public Iq generateSetBlockRequest( - final Jid jid, final boolean reportSpam, final String serverMsgId) { - final Iq iq = new Iq(Iq.Type.SET); - final Element block = iq.addChild("block", Namespace.BLOCKING); - final Element item = block.addChild("item").setAttribute("jid", jid); - if (reportSpam) { - final Element report = item.addChild("report", Namespace.REPORTING); - report.setAttribute("reason", Namespace.REPORTING_REASON_SPAM); - if (serverMsgId != null) { - final Element stanzaId = report.addChild("stanza-id", Namespace.STANZA_IDS); - stanzaId.setAttribute("by", jid); - stanzaId.setAttribute("id", serverMsgId); - } - } - Log.d(Config.LOGTAG, iq.toString()); - return iq; - } - - public Iq generateSetUnblockRequest(final Jid jid) { - final Iq iq = new Iq(Iq.Type.SET); - final Element block = iq.addChild("unblock", Namespace.BLOCKING); - block.addChild("item").setAttribute("jid", jid); - return iq; - } - public Iq generateSetPassword(final Account account, final String newPassword) { final Iq packet = new Iq(Iq.Type.SET); packet.setTo(account.getDomain()); diff --git a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java index 60fa01872680da3e9e2248e6b9056887dbc9a1e3..21301e6a7658f25a27c8ac0a2763ed59c1af02b4 100644 --- a/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/PresenceGenerator.java @@ -1,11 +1,8 @@ package eu.siacs.conversations.generator; -import android.text.TextUtils; import eu.siacs.conversations.entities.Account; -import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.services.XmppConnectionService; -import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.manager.PresenceManager; import im.conversations.android.xmpp.model.stanza.Presence; @@ -15,50 +12,6 @@ public class PresenceGenerator extends AbstractGenerator { super(service); } - private im.conversations.android.xmpp.model.stanza.Presence subscription( - String type, Contact contact) { - im.conversations.android.xmpp.model.stanza.Presence packet = - new im.conversations.android.xmpp.model.stanza.Presence(); - packet.setAttribute("type", type); - packet.setTo(contact.getJid()); - packet.setFrom(contact.getAccount().getJid().asBareJid()); - return packet; - } - - public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom( - final Contact contact) { - return requestPresenceUpdatesFrom(contact, null); - } - - public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom( - final Contact contact, final String preAuth) { - im.conversations.android.xmpp.model.stanza.Presence packet = - subscription("subscribe", contact); - String displayName = contact.getAccount().getDisplayName(); - if (!TextUtils.isEmpty(displayName)) { - packet.addChild("nick", Namespace.NICK).setContent(displayName); - } - if (preAuth != null) { - packet.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth); - } - return packet; - } - - public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesFrom( - Contact contact) { - return subscription("unsubscribe", contact); - } - - public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesTo( - Contact contact) { - return subscription("unsubscribed", contact); - } - - public im.conversations.android.xmpp.model.stanza.Presence sendPresenceUpdatesTo( - Contact contact) { - return subscription("subscribed", contact); - } - public im.conversations.android.xmpp.model.stanza.Presence selfPresence( Account account, Presence.Availability status) { return selfPresence(account, status, true); diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 3cdd86b600c44c668869dba930ef07f694a5bb62..eb58377ff8e650605eb7587f48be5a4018e46b1d 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -24,6 +24,7 @@ import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.manager.DiscoManager; +import eu.siacs.conversations.xmpp.manager.PresenceManager; import eu.siacs.conversations.xmpp.manager.RosterManager; import eu.siacs.conversations.xmpp.pep.Avatar; import im.conversations.android.xmpp.Entity; @@ -445,8 +446,9 @@ public class PresenceParser extends AbstractParser mXmppConnectionService.getAvatarService().clear(contact); } if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { - mXmppConnectionService.sendPresencePacket( - account, mPresenceGenerator.sendPresenceUpdatesTo(contact)); + connection + .getManager(PresenceManager.class) + .subscribed(contact.getJid().asBareJid()); } else { contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); final Conversation conversation = diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 00e353bc802ee07f508c1595d24763b2c8befdce..34323ed4c61b7575f45aaf97b39668820c5f8aa8 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -137,7 +137,9 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import eu.siacs.conversations.xmpp.mam.MamReference; +import eu.siacs.conversations.xmpp.manager.BlockingManager; import eu.siacs.conversations.xmpp.manager.DiscoManager; +import eu.siacs.conversations.xmpp.manager.PresenceManager; import eu.siacs.conversations.xmpp.manager.RosterManager; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.PublishOptions; @@ -1902,7 +1904,7 @@ public class XmppConnectionService extends Service { + ": adding " + contact.getJid() + " on sending message"); - createContact(contact, true); + createContact(contact); } } @@ -3022,10 +3024,13 @@ public class XmppConnectionService extends Service { } } - public void stopPresenceUpdatesTo(Contact contact) { + public void stopPresenceUpdatesTo(final Contact contact) { Log.d(Config.LOGTAG, "Canceling presence request from " + contact.getJid().toString()); - sendPresencePacket(contact.getAccount(), mPresenceGenerator.stopPresenceUpdatesTo(contact)); contact.resetOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST); + contact.getAccount() + .getXmppConnection() + .getManager(PresenceManager.class) + .unsubscribed(contact.getJid().asBareJid()); } public void createAccount(final Account account) { @@ -4690,57 +4695,20 @@ public class XmppConnectionService extends Service { updateConversationUi(); } - // TODO move this to RosterManager - public void syncDirtyContacts(Account account) { - for (Contact contact : account.getRoster().getContacts()) { - if (contact.getOption(Contact.Options.DIRTY_PUSH)) { - pushContactToServer(contact); - } - if (contact.getOption(Contact.Options.DIRTY_DELETE)) { - deleteContactOnServer(contact); - } - } + public void createContact(final Contact contact) { + createContact(contact, null); } - public void createContact(final Contact contact, final boolean autoGrant) { - createContact(contact, autoGrant, null); + public void createContact(final Contact contact, final String preAuth) { + contact.setOption(Contact.Options.PREEMPTIVE_GRANT); + contact.setOption(Contact.Options.ASKING); + final var connection = contact.getAccount().getXmppConnection(); + connection.getManager(RosterManager.class).addRosterItem(contact, preAuth); } - public void createContact( - final Contact contact, final boolean autoGrant, final String preAuth) { - if (autoGrant) { - contact.setOption(Contact.Options.PREEMPTIVE_GRANT); - contact.setOption(Contact.Options.ASKING); - } - pushContactToServer(contact, preAuth); - } - - public void pushContactToServer(final Contact contact) { - pushContactToServer(contact, null); - } - - private void pushContactToServer(final Contact contact, final String preAuth) { - contact.resetOption(Contact.Options.DIRTY_DELETE); - contact.setOption(Contact.Options.DIRTY_PUSH); - final Account account = contact.getAccount(); - if (account.getStatus() == Account.State.ONLINE) { - final boolean ask = contact.getOption(Contact.Options.ASKING); - final boolean sendUpdates = - contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) - && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); - final Iq iq = new Iq(Iq.Type.SET); - iq.query(Namespace.ROSTER).addChild(contact.asElement()); - account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); - if (sendUpdates) { - sendPresencePacket(account, mPresenceGenerator.sendPresenceUpdatesTo(contact)); - } - if (ask) { - sendPresencePacket( - account, mPresenceGenerator.requestPresenceUpdatesFrom(contact, preAuth)); - } - } else { - account.getXmppConnection().getManager(RosterManager.class).writeToDatabaseAsync(); - } + public void deleteContactOnServer(final Contact contact) { + final var connection = contact.getAccount().getXmppConnection(); + connection.getManager(RosterManager.class).deleteRosterItem(contact); } public void publishMucAvatar( @@ -5312,20 +5280,6 @@ public class XmppConnectionService extends Service { } } - public void deleteContactOnServer(Contact contact) { - contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - contact.resetOption(Contact.Options.DIRTY_PUSH); - contact.setOption(Contact.Options.DIRTY_DELETE); - Account account = contact.getAccount(); - if (account.getStatus() == Account.State.ONLINE) { - final Iq iq = new Iq(Iq.Type.SET); - Element item = iq.query(Namespace.ROSTER).addChild("item"); - item.setAttribute("jid", contact.getJid()); - item.setAttribute("subscription", "remove"); - account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler); - } - } - public void updateConversation(final Conversation conversation) { mDatabaseWriterExecutor.execute(() -> databaseBackend.updateConversation(conversation)); } @@ -6145,29 +6099,11 @@ public class XmppConnectionService extends Service { public boolean sendBlockRequest( final Blockable blockable, final boolean reportSpam, final String serverMsgId) { - if (blockable != null && blockable.getBlockedJid() != null) { - final var account = blockable.getAccount(); - final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket( - account, - getIqGenerator().generateSetBlockRequest(jid, reportSpam, serverMsgId), - (response) -> { - if (response.getType() == Iq.Type.RESULT) { - account.getBlocklist().add(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); - } - }); - if (blockable.getBlockedJid().isFullJid()) { - return false; - } else if (removeBlockedConversations(blockable.getAccount(), jid)) { - updateConversationUi(); - return true; - } else { - return false; - } - } else { - return false; - } + final var account = blockable.getAccount(); + final var connection = account.getXmppConnection(); + return connection + .getManager(BlockingManager.class) + .block(blockable, reportSpam, serverMsgId); } public boolean removeBlockedConversations(final Account account, final Jid blockedJid) { @@ -6202,19 +6138,9 @@ public class XmppConnectionService extends Service { } public void sendUnblockRequest(final Blockable blockable) { - if (blockable != null && blockable.getJid() != null) { - final var account = blockable.getAccount(); - final Jid jid = blockable.getBlockedJid(); - this.sendIqPacket( - account, - getIqGenerator().generateSetUnblockRequest(jid), - response -> { - if (response.getType() == Iq.Type.RESULT) { - account.getBlocklist().remove(jid); - updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); - } - }); - } + final var account = blockable.getAccount(); + final var connection = account.getXmppConnection(); + connection.getManager(BlockingManager.class).unblock(blockable); } public void publishDisplayName(final Account account) { diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index 039c26e47fc303e7f4fbea9b7b8e676df7b92261..06a1183d369490c51ddd9040d53cf713c9d74ec7 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -70,6 +70,8 @@ import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.XmppConnection; +import eu.siacs.conversations.xmpp.manager.PresenceManager; +import eu.siacs.conversations.xmpp.manager.RosterManager; import im.conversations.android.xmpp.model.stanza.Presence; import java.util.Collection; import java.util.Collections; @@ -109,11 +111,10 @@ public class ContactDetailsActivity extends OmemoActivity } } else { contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); - xmppConnectionService.sendPresencePacket( - contact.getAccount(), - xmppConnectionService - .getPresenceGenerator() - .stopPresenceUpdatesTo(contact)); + final var connection = contact.getAccount().getXmppConnection(); + connection + .getManager(PresenceManager.class) + .unsubscribed(contact.getJid().asBareJid()); } } }; @@ -122,18 +123,15 @@ public class ContactDetailsActivity extends OmemoActivity @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + final var connection = contact.getAccount().getXmppConnection(); if (isChecked) { - xmppConnectionService.sendPresencePacket( - contact.getAccount(), - xmppConnectionService - .getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); + connection + .getManager(PresenceManager.class) + .subscribe(contact.getJid().asBareJid()); } else { - xmppConnectionService.sendPresencePacket( - contact.getAccount(), - xmppConnectionService - .getPresenceGenerator() - .stopPresenceUpdatesFrom(contact)); + connection + .getManager(PresenceManager.class) + .unsubscribe(contact.getJid().asBareJid()); } } }; @@ -343,8 +341,10 @@ public class ContactDetailsActivity extends OmemoActivity R.string.contact_name, value -> { contact.setServerName(value); - ContactDetailsActivity.this.xmppConnectionService - .pushContactToServer(contact); + final var connection = contact.getAccount().getXmppConnection(); + connection + .getManager(RosterManager.class) + .addRosterItem(contact, null); populateView(); return null; }, diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index bf87e21271952bc9f7b145836e91f7895e8ad84b..0e5df61721920558628f29547ac9c26d97e8c423 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -127,6 +127,7 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession; import eu.siacs.conversations.xmpp.jingle.RtpCapability; +import eu.siacs.conversations.xmpp.manager.PresenceManager; import im.conversations.android.xmpp.model.stanza.Presence; import java.util.ArrayList; import java.util.Arrays; @@ -446,7 +447,7 @@ public class ConversationFragment extends XmppFragment public void onClick(View v) { final Contact contact = conversation == null ? null : conversation.getContact(); if (contact != null) { - activity.xmppConnectionService.createContact(contact, true); + activity.xmppConnectionService.createContact(contact); activity.switchToContactDetails(contact); } } @@ -458,11 +459,10 @@ public class ConversationFragment extends XmppFragment public void onClick(View v) { final Contact contact = conversation == null ? null : conversation.getContact(); if (contact != null) { - activity.xmppConnectionService.sendPresencePacket( - contact.getAccount(), - activity.xmppConnectionService - .getPresenceGenerator() - .sendPresenceUpdatesTo(contact)); + final var connection = contact.getAccount().getXmppConnection(); + connection + .getManager(PresenceManager.class) + .subscribed(contact.getJid().asBareJid()); hideSnackbar(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java index 9adb4e7bec4c8fb430916c548344386cd8c4352f..721a85739fc0babc55871a735d206514ad42648e 100644 --- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java @@ -647,7 +647,7 @@ public class StartConversationActivity extends XmppActivity invite == null ? null : invite.getParameter(XmppUri.PARAMETER_PRE_AUTH); - xmppConnectionService.createContact(contact, true, preAuth); + xmppConnectionService.createContact(contact, preAuth); if (invite != null && invite.hasFingerprints()) { xmppConnectionService.verifyFingerprints( contact, invite.getFingerprints()); diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index d597599bd1c97a52441136275eb3da664479e19e..7d899b4ee7fa923af30e0feb7db9ed4e542147c2 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -85,6 +85,7 @@ import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; +import eu.siacs.conversations.xmpp.manager.PresenceManager; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -894,7 +895,7 @@ public abstract class XmppActivity extends ActionBarActivity { builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton( getString(R.string.add_contact), - (dialog, which) -> xmppConnectionService.createContact(contact, true)); + (dialog, which) -> xmppConnectionService.createContact(contact)); builder.create().show(); } @@ -906,13 +907,10 @@ public abstract class XmppActivity extends ActionBarActivity { builder.setPositiveButton( R.string.request_now, (dialog, which) -> { - if (xmppConnectionServiceBound) { - xmppConnectionService.sendPresencePacket( - contact.getAccount(), - xmppConnectionService - .getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); - } + final var connection = contact.getAccount().getXmppConnection(); + connection + .getManager(PresenceManager.class) + .subscribe(contact.getJid().asBareJid()); }); builder.create().show(); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 3e052ebf8e0643ae0bd335553923ff97efd9c57f..dec02a32a3614c0ed97adfee4e51b5d271a90296 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -3071,7 +3071,7 @@ public class XmppConnection implements Runnable { } public boolean blocking() { - return hasDiscoFeature(account.getDomain(), Namespace.BLOCKING); + return connection.getManager(BlockingManager.class).hasFeature(); } public boolean spamReporting() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/BlockingManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/BlockingManager.java index 89d21602fec1084581b455c0781a85a7e3bc3295..63bd62af34c465a1be8b88b25f181b91e0dcd925 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/BlockingManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/BlockingManager.java @@ -2,11 +2,13 @@ package eu.siacs.conversations.xmpp.manager; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.Jid; @@ -18,7 +20,9 @@ import im.conversations.android.xmpp.model.blocking.Item; import im.conversations.android.xmpp.model.blocking.Unblock; import im.conversations.android.xmpp.model.error.Condition; import im.conversations.android.xmpp.model.error.Error; +import im.conversations.android.xmpp.model.reporting.Report; import im.conversations.android.xmpp.model.stanza.Iq; +import im.conversations.android.xmpp.model.unique.StanzaId; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -141,4 +145,85 @@ public class BlockingManager extends AbstractManager { } return builder.build(); } + + public boolean block( + @NonNull final Blockable blockable, + final boolean reportSpam, + @Nullable final String serverMsgId) { + final var address = blockable.getBlockedJid(); + final var iq = new Iq(Iq.Type.SET); + final var block = iq.addExtension(new Block()); + final var item = block.addExtension(new Item()); + item.setJid(address); + if (reportSpam) { + final var report = item.addExtension(new Report()); + report.setReason(Namespace.REPORTING_REASON_SPAM); + if (serverMsgId != null) { + // XEP has a 'by' attribute that is the same as reported jid but that doesn't make + // sense this the 'by' attribute in the stanza-id refers to the arriving entity + // (usually the account or the MUC) + report.addExtension(new StanzaId(serverMsgId)); + } + } + final var future = this.connection.sendIqPacket(iq); + Futures.addCallback( + future, + new FutureCallback<>() { + @Override + public void onSuccess(Iq result) { + synchronized (blocklist) { + blocklist.add(address); + } + service.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); + } + + @Override + public void onFailure(@NonNull Throwable throwable) { + Log.d( + Config.LOGTAG, + getAccount().getJid().asBareJid() + ": could not block " + address, + throwable); + } + }, + MoreExecutors.directExecutor()); + if (address.isFullJid()) { + return false; + } else if (service.removeBlockedConversations(getAccount(), address)) { + service.updateConversationUi(); + return true; + } else { + return false; + } + } + + public void unblock(@NonNull final Blockable blockable) { + final var address = blockable.getBlockedJid(); + final var iq = new Iq(Iq.Type.SET); + final var unblock = iq.addExtension(new Unblock()); + final var item = unblock.addExtension(new Item()); + item.setJid(address); + final var future = this.connection.sendIqPacket(iq); + Futures.addCallback( + future, + new FutureCallback() { + @Override + public void onSuccess(Iq result) { + synchronized (blocklist) { + blocklist.remove(address); + } + service.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); + } + + @Override + public void onFailure(@NonNull Throwable t) { + Log.d( + Config.LOGTAG, + getAccount().getJid().asBareJid() + + ": could not unblock " + + address, + t); + } + }, + MoreExecutors.directExecutor()); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java index 14de9f576f8590bb886091717411d5005af247b3..21dc794be3e171a7f2b8573c235c3850b95b18a8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/PresenceManager.java @@ -1,12 +1,16 @@ package eu.siacs.conversations.xmpp.manager; import android.content.Context; +import com.google.common.base.Strings; +import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.XmppConnection; import im.conversations.android.xmpp.EntityCapabilities; import im.conversations.android.xmpp.EntityCapabilities2; import im.conversations.android.xmpp.ServiceDescription; import im.conversations.android.xmpp.model.capabilties.Capabilities; import im.conversations.android.xmpp.model.capabilties.LegacyCapabilities; +import im.conversations.android.xmpp.model.nick.Nick; +import im.conversations.android.xmpp.model.pars.PreAuth; import im.conversations.android.xmpp.model.pgp.Signed; import im.conversations.android.xmpp.model.stanza.Presence; import java.util.HashMap; @@ -21,6 +25,43 @@ public class PresenceManager extends AbstractManager { super(context, connection); } + public void subscribe(final Jid address) { + subscribe(address, null); + } + + public void subscribe(final Jid address, final String preAuth) { + + var presence = new Presence(Presence.Type.SUBSCRIBE); + presence.setTo(address); + + final var displayName = getAccount().getDisplayName(); + if (!Strings.isNullOrEmpty(displayName)) { + presence.addExtension(new Nick(displayName)); + } + if (preAuth != null) { + presence.addExtension(new PreAuth()).setToken(preAuth); + } + this.connection.sendPresencePacket(presence); + } + + public void unsubscribe(final Jid address) { + var presence = new Presence(Presence.Type.UNSUBSCRIBE); + presence.setTo(address); + this.connection.sendPresencePacket(presence); + } + + public void unsubscribed(final Jid address) { + var presence = new Presence(Presence.Type.UNSUBSCRIBED); + presence.setTo(address); + this.connection.sendPresencePacket(presence); + } + + public void subscribed(final Jid address) { + var presence = new Presence(Presence.Type.SUBSCRIBED); + presence.setTo(address); + this.connection.sendPresencePacket(presence); + } + public Presence getPresence(final Presence.Availability availability, final boolean personal) { final var account = connection.getAccount(); final var serviceDiscoveryFeatures = getManager(DiscoManager.class).getServiceDescription(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/manager/RosterManager.java b/src/main/java/eu/siacs/conversations/xmpp/manager/RosterManager.java index ddf5051c0d32d8a33eaa31a710985eebd2e1a94d..ff2d13e50748efb5af9bc3c271882a6e4f5f9ee8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/manager/RosterManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/manager/RosterManager.java @@ -212,7 +212,7 @@ public class RosterManager extends AbstractManager implements Roster { public Contact getContactFromContactList(@NonNull final Jid jid) { synchronized (this.contacts) { final var contact = - Iterables.find(this.contacts, c -> c.getJid().equals(jid.asBareJid())); + Iterables.find(this.contacts, c -> c.getJid().equals(jid.asBareJid()), null); if (contact != null && contact.showInContactList()) { return contact; } else { @@ -273,4 +273,105 @@ public class RosterManager extends AbstractManager implements Roster { } getDatabase().writeRoster(account, version, contacts); } + + public void syncDirtyContacts() { + synchronized (this.contacts) { + for (final var contact : this.contacts) { + if (contact.getOption(Contact.Options.DIRTY_PUSH)) { + addRosterItem(contact, null); + } + if (contact.getOption(Contact.Options.DIRTY_DELETE)) { + deleteRosterItem(contact); + } + } + } + } + + public void addRosterItem(final Contact contact, final String preAuth) { + final var address = contact.getJid().asBareJid(); + contact.resetOption(Contact.Options.DIRTY_DELETE); + contact.setOption(Contact.Options.DIRTY_PUSH); + // sync the 'dirty push' flag to disk in case we are offline + this.writeToDatabaseAsync(); + final boolean ask = contact.getOption(Contact.Options.ASKING); + final boolean sendUpdates = + contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST) + && contact.getOption(Contact.Options.PREEMPTIVE_GRANT); + final Iq iq = new Iq(Iq.Type.SET); + final var query = iq.addExtension(new Query()); + final var item = query.addExtension(new Item()); + item.setJid(address); + final var serverName = contact.getServerName(); + if (serverName != null) { + item.setItemName(serverName); + } + item.setGroups(contact.getGroups(false)); + final var future = this.connection.sendIqPacket(iq); + Futures.addCallback( + future, + new FutureCallback() { + @Override + public void onSuccess(Iq result) { + Log.d( + Config.LOGTAG, + getAccount().getJid().asBareJid() + + ": pushed roster item " + + address); + } + + @Override + public void onFailure(@NonNull Throwable t) { + Log.d( + Config.LOGTAG, + getAccount().getJid().asBareJid() + + ": could not push roster item " + + address, + t); + } + }, + MoreExecutors.directExecutor()); + if (sendUpdates) { + getManager(PresenceManager.class).subscribed(contact.getJid().asBareJid()); + } + if (ask) { + getManager(PresenceManager.class).subscribe(contact.getJid().asBareJid(), preAuth); + } + } + + public void deleteRosterItem(final Contact contact) { + final var address = contact.getJid().asBareJid(); + contact.resetOption(Contact.Options.PREEMPTIVE_GRANT); + contact.resetOption(Contact.Options.DIRTY_PUSH); + contact.setOption(Contact.Options.DIRTY_DELETE); + this.writeToDatabaseAsync(); + final Iq iq = new Iq(Iq.Type.SET); + final var query = iq.addExtension(new Query()); + final var item = query.addExtension(new Item()); + item.setJid(address); + item.setSubscription(Item.Subscription.REMOVE); + final var future = this.connection.sendIqPacket(iq); + Futures.addCallback( + future, + new FutureCallback() { + @Override + public void onSuccess(final Iq result) { + Log.d( + Config.LOGTAG, + getAccount().getJid().asBareJid() + + ": removed roster item " + + address); + } + + @Override + public void onFailure(final @NonNull Throwable t) { + Log.d( + Config.LOGTAG, + getAccount().getJid().asBareJid() + + ": could not remove roster item " + + address, + t); + } + }, + MoreExecutors.directExecutor()); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java index 647b0ae9912de215bffb4f5057bbc95dae3e6bfd..e93dbe06b2643146e577e8262f30de8280103661 100644 --- a/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java +++ b/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java @@ -14,4 +14,8 @@ public class Item extends Extension { public Jid getJid() { return getAttributeAsJid("jid"); } + + public void setJid(final Jid address) { + this.setAttribute("jid", address); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java index e9a98512823681750c9bdd5c79c8507ea2b9ce23..81e55062f8ec470306c419af16063ba1f7aa8bfb 100644 --- a/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java +++ b/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java @@ -10,4 +10,9 @@ public class Nick extends Extension { public Nick() { super(Nick.class); } + + public Nick(final String nick) { + this(); + this.setContent(nick); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/reporting/Report.java b/src/main/java/im/conversations/android/xmpp/model/reporting/Report.java new file mode 100644 index 0000000000000000000000000000000000000000..a5a763c2d035e6aa0c758199df3d669a59a1ec12 --- /dev/null +++ b/src/main/java/im/conversations/android/xmpp/model/reporting/Report.java @@ -0,0 +1,16 @@ +package im.conversations.android.xmpp.model.reporting; + +import eu.siacs.conversations.xml.Namespace; +import im.conversations.android.annotation.XmlElement; +import im.conversations.android.xmpp.model.Extension; + +@XmlElement(namespace = Namespace.REPORTING) +public class Report extends Extension { + public Report() { + super(Report.class); + } + + public void setReason(final String reason) { + this.setAttribute("reason", reason); + } +} diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Group.java b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java index 9f36efae7c7d6defa57a3b142a55725e2ae386df..355e13c7f8fb22dea3e85473dcb9eedc2e5c719b 100644 --- a/src/main/java/im/conversations/android/xmpp/model/roster/Group.java +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Group.java @@ -9,4 +9,9 @@ public class Group extends Extension { public Group() { super(Group.class); } + + public Group(final String group) { + this(); + this.setContent(group); + } } diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java index 0a2e0ef54add741e8141d333dc9d21e510e309ec..a8bcc3e70433696713a0319a3c423d851c653a01 100644 --- a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java +++ b/src/main/java/im/conversations/android/xmpp/model/roster/Item.java @@ -1,13 +1,10 @@ package im.conversations.android.xmpp.model.roster; import com.google.common.collect.Collections2; - import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.Jid; - import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.Extension; - import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -28,10 +25,18 @@ public class Item extends Extension { return getAttributeAsJid("jid"); } + public void setJid(final Jid jid) { + this.setAttribute("jid", jid); + } + public String getItemName() { return this.getAttribute("name"); } + public void setItemName(final String serverName) { + this.setAttribute("name", serverName); + } + public boolean isPendingOut() { return "subscribe".equalsIgnoreCase(this.getAttribute("ask")); } @@ -45,12 +50,26 @@ public class Item extends Extension { } } + public void setSubscription(final Subscription subscription) { + if (subscription == null) { + this.removeAttribute("subscription"); + } else { + this.setAttribute("subscription", subscription.toString().toLowerCase(Locale.ROOT)); + } + } + public Collection getGroups() { return Collections2.filter( Collections2.transform(getExtensions(Group.class), Element::getContent), Objects::nonNull); } + public void setGroups(final Collection groups) { + for (final String group : groups) { + this.addExtension(new Group()); + } + } + public enum Subscription { NONE, TO, diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java index 0887fde2c649978fec81669efd7ba2673d519e4f..e4d1747f429acf444d4693172f4571b6751ed155 100644 --- a/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java +++ b/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java @@ -5,6 +5,7 @@ import im.conversations.android.annotation.XmlElement; import im.conversations.android.xmpp.model.capabilties.EntityCapabilities; import im.conversations.android.xmpp.model.jabber.Show; import im.conversations.android.xmpp.model.jabber.Status; +import java.util.Locale; @XmlElement public class Presence extends Stanza implements EntityCapabilities { @@ -13,6 +14,11 @@ public class Presence extends Stanza implements EntityCapabilities { super(Presence.class); } + public Presence(final Type type) { + this(); + this.setType(type); + } + public Availability getAvailability() { final var show = getExtension(Show.class); if (show == null) { @@ -28,6 +34,18 @@ public class Presence extends Stanza implements EntityCapabilities { this.addExtension(new Show()).setContent(availability.toShowString()); } + public void setType(final Type type) { + if (type == null) { + this.removeAttribute("type"); + } else { + this.setAttribute("type", type.toString().toLowerCase(Locale.ROOT)); + } + } + + public Type getType() { + return Type.valueOfOrNull(this.getAttribute("type")); + } + public void setStatus(final String status) { if (Strings.isNullOrEmpty(status)) { return; @@ -40,6 +58,27 @@ public class Presence extends Stanza implements EntityCapabilities { return status == null ? null : status.getContent(); } + public enum Type { + ERROR, + PROBE, + SUBSCRIBE, + SUBSCRIBED, + UNAVAILABLE, + UNSUBSCRIBE, + UNSUBSCRIBED; + + public static Type valueOfOrNull(final String type) { + if (Strings.isNullOrEmpty(type)) { + return null; + } + try { + return valueOf(type.toUpperCase(Locale.ROOT)); + } catch (final IllegalArgumentException e) { + return null; + } + } + } + public enum Availability { CHAT, ONLINE, diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java index 0078bcbae2be3558658908c98a48e71d6636ac42..f2d1c506814b6cc3f160fb0866348bb61513546d 100644 --- a/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java +++ b/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java @@ -13,6 +13,11 @@ public class StanzaId extends Extension { super(StanzaId.class); } + public StanzaId(final String id) { + this(); + this.setAttribute("id", id); + } + public Jid getBy() { return this.getAttributeAsJid("by"); } diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java index 6092b0e8f406a6b979f3b88618efaa0ef114e7c2..e42e400dc9be30a3dfe29d1602252dca19d0a2aa 100644 --- a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java +++ b/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java @@ -98,7 +98,7 @@ public class BindProcessor extends XmppConnection.Delegate implements Runnable { service.getPushManagementService().registerPushTokenOnServer(account); } service.connectMultiModeConversations(account); - service.syncDirtyContacts(account); + connection.getManager(RosterManager.class).syncDirtyContacts(); service.getUnifiedPushBroker().renewUnifiedPushEndpointsOnBind(account); }