1package eu.siacs.conversations.entities;
2
3import android.util.Pair;
4import com.google.common.base.Strings;
5import com.google.common.collect.Iterables;
6import eu.siacs.conversations.xmpp.Jid;
7import eu.siacs.conversations.xmpp.manager.DiscoManager;
8import im.conversations.android.xmpp.model.disco.info.Identity;
9import im.conversations.android.xmpp.model.stanza.Presence;
10import java.util.ArrayList;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.Set;
16
17public class Presences {
18 private final HashMap<String, Presence> presences = new HashMap<>();
19 private final Contact contact;
20
21 public Presences(final Contact contact) {
22 this.contact = contact;
23 }
24
25 private static String nameWithoutVersion(String name) {
26 String[] parts = name.split(" ");
27 if (parts.length > 1 && Character.isDigit(parts[parts.length - 1].charAt(0))) {
28 StringBuilder output = new StringBuilder();
29 for (int i = 0; i < parts.length - 1; ++i) {
30 if (output.length() != 0) {
31 output.append(' ');
32 }
33 output.append(parts[i]);
34 }
35 return output.toString();
36 } else {
37 return name;
38 }
39 }
40
41 public List<Presence> getPresences() {
42 synchronized (this.presences) {
43 return new ArrayList<>(this.presences.values());
44 }
45 }
46
47 public Map<String, Presence> getPresencesMap() {
48 synchronized (this.presences) {
49 return new HashMap<>(this.presences);
50 }
51 }
52
53 public Presence get(String resource) {
54 synchronized (this.presences) {
55 return this.presences.get(resource);
56 }
57 }
58
59 public void updatePresence(String resource, Presence presence) {
60 synchronized (this.presences) {
61 this.presences.put(resource, presence);
62 }
63 }
64
65 public void removePresence(String resource) {
66 synchronized (this.presences) {
67 this.presences.remove(resource);
68 }
69 }
70
71 public void clearPresences() {
72 synchronized (this.presences) {
73 this.presences.clear();
74 }
75 }
76
77 public Presence.Availability getShownStatus() {
78 Presence.Availability highestAvailability = Presence.Availability.OFFLINE;
79 synchronized (this.presences) {
80 for (final Presence p : presences.values()) {
81 final var availability = p.getAvailability();
82 if (availability == Presence.Availability.DND) {
83 return availability;
84 } else if (availability.compareTo(highestAvailability) < 0) {
85 highestAvailability = availability;
86 }
87 }
88 }
89 return highestAvailability;
90 }
91
92 public int size() {
93 synchronized (this.presences) {
94 return presences.size();
95 }
96 }
97
98 public boolean isEmpty() {
99 synchronized (this.presences) {
100 return this.presences.isEmpty();
101 }
102 }
103
104 public String[] toResourceArray() {
105 synchronized (this.presences) {
106 final String[] presencesArray = new String[presences.size()];
107 presences.keySet().toArray(presencesArray);
108 return presencesArray;
109 }
110 }
111
112 public List<PresenceTemplate> asTemplates() {
113 synchronized (this.presences) {
114 ArrayList<PresenceTemplate> templates = new ArrayList<>(presences.size());
115 for (Presence presence : this.presences.values()) {
116 String message = Strings.nullToEmpty(presence.getStatus()).trim();
117 if (Strings.isNullOrEmpty(message)) {
118 continue;
119 }
120 templates.add(new PresenceTemplate(presence.getAvailability(), message));
121 }
122 return templates;
123 }
124 }
125
126 public boolean has(String presence) {
127 synchronized (this.presences) {
128 return presences.containsKey(presence);
129 }
130 }
131
132 public Set<String> getStatusMessages() {
133 Set<String> messages = new HashSet<>();
134 synchronized (this.presences) {
135 for (Presence presence : this.presences.values()) {
136 String message = Strings.nullToEmpty(presence.getStatus()).trim();
137 if (Strings.isNullOrEmpty(message)) {
138 continue;
139 }
140 messages.add(message);
141 }
142 }
143 return messages;
144 }
145
146 public Set<Jid> getFullJids() {
147 final Set<Jid> jids = new HashSet<>();
148 synchronized (this.presences) {
149 for (var resource : this.presences.keySet()) {
150 final var jid = Strings.isNullOrEmpty(resource) ? contact.getJid().asBareJid() : contact.getJid().withResource(resource);
151 jids.add(jid);
152 }
153 }
154 jids.add(contact.getJid().asBareJid());
155 return jids;
156 }
157
158 public boolean allOrNonSupport(String namespace) {
159 final var connection = this.contact.getAccount().getXmppConnection();
160 if (connection == null) {
161 return true;
162 }
163 synchronized (this.presences) {
164 for (var resource : this.presences.keySet()) {
165 final var disco =
166 connection
167 .getManager(DiscoManager.class)
168 .get(
169 Strings.isNullOrEmpty(resource)
170 ? contact.getJid().asBareJid()
171 : contact.getJid().withResource(resource));
172 if (disco == null || !disco.getFeatureStrings().contains(namespace)) {
173 return false;
174 }
175 }
176 }
177 return true;
178 }
179
180 public boolean anySupport(final String namespace) {
181 final var connection = this.contact.getAccount().getXmppConnection();
182 if (connection == null) {
183 return false;
184 }
185 final var jids = getFullJids();
186 if (jids.size() == 0) {
187 return true;
188 }
189 for (final var jid : jids) {
190 final var disco = connection.getManager(DiscoManager.class).get(jid);
191 if (disco != null && disco.hasFeature(namespace)) {
192 return true;
193 }
194 }
195 return false;
196 }
197
198 public String firstWhichSupport(final String namespace) {
199 final var connection = this.contact.getAccount().getXmppConnection();
200 if (connection == null) {
201 return null;
202 }
203 for (final var jid : getFullJids()) {
204 final var disco = connection.getManager(DiscoManager.class).get(jid);
205 if (disco != null && disco.hasFeature(namespace)) {
206 return jid.getResource() == null ? "" : jid.getResource();
207 }
208 }
209 return null;
210 }
211
212 public boolean anyIdentity(final String category, final String type) {
213 final var connection = this.contact.getAccount().getXmppConnection();
214 if (connection == null) {
215 return false;
216 }
217 for (final var jid : getFullJids()) {
218 final var disco = connection.getManager(DiscoManager.class).get(jid);
219 if (disco != null && disco.hasIdentityWithCategoryAndType(category, type)) {
220 return true;
221 }
222 }
223 return false;
224 }
225
226 public Pair<Map<String, String>, Map<String, String>> toTypeAndNameMap() {
227 Map<String, String> typeMap = new HashMap<>();
228 Map<String, String> nameMap = new HashMap<>();
229 final var connection = this.contact.getAccount().getXmppConnection();
230 if (connection == null) {
231 return new Pair<>(typeMap, nameMap);
232 }
233 synchronized (this.presences) {
234 for (final String resource : this.presences.keySet()) {
235 final var serviceDiscoveryResult =
236 connection
237 .getManager(DiscoManager.class)
238 .get(
239 Strings.isNullOrEmpty(resource)
240 ? contact.getJid().asBareJid()
241 : contact.getJid().withResource(resource));
242 if (serviceDiscoveryResult != null
243 && !serviceDiscoveryResult.getIdentities().isEmpty()) {
244 final Identity identity =
245 Iterables.getFirst(serviceDiscoveryResult.getIdentities(), null);
246 String type = identity.getType();
247 String name = identity.getIdentityName();
248 if (type != null) {
249 typeMap.put(resource, type);
250 }
251 if (name != null) {
252 nameMap.put(resource, nameWithoutVersion(name));
253 }
254 }
255 }
256 }
257 return new Pair<>(typeMap, nameMap);
258 }
259}