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.manager.DiscoManager;
7import im.conversations.android.xmpp.model.disco.info.Identity;
8import im.conversations.android.xmpp.model.stanza.Presence;
9import java.util.ArrayList;
10import java.util.HashMap;
11import java.util.HashSet;
12import java.util.List;
13import java.util.Map;
14import java.util.Set;
15
16public class Presences {
17 private final HashMap<String, Presence> presences = new HashMap<>();
18 private final Contact contact;
19
20 public Presences(final Contact contact) {
21 this.contact = contact;
22 }
23
24 private static String nameWithoutVersion(String name) {
25 String[] parts = name.split(" ");
26 if (parts.length > 1 && Character.isDigit(parts[parts.length - 1].charAt(0))) {
27 StringBuilder output = new StringBuilder();
28 for (int i = 0; i < parts.length - 1; ++i) {
29 if (output.length() != 0) {
30 output.append(' ');
31 }
32 output.append(parts[i]);
33 }
34 return output.toString();
35 } else {
36 return name;
37 }
38 }
39
40 public List<Presence> getPresences() {
41 synchronized (this.presences) {
42 return new ArrayList<>(this.presences.values());
43 }
44 }
45
46 public Map<String, Presence> getPresencesMap() {
47 synchronized (this.presences) {
48 return new HashMap<>(this.presences);
49 }
50 }
51
52 public Presence get(String resource) {
53 synchronized (this.presences) {
54 return this.presences.get(resource);
55 }
56 }
57
58 public void updatePresence(String resource, Presence presence) {
59 synchronized (this.presences) {
60 this.presences.put(resource, presence);
61 }
62 }
63
64 public void removePresence(String resource) {
65 synchronized (this.presences) {
66 this.presences.remove(resource);
67 }
68 }
69
70 public void clearPresences() {
71 synchronized (this.presences) {
72 this.presences.clear();
73 }
74 }
75
76 public Presence.Availability getShownStatus() {
77 Presence.Availability highestAvailability = Presence.Availability.OFFLINE;
78 synchronized (this.presences) {
79 for (final Presence p : presences.values()) {
80 final var availability = p.getAvailability();
81 if (availability == Presence.Availability.DND) {
82 return availability;
83 } else if (availability.compareTo(highestAvailability) < 0) {
84 highestAvailability = availability;
85 }
86 }
87 }
88 return highestAvailability;
89 }
90
91 public int size() {
92 synchronized (this.presences) {
93 return presences.size();
94 }
95 }
96
97 public boolean isEmpty() {
98 synchronized (this.presences) {
99 return this.presences.isEmpty();
100 }
101 }
102
103 public String[] toResourceArray() {
104 synchronized (this.presences) {
105 final String[] presencesArray = new String[presences.size()];
106 presences.keySet().toArray(presencesArray);
107 return presencesArray;
108 }
109 }
110
111 public List<PresenceTemplate> asTemplates() {
112 synchronized (this.presences) {
113 ArrayList<PresenceTemplate> templates = new ArrayList<>(presences.size());
114 for (Presence presence : this.presences.values()) {
115 String message = Strings.nullToEmpty(presence.getStatus()).trim();
116 if (Strings.isNullOrEmpty(message)) {
117 continue;
118 }
119 templates.add(new PresenceTemplate(presence.getAvailability(), message));
120 }
121 return templates;
122 }
123 }
124
125 public boolean has(String presence) {
126 synchronized (this.presences) {
127 return presences.containsKey(presence);
128 }
129 }
130
131 public Set<String> getStatusMessages() {
132 Set<String> messages = new HashSet<>();
133 synchronized (this.presences) {
134 for (Presence presence : this.presences.values()) {
135 String message = Strings.nullToEmpty(presence.getStatus()).trim();
136 if (Strings.isNullOrEmpty(message)) {
137 continue;
138 }
139 messages.add(message);
140 }
141 }
142 return messages;
143 }
144
145 public boolean allOrNonSupport(String namespace) {
146 final var connection = this.contact.getAccount().getXmppConnection();
147 if (connection == null) {
148 return true;
149 }
150 synchronized (this.presences) {
151 for (var resource : this.presences.keySet()) {
152 final var disco =
153 connection
154 .getManager(DiscoManager.class)
155 .get(
156 Strings.isNullOrEmpty(resource)
157 ? contact.getJid().asBareJid()
158 : contact.getJid().withResource(resource));
159 if (disco == null || !disco.getFeatureStrings().contains(namespace)) {
160 return false;
161 }
162 }
163 }
164 return true;
165 }
166
167 public Pair<Map<String, String>, Map<String, String>> toTypeAndNameMap() {
168 Map<String, String> typeMap = new HashMap<>();
169 Map<String, String> nameMap = new HashMap<>();
170 final var connection = this.contact.getAccount().getXmppConnection();
171 if (connection == null) {
172 return new Pair<>(typeMap, nameMap);
173 }
174 synchronized (this.presences) {
175 for (final String resource : this.presences.keySet()) {
176 final var serviceDiscoveryResult =
177 connection
178 .getManager(DiscoManager.class)
179 .get(
180 Strings.isNullOrEmpty(resource)
181 ? contact.getJid().asBareJid()
182 : contact.getJid().withResource(resource));
183 if (serviceDiscoveryResult != null
184 && !serviceDiscoveryResult.getIdentities().isEmpty()) {
185 final Identity identity =
186 Iterables.getFirst(serviceDiscoveryResult.getIdentities(), null);
187 String type = identity.getType();
188 String name = identity.getIdentityName();
189 if (type != null) {
190 typeMap.put(resource, type);
191 }
192 if (name != null) {
193 nameMap.put(resource, nameWithoutVersion(name));
194 }
195 }
196 }
197 }
198 return new Pair<>(typeMap, nameMap);
199 }
200}