1package eu.siacs.conversations.xmpp.manager;
2
3import android.util.Log;
4import androidx.annotation.NonNull;
5import com.google.common.collect.ImmutableSet;
6import com.google.common.util.concurrent.FutureCallback;
7import com.google.common.util.concurrent.Futures;
8import com.google.common.util.concurrent.MoreExecutors;
9import eu.siacs.conversations.Config;
10import eu.siacs.conversations.services.XmppConnectionService;
11import eu.siacs.conversations.xml.Namespace;
12import eu.siacs.conversations.xmpp.Jid;
13import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
14import eu.siacs.conversations.xmpp.XmppConnection;
15import im.conversations.android.xmpp.model.blocking.Block;
16import im.conversations.android.xmpp.model.blocking.Blocklist;
17import im.conversations.android.xmpp.model.blocking.Item;
18import im.conversations.android.xmpp.model.blocking.Unblock;
19import im.conversations.android.xmpp.model.error.Condition;
20import im.conversations.android.xmpp.model.error.Error;
21import im.conversations.android.xmpp.model.stanza.Iq;
22import java.util.Collection;
23import java.util.HashSet;
24import java.util.Set;
25
26public class BlockingManager extends AbstractManager {
27
28 private final XmppConnectionService service;
29
30 private final HashSet<Jid> blocklist = new HashSet<>();
31
32 public BlockingManager(final XmppConnectionService service, final XmppConnection connection) {
33 super(service, connection);
34 // TODO find a way to get rid of XmppConnectionService and use context instead
35 this.service = service;
36 }
37
38 public void request() {
39 final var future = this.connection.sendIqPacket(new Iq(Iq.Type.GET, new Blocklist()));
40 Futures.addCallback(
41 future,
42 new FutureCallback<>() {
43 @Override
44 public void onSuccess(final Iq result) {
45 final var blocklist = result.getExtension(Blocklist.class);
46 if (blocklist == null) {
47 Log.d(
48 Config.LOGTAG,
49 getAccount().getJid().asBareJid()
50 + ": invalid blocklist response");
51 return;
52 }
53 final var addresses = itemsAsAddresses(blocklist.getItems());
54 Log.d(
55 Config.LOGTAG,
56 getAccount().getJid().asBareJid()
57 + ": discovered blocklist with "
58 + addresses.size()
59 + " items");
60 setBlocklist(addresses);
61 removeBlockedConversations(addresses);
62 service.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
63 }
64
65 @Override
66 public void onFailure(@NonNull final Throwable throwable) {
67 Log.w(
68 Config.LOGTAG,
69 getAccount().getJid().asBareJid()
70 + ": could not retrieve blocklist",
71 throwable);
72 }
73 },
74 MoreExecutors.directExecutor());
75 }
76
77 public void pushBlock(final Iq request) {
78 if (connection.fromServer(request)) {
79 final var block = request.getExtension(Block.class);
80 final var addresses = itemsAsAddresses(block.getItems());
81 synchronized (this.blocklist) {
82 this.blocklist.addAll(addresses);
83 }
84 this.removeBlockedConversations(addresses);
85 this.service.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
86 this.connection.sendResultFor(request);
87 } else {
88 this.connection.sendErrorFor(request, Error.Type.AUTH, new Condition.Forbidden());
89 }
90 }
91
92 public void pushUnblock(final Iq request) {
93 if (connection.fromServer(request)) {
94 final var unblock = request.getExtension(Unblock.class);
95 final var address = itemsAsAddresses(unblock.getItems());
96 synchronized (this.blocklist) {
97 this.blocklist.removeAll(address);
98 }
99 this.service.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
100 this.connection.sendResultFor(request);
101 } else {
102 this.connection.sendErrorFor(request, Error.Type.AUTH, new Condition.Forbidden());
103 }
104 }
105
106 private void removeBlockedConversations(final Collection<Jid> addresses) {
107 var removed = false;
108 for (final Jid address : addresses) {
109 removed |= service.removeBlockedConversations(getAccount(), address);
110 }
111 if (removed) {
112 service.updateConversationUi();
113 }
114 }
115
116 public ImmutableSet<Jid> getBlocklist() {
117 synchronized (this.blocklist) {
118 return ImmutableSet.copyOf(this.blocklist);
119 }
120 }
121
122 private void setBlocklist(final Collection<Jid> addresses) {
123 synchronized (this.blocklist) {
124 this.blocklist.clear();
125 this.blocklist.addAll(addresses);
126 }
127 }
128
129 public boolean hasFeature() {
130 return getManager(DiscoManager.class).hasServerFeature(Namespace.BLOCKING);
131 }
132
133 private static Set<Jid> itemsAsAddresses(final Collection<Item> items) {
134 final var builder = new ImmutableSet.Builder<Jid>();
135 for (final var item : items) {
136 final var jid = Jid.Invalid.getNullForInvalid(item.getJid());
137 if (jid == null) {
138 continue;
139 }
140 builder.add(jid);
141 }
142 return builder.build();
143 }
144}