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 return jids;
155 }
156
157 public boolean allOrNonSupport(String namespace) {
158 final var connection = this.contact.getAccount().getXmppConnection();
159 if (connection == null) {
160 return true;
161 }
162 synchronized (this.presences) {
163 for (var resource : this.presences.keySet()) {
164 final var disco =
165 connection
166 .getManager(DiscoManager.class)
167 .get(
168 Strings.isNullOrEmpty(resource)
169 ? contact.getJid().asBareJid()
170 : contact.getJid().withResource(resource));
171 if (disco == null || !disco.getFeatureStrings().contains(namespace)) {
172 return false;
173 }
174 }
175 }
176 return true;
177 }
178
179 public boolean anySupport(final String namespace) {
180 final var connection = this.contact.getAccount().getXmppConnection();
181 if (connection == null) {
182 return false;
183 }
184 final var jids = getFullJids();
185 if (jids.size() == 0) {
186 return true;
187 }
188 for (final var jid : jids) {
189 final var disco = connection.getManager(DiscoManager.class).get(jid);
190 if (disco != null && disco.hasFeature(namespace)) {
191 return true;
192 }
193 }
194 return false;
195 }
196
197 public String firstWhichSupport(final String namespace) {
198 final var connection = this.contact.getAccount().getXmppConnection();
199 if (connection == null) {
200 return null;
201 }
202 for (final var jid : getFullJids()) {
203 final var disco = connection.getManager(DiscoManager.class).get(jid);
204 if (disco != null && disco.hasFeature(namespace)) {
205 return jid.getResource();
206 }
207 }
208 return null;
209 }
210
211 public boolean anyIdentity(final String category, final String type) {
212 final var connection = this.contact.getAccount().getXmppConnection();
213 if (connection == null) {
214 return false;
215 }
216 for (final var jid : getFullJids()) {
217 final var disco = connection.getManager(DiscoManager.class).get(jid);
218 if (disco != null && disco.hasIdentityWithCategoryAndType(category, type)) {
219 return true;
220 }
221 }
222 return false;
223 }
224
225 public Pair<Map<String, String>, Map<String, String>> toTypeAndNameMap() {
226 Map<String, String> typeMap = new HashMap<>();
227 Map<String, String> nameMap = new HashMap<>();
228 final var connection = this.contact.getAccount().getXmppConnection();
229 if (connection == null) {
230 return new Pair<>(typeMap, nameMap);
231 }
232 synchronized (this.presences) {
233 for (final String resource : this.presences.keySet()) {
234 final var serviceDiscoveryResult =
235 connection
236 .getManager(DiscoManager.class)
237 .get(
238 Strings.isNullOrEmpty(resource)
239 ? contact.getJid().asBareJid()
240 : contact.getJid().withResource(resource));
241 if (serviceDiscoveryResult != null
242 && !serviceDiscoveryResult.getIdentities().isEmpty()) {
243 final Identity identity =
244 Iterables.getFirst(serviceDiscoveryResult.getIdentities(), null);
245 String type = identity.getType();
246 String name = identity.getIdentityName();
247 if (type != null) {
248 typeMap.put(resource, type);
249 }
250 if (name != null) {
251 nameMap.put(resource, nameWithoutVersion(name));
252 }
253 }
254 }
255 }
256 return new Pair<>(typeMap, nameMap);
257 }
258}