1package eu.siacs.conversations.entities;
2
3import android.annotation.SuppressLint;
4
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.HashSet;
8import java.util.LinkedHashMap;
9import java.util.List;
10import java.util.Map;
11import java.util.Set;
12
13import eu.siacs.conversations.R;
14import eu.siacs.conversations.xmpp.forms.Data;
15import eu.siacs.conversations.xmpp.forms.Field;
16import eu.siacs.conversations.xmpp.jid.InvalidJidException;
17import eu.siacs.conversations.xmpp.jid.Jid;
18import eu.siacs.conversations.xmpp.pep.Avatar;
19
20@SuppressLint("DefaultLocale")
21public class MucOptions {
22
23 public Account getAccount() {
24 return this.conversation.getAccount();
25 }
26
27 public void setSelf(User user) {
28 this.self = user;
29 }
30
31 public enum Affiliation {
32 OWNER("owner", 4, R.string.owner),
33 ADMIN("admin", 3, R.string.admin),
34 MEMBER("member", 2, R.string.member),
35 OUTCAST("outcast", 0, R.string.outcast),
36 NONE("none", 1, R.string.no_affiliation);
37
38 Affiliation(String string, int rank, int resId) {
39 this.string = string;
40 this.resId = resId;
41 this.rank = rank;
42 }
43
44 private String string;
45 private int resId;
46 private int rank;
47
48 public int getResId() {
49 return resId;
50 }
51
52 @Override
53 public String toString() {
54 return this.string;
55 }
56
57 public boolean outranks(Affiliation affiliation) {
58 return rank > affiliation.rank;
59 }
60
61 public boolean ranks(Affiliation affiliation) {
62 return rank >= affiliation.rank;
63 }
64 }
65
66 public enum Role {
67 MODERATOR("moderator", R.string.moderator,3),
68 VISITOR("visitor", R.string.visitor,1),
69 PARTICIPANT("participant", R.string.participant,2),
70 NONE("none", R.string.no_role,0);
71
72 Role(String string, int resId, int rank) {
73 this.string = string;
74 this.resId = resId;
75 this.rank = rank;
76 }
77
78 private String string;
79 private int resId;
80 private int rank;
81
82 public int getResId() {
83 return resId;
84 }
85
86 @Override
87 public String toString() {
88 return this.string;
89 }
90
91 public boolean ranks(Role role) {
92 return rank >= role.rank;
93 }
94 }
95
96 public enum Error {
97 NO_RESPONSE,
98 NONE,
99 NICK_IN_USE,
100 PASSWORD_REQUIRED,
101 BANNED,
102 MEMBERS_ONLY,
103 KICKED,
104 SHUTDOWN,
105 UNKNOWN
106 }
107
108 public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
109 public static final String STATUS_CODE_SELF_PRESENCE = "110";
110 public static final String STATUS_CODE_BANNED = "301";
111 public static final String STATUS_CODE_CHANGED_NICK = "303";
112 public static final String STATUS_CODE_KICKED = "307";
113 public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
114 public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
115 public static final String STATUS_CODE_SHUTDOWN = "332";
116
117 private interface OnEventListener {
118 void onSuccess();
119
120 void onFailure();
121 }
122
123 public interface OnRenameListener extends OnEventListener {
124
125 }
126
127 public static class User {
128 private Role role = Role.NONE;
129 private Affiliation affiliation = Affiliation.NONE;
130 private Jid jid;
131 private Jid fullJid;
132 private long pgpKeyId = 0;
133 private Avatar avatar;
134 private MucOptions options;
135
136 public User(MucOptions options, Jid from) {
137 this.options = options;
138 this.fullJid = from;
139 }
140
141 public String getName() {
142 return this.fullJid.getResourcepart();
143 }
144
145 public void setJid(Jid jid) {
146 this.jid = jid;
147 }
148
149 public Jid getJid() {
150 return this.jid;
151 }
152
153 public Role getRole() {
154 return this.role;
155 }
156
157 public void setRole(String role) {
158 role = role.toLowerCase();
159 switch (role) {
160 case "moderator":
161 this.role = Role.MODERATOR;
162 break;
163 case "participant":
164 this.role = Role.PARTICIPANT;
165 break;
166 case "visitor":
167 this.role = Role.VISITOR;
168 break;
169 default:
170 this.role = Role.NONE;
171 break;
172 }
173 }
174
175 @Override
176 public boolean equals(Object other) {
177 if (this == other) {
178 return true;
179 } else if (!(other instanceof User)) {
180 return false;
181 } else {
182 User o = (User) other;
183 return getName() != null && getName().equals(o.getName())
184 && jid != null && jid.equals(o.jid)
185 && affiliation == o.affiliation
186 && role == o.role;
187 }
188 }
189
190 public Affiliation getAffiliation() {
191 return this.affiliation;
192 }
193
194 public void setAffiliation(String affiliation) {
195 affiliation = affiliation.toLowerCase();
196 switch (affiliation) {
197 case "admin":
198 this.affiliation = Affiliation.ADMIN;
199 break;
200 case "owner":
201 this.affiliation = Affiliation.OWNER;
202 break;
203 case "member":
204 this.affiliation = Affiliation.MEMBER;
205 break;
206 case "outcast":
207 this.affiliation = Affiliation.OUTCAST;
208 break;
209 default:
210 this.affiliation = Affiliation.NONE;
211 }
212 }
213
214 public void setPgpKeyId(long id) {
215 this.pgpKeyId = id;
216 }
217
218 public long getPgpKeyId() {
219 return this.pgpKeyId;
220 }
221
222 public Contact getContact() {
223 return getAccount().getRoster().getContactFromRoster(getJid());
224 }
225
226 public boolean setAvatar(Avatar avatar) {
227 if (this.avatar != null && this.avatar.equals(avatar)) {
228 return false;
229 } else {
230 this.avatar = avatar;
231 return true;
232 }
233 }
234
235 public String getAvatar() {
236 return avatar == null ? null : avatar.getFilename();
237 }
238
239 public Account getAccount() {
240 return options.getAccount();
241 }
242
243 public Jid getFullJid() {
244 return fullJid;
245 }
246 }
247
248 private Account account;
249 private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>());
250 private final Set<Jid> members = Collections.synchronizedSet(new HashSet<Jid>());
251 private final List<String> features = new ArrayList<>();
252 private Data form = new Data();
253 private Conversation conversation;
254 private boolean isOnline = false;
255 private Error error = Error.NONE;
256 public OnRenameListener onRenameListener = null;
257 private User self;
258 private String subject = null;
259 private String password = null;
260 public boolean mNickChangingInProgress = false;
261
262 public MucOptions(Conversation conversation) {
263 this.account = conversation.getAccount();
264 this.conversation = conversation;
265 this.self = new User(this,createJoinJid(getProposedNick()));
266 }
267
268 public void updateFeatures(ArrayList<String> features) {
269 this.features.clear();
270 this.features.addAll(features);
271 }
272
273 public void updateFormData(Data form) {
274 this.form = form;
275 }
276
277 public boolean hasFeature(String feature) {
278 return this.features.contains(feature);
279 }
280
281 public boolean canInvite() {
282 Field field = this.form.getFieldByName("muc#roomconfig_allowinvites");
283 return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
284 }
285
286 public boolean canChangeSubject() {
287 Field field = this.form.getFieldByName("muc#roomconfig_changesubject");
288 return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
289 }
290
291 public boolean participating() {
292 return !online()
293 || self.getRole().ranks(Role.PARTICIPANT)
294 || hasFeature("muc_unmoderated");
295 }
296
297 public boolean membersOnly() {
298 return hasFeature("muc_membersonly");
299 }
300
301 public boolean mamSupport() {
302 // Update with "urn:xmpp:mam:1" once we support it
303 return hasFeature("urn:xmpp:mam:0");
304 }
305
306 public boolean nonanonymous() {
307 return hasFeature("muc_nonanonymous");
308 }
309
310 public boolean persistent() {
311 return hasFeature("muc_persistent");
312 }
313
314 public boolean moderated() {
315 return hasFeature("muc_moderated");
316 }
317
318 public User deleteUser(String name) {
319 return this.users.remove(name);
320 }
321
322 public void addUser(User user) {
323 this.users.put(user.getName(), user);
324 }
325
326 public User findUser(String name) {
327 return this.users.get(name);
328 }
329
330 public boolean isUserInRoom(String name) {
331 return findUser(name) != null;
332 }
333
334 public void setError(Error error) {
335 this.isOnline = isOnline && error == Error.NONE;
336 this.error = error;
337 }
338
339 public void setOnline() {
340 this.isOnline = true;
341 }
342
343 public ArrayList<User> getUsers() {
344 return new ArrayList<>(users.values());
345 }
346
347 public List<User> getUsers(int max) {
348 ArrayList<User> users = getUsers();
349 return users.subList(0, Math.min(max, users.size()));
350 }
351
352 public int getUserCount() {
353 return this.users.size();
354 }
355
356 public String getProposedNick() {
357 if (conversation.getBookmark() != null
358 && conversation.getBookmark().getNick() != null
359 && !conversation.getBookmark().getNick().isEmpty()) {
360 return conversation.getBookmark().getNick();
361 } else if (!conversation.getJid().isBareJid()) {
362 return conversation.getJid().getResourcepart();
363 } else {
364 return account.getUsername();
365 }
366 }
367
368 public String getActualNick() {
369 if (this.self.getName() != null) {
370 return this.self.getName();
371 } else {
372 return this.getProposedNick();
373 }
374 }
375
376 public boolean online() {
377 return this.isOnline;
378 }
379
380 public Error getError() {
381 return this.error;
382 }
383
384 public void setOnRenameListener(OnRenameListener listener) {
385 this.onRenameListener = listener;
386 }
387
388 public void setOffline() {
389 this.users.clear();
390 this.error = Error.NO_RESPONSE;
391 this.isOnline = false;
392 }
393
394 public User getSelf() {
395 return self;
396 }
397
398 public void setSubject(String content) {
399 this.subject = content;
400 }
401
402 public String getSubject() {
403 return this.subject;
404 }
405
406 public String createNameFromParticipants() {
407 if (users.size() >= 2) {
408 List<String> names = new ArrayList<>();
409 for (User user : getUsers(5)) {
410 Contact contact = user.getContact();
411 if (contact != null && !contact.getDisplayName().isEmpty()) {
412 names.add(contact.getDisplayName().split("\\s+")[0]);
413 } else {
414 names.add(user.getName());
415 }
416 }
417 StringBuilder builder = new StringBuilder();
418 for (int i = 0; i < names.size(); ++i) {
419 builder.append(names.get(i));
420 if (i != names.size() - 1) {
421 builder.append(", ");
422 }
423 }
424 return builder.toString();
425 } else {
426 return null;
427 }
428 }
429
430 public long[] getPgpKeyIds() {
431 List<Long> ids = new ArrayList<>();
432 for (User user : this.users.values()) {
433 if (user.getPgpKeyId() != 0) {
434 ids.add(user.getPgpKeyId());
435 }
436 }
437 ids.add(account.getPgpId());
438 long[] primitiveLongArray = new long[ids.size()];
439 for (int i = 0; i < ids.size(); ++i) {
440 primitiveLongArray[i] = ids.get(i);
441 }
442 return primitiveLongArray;
443 }
444
445 public boolean pgpKeysInUse() {
446 for (User user : this.users.values()) {
447 if (user.getPgpKeyId() != 0) {
448 return true;
449 }
450 }
451 return false;
452 }
453
454 public boolean everybodyHasKeys() {
455 for (User user : this.users.values()) {
456 if (user.getPgpKeyId() == 0) {
457 return false;
458 }
459 }
460 return true;
461 }
462
463 public Jid createJoinJid(String nick) {
464 try {
465 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
466 } catch (final InvalidJidException e) {
467 return null;
468 }
469 }
470
471 public Jid getTrueCounterpart(String name) {
472 User user = findUser(name);
473 return user == null ? null : user.getJid();
474 }
475
476 public String getPassword() {
477 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
478 if (this.password == null && conversation.getBookmark() != null
479 && conversation.getBookmark().getPassword() != null) {
480 return conversation.getBookmark().getPassword();
481 } else {
482 return this.password;
483 }
484 }
485
486 public void setPassword(String password) {
487 if (conversation.getBookmark() != null) {
488 conversation.getBookmark().setPassword(password);
489 } else {
490 this.password = password;
491 }
492 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
493 }
494
495 public Conversation getConversation() {
496 return this.conversation;
497 }
498
499 public void putMember(Jid jid) {
500 members.add(jid);
501 }
502
503 public List<Jid> getMembers() {
504 return new ArrayList<>(members);
505 }
506}