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.List;
9import java.util.Set;
10
11import eu.siacs.conversations.R;
12import eu.siacs.conversations.xmpp.forms.Data;
13import eu.siacs.conversations.xmpp.forms.Field;
14import eu.siacs.conversations.xmpp.jid.InvalidJidException;
15import eu.siacs.conversations.xmpp.jid.Jid;
16import eu.siacs.conversations.xmpp.pep.Avatar;
17
18@SuppressLint("DefaultLocale")
19public class MucOptions {
20
21 private boolean mAutoPushConfiguration = true;
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 void changeAffiliation(Jid jid, Affiliation affiliation) {
32 User user = findUserByRealJid(jid);
33 synchronized (users) {
34 if (user != null && user.getRole() == Role.NONE) {
35 users.remove(user);
36 if (affiliation.ranks(Affiliation.MEMBER)) {
37 user.affiliation = affiliation;
38 users.add(user);
39 }
40 }
41 }
42 }
43
44 public void flagNoAutoPushConfiguration() {
45 mAutoPushConfiguration = false;
46 }
47
48 public boolean autoPushConfiguration() {
49 return mAutoPushConfiguration;
50 }
51
52 public enum Affiliation {
53 OWNER("owner", 4, R.string.owner),
54 ADMIN("admin", 3, R.string.admin),
55 MEMBER("member", 2, R.string.member),
56 OUTCAST("outcast", 0, R.string.outcast),
57 NONE("none", 1, R.string.no_affiliation);
58
59 Affiliation(String string, int rank, int resId) {
60 this.string = string;
61 this.resId = resId;
62 this.rank = rank;
63 }
64
65 private String string;
66 private int resId;
67 private int rank;
68
69 public int getResId() {
70 return resId;
71 }
72
73 @Override
74 public String toString() {
75 return this.string;
76 }
77
78 public boolean outranks(Affiliation affiliation) {
79 return rank > affiliation.rank;
80 }
81
82 public boolean ranks(Affiliation affiliation) {
83 return rank >= affiliation.rank;
84 }
85 }
86
87 public enum Role {
88 MODERATOR("moderator", R.string.moderator,3),
89 VISITOR("visitor", R.string.visitor,1),
90 PARTICIPANT("participant", R.string.participant,2),
91 NONE("none", R.string.no_role,0);
92
93 Role(String string, int resId, int rank) {
94 this.string = string;
95 this.resId = resId;
96 this.rank = rank;
97 }
98
99 private String string;
100 private int resId;
101 private int rank;
102
103 public int getResId() {
104 return resId;
105 }
106
107 @Override
108 public String toString() {
109 return this.string;
110 }
111
112 public boolean ranks(Role role) {
113 return rank >= role.rank;
114 }
115 }
116
117 public enum Error {
118 NO_RESPONSE,
119 SERVER_NOT_FOUND,
120 NONE,
121 NICK_IN_USE,
122 PASSWORD_REQUIRED,
123 BANNED,
124 MEMBERS_ONLY,
125 KICKED,
126 SHUTDOWN,
127 UNKNOWN
128 }
129
130 public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
131 public static final String STATUS_CODE_SELF_PRESENCE = "110";
132 public static final String STATUS_CODE_ROOM_CREATED = "201";
133 public static final String STATUS_CODE_BANNED = "301";
134 public static final String STATUS_CODE_CHANGED_NICK = "303";
135 public static final String STATUS_CODE_KICKED = "307";
136 public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
137 public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
138 public static final String STATUS_CODE_SHUTDOWN = "332";
139
140 private interface OnEventListener {
141 void onSuccess();
142
143 void onFailure();
144 }
145
146 public interface OnRenameListener extends OnEventListener {
147
148 }
149
150 public static class User implements Comparable<User> {
151 private Role role = Role.NONE;
152 private Affiliation affiliation = Affiliation.NONE;
153 private Jid realJid;
154 private Jid fullJid;
155 private long pgpKeyId = 0;
156 private Avatar avatar;
157 private MucOptions options;
158
159 public User(MucOptions options, Jid from) {
160 this.options = options;
161 this.fullJid = from;
162 }
163
164 public String getName() {
165 return fullJid == null ? null : fullJid.getResourcepart();
166 }
167
168 public void setRealJid(Jid jid) {
169 this.realJid = jid != null ? jid.toBareJid() : null;
170 }
171
172 public Role getRole() {
173 return this.role;
174 }
175
176 public void setRole(String role) {
177 if (role == null) {
178 this.role = Role.NONE;
179 return;
180 }
181 role = role.toLowerCase();
182 switch (role) {
183 case "moderator":
184 this.role = Role.MODERATOR;
185 break;
186 case "participant":
187 this.role = Role.PARTICIPANT;
188 break;
189 case "visitor":
190 this.role = Role.VISITOR;
191 break;
192 default:
193 this.role = Role.NONE;
194 break;
195 }
196 }
197
198 public Affiliation getAffiliation() {
199 return this.affiliation;
200 }
201
202 public void setAffiliation(String affiliation) {
203 if (affiliation == null) {
204 this.affiliation = Affiliation.NONE;
205 return;
206 }
207 affiliation = affiliation.toLowerCase();
208 switch (affiliation) {
209 case "admin":
210 this.affiliation = Affiliation.ADMIN;
211 break;
212 case "owner":
213 this.affiliation = Affiliation.OWNER;
214 break;
215 case "member":
216 this.affiliation = Affiliation.MEMBER;
217 break;
218 case "outcast":
219 this.affiliation = Affiliation.OUTCAST;
220 break;
221 default:
222 this.affiliation = Affiliation.NONE;
223 }
224 }
225
226 public void setPgpKeyId(long id) {
227 this.pgpKeyId = id;
228 }
229
230 public long getPgpKeyId() {
231 return this.pgpKeyId;
232 }
233
234 public Contact getContact() {
235 if (fullJid != null) {
236 return getAccount().getRoster().getContactFromRoster(realJid);
237 } else if (realJid != null){
238 return getAccount().getRoster().getContact(realJid);
239 } else {
240 return null;
241 }
242 }
243
244 public boolean setAvatar(Avatar avatar) {
245 if (this.avatar != null && this.avatar.equals(avatar)) {
246 return false;
247 } else {
248 this.avatar = avatar;
249 return true;
250 }
251 }
252
253 public String getAvatar() {
254 return avatar == null ? null : avatar.getFilename();
255 }
256
257 public Account getAccount() {
258 return options.getAccount();
259 }
260
261 public Jid getFullJid() {
262 return fullJid;
263 }
264
265 @Override
266 public boolean equals(Object o) {
267 if (this == o) return true;
268 if (o == null || getClass() != o.getClass()) return false;
269
270 User user = (User) o;
271
272 if (role != user.role) return false;
273 if (affiliation != user.affiliation) return false;
274 if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null)
275 return false;
276 return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null;
277
278 }
279
280 @Override
281 public int hashCode() {
282 int result = role != null ? role.hashCode() : 0;
283 result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0);
284 result = 31 * result + (realJid != null ? realJid.hashCode() : 0);
285 result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0);
286 return result;
287 }
288
289 @Override
290 public String toString() {
291 return "[fulljid:"+String.valueOf(fullJid)+",realjid:"+String.valueOf(realJid)+",affiliation"+affiliation.toString()+"]";
292 }
293
294 public boolean realJidMatchesAccount() {
295 return realJid != null && realJid.equals(options.account.getJid().toBareJid());
296 }
297
298 @Override
299 public int compareTo(User another) {
300 if (another.getAffiliation().outranks(getAffiliation())) {
301 return 1;
302 } else if (getAffiliation().outranks(another.getAffiliation())) {
303 return -1;
304 } else {
305 return getComparableName().compareToIgnoreCase(another.getComparableName());
306 }
307 }
308
309
310 private String getComparableName() {
311 Contact contact = getContact();
312 if (contact != null) {
313 return contact.getDisplayName();
314 } else {
315 String name = getName();
316 return name == null ? "" : name;
317 }
318 }
319
320 public Jid getRealJid() {
321 return realJid;
322 }
323 }
324
325 private Account account;
326 private final Set<User> users = new HashSet<>();
327 private final List<String> features = new ArrayList<>();
328 private Data form = new Data();
329 private Conversation conversation;
330 private boolean isOnline = false;
331 private Error error = Error.NONE;
332 public OnRenameListener onRenameListener = null;
333 private User self;
334 private String subject = null;
335 private String password = null;
336 public boolean mNickChangingInProgress = false;
337
338 public MucOptions(Conversation conversation) {
339 this.account = conversation.getAccount();
340 this.conversation = conversation;
341 this.self = new User(this,createJoinJid(getProposedNick()));
342 }
343
344 public void updateFeatures(ArrayList<String> features) {
345 this.features.clear();
346 this.features.addAll(features);
347 }
348
349 public void updateFormData(Data form) {
350 this.form = form;
351 }
352
353 public boolean hasFeature(String feature) {
354 return this.features.contains(feature);
355 }
356
357 public boolean canInvite() {
358 Field field = this.form.getFieldByName("muc#roomconfig_allowinvites");
359 return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
360 }
361
362 public boolean canChangeSubject() {
363 Field field = this.form.getFieldByName("muc#roomconfig_changesubject");
364 return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
365 }
366
367 public boolean participating() {
368 return !online()
369 || self.getRole().ranks(Role.PARTICIPANT)
370 || hasFeature("muc_unmoderated");
371 }
372
373 public boolean membersOnly() {
374 return hasFeature("muc_membersonly");
375 }
376
377 public boolean mamSupport() {
378 // Update with "urn:xmpp:mam:1" once we support it
379 return hasFeature("urn:xmpp:mam:0");
380 }
381
382 public boolean nonanonymous() {
383 return hasFeature("muc_nonanonymous");
384 }
385
386 public boolean persistent() {
387 return hasFeature("muc_persistent");
388 }
389
390 public boolean moderated() {
391 return hasFeature("muc_moderated");
392 }
393
394 public User deleteUser(Jid jid) {
395 User user = findUserByFullJid(jid);
396 if (user != null) {
397 synchronized (users) {
398 users.remove(user);
399 if (membersOnly() &&
400 nonanonymous() &&
401 user.affiliation.ranks(Affiliation.MEMBER) &&
402 user.realJid != null) {
403 user.role = Role.NONE;
404 user.avatar = null;
405 user.fullJid = null;
406 users.add(user);
407 }
408 }
409 }
410 return user;
411 }
412
413 public void addUser(User user) {
414 User old;
415 if (user.fullJid == null && user.realJid != null) {
416 old = findUserByRealJid(user.realJid);
417 if (old != null) {
418 if (old.fullJid != null) {
419 return; //don't add. user already exists
420 } else {
421 synchronized (users) {
422 users.remove(old);
423 }
424 }
425 }
426 } else if (user.realJid != null) {
427 old = findUserByRealJid(user.realJid);
428 synchronized (users) {
429 if (old != null && old.fullJid == null) {
430 users.remove(old);
431 }
432 }
433 }
434 old = findUserByFullJid(user.getFullJid());
435 synchronized (this.users) {
436 if (old != null) {
437 users.remove(old);
438 }
439 this.users.add(user);
440 }
441 }
442
443 public User findUserByFullJid(Jid jid) {
444 if (jid == null) {
445 return null;
446 }
447 synchronized (users) {
448 for (User user : users) {
449 if (jid.equals(user.getFullJid())) {
450 return user;
451 }
452 }
453 }
454 return null;
455 }
456
457 private User findUserByRealJid(Jid jid) {
458 if (jid == null) {
459 return null;
460 }
461 synchronized (users) {
462 for (User user : users) {
463 if (jid.equals(user.realJid)) {
464 return user;
465 }
466 }
467 }
468 return null;
469 }
470
471 public boolean isContactInRoom(Contact contact) {
472 return findUserByRealJid(contact.getJid().toBareJid()) != null;
473 }
474
475 public boolean isUserInRoom(Jid jid) {
476 return findUserByFullJid(jid) != null;
477 }
478
479 public void setError(Error error) {
480 this.isOnline = isOnline && error == Error.NONE;
481 this.error = error;
482 }
483
484 public void setOnline() {
485 this.isOnline = true;
486 }
487
488 public ArrayList<User> getUsers() {
489 return getUsers(true);
490 }
491
492 public ArrayList<User> getUsers(boolean includeOffline) {
493 synchronized (users) {
494 if (includeOffline) {
495 return new ArrayList<>(users);
496 } else {
497 ArrayList<User> onlineUsers = new ArrayList<>();
498 for (User user : users) {
499 if (user.getRole().ranks(Role.PARTICIPANT)) {
500 onlineUsers.add(user);
501 }
502 }
503 return onlineUsers;
504 }
505 }
506 }
507
508 public List<User> getUsers(int max) {
509 ArrayList<User> users = getUsers();
510 return users.subList(0, Math.min(max, users.size()));
511 }
512
513 public int getUserCount() {
514 synchronized (users) {
515 return users.size();
516 }
517 }
518
519 public String getProposedNick() {
520 if (conversation.getBookmark() != null
521 && conversation.getBookmark().getNick() != null
522 && !conversation.getBookmark().getNick().trim().isEmpty()) {
523 return conversation.getBookmark().getNick().trim();
524 } else if (!conversation.getJid().isBareJid()) {
525 return conversation.getJid().getResourcepart();
526 } else {
527 return account.getUsername();
528 }
529 }
530
531 public String getActualNick() {
532 if (this.self.getName() != null) {
533 return this.self.getName();
534 } else {
535 return this.getProposedNick();
536 }
537 }
538
539 public boolean online() {
540 return this.isOnline;
541 }
542
543 public Error getError() {
544 return this.error;
545 }
546
547 public void setOnRenameListener(OnRenameListener listener) {
548 this.onRenameListener = listener;
549 }
550
551 public void setOffline() {
552 synchronized (users) {
553 this.users.clear();
554 }
555 this.error = Error.NO_RESPONSE;
556 this.isOnline = false;
557 }
558
559 public User getSelf() {
560 return self;
561 }
562
563 public void setSubject(String content) {
564 this.subject = content;
565 }
566
567 public String getSubject() {
568 return this.subject;
569 }
570
571 public String createNameFromParticipants() {
572 if (getUserCount() >= 2) {
573 List<String> names = new ArrayList<>();
574 for (User user : getUsers(5)) {
575 Contact contact = user.getContact();
576 if (contact != null && !contact.getDisplayName().isEmpty()) {
577 names.add(contact.getDisplayName().split("\\s+")[0]);
578 } else if (user.getName() != null){
579 names.add(user.getName());
580 }
581 }
582 StringBuilder builder = new StringBuilder();
583 for (int i = 0; i < names.size(); ++i) {
584 builder.append(names.get(i));
585 if (i != names.size() - 1) {
586 builder.append(", ");
587 }
588 }
589 return builder.toString();
590 } else {
591 return null;
592 }
593 }
594
595 public long[] getPgpKeyIds() {
596 List<Long> ids = new ArrayList<>();
597 for (User user : this.users) {
598 if (user.getPgpKeyId() != 0) {
599 ids.add(user.getPgpKeyId());
600 }
601 }
602 ids.add(account.getPgpId());
603 long[] primitiveLongArray = new long[ids.size()];
604 for (int i = 0; i < ids.size(); ++i) {
605 primitiveLongArray[i] = ids.get(i);
606 }
607 return primitiveLongArray;
608 }
609
610 public boolean pgpKeysInUse() {
611 synchronized (users) {
612 for (User user : users) {
613 if (user.getPgpKeyId() != 0) {
614 return true;
615 }
616 }
617 }
618 return false;
619 }
620
621 public boolean everybodyHasKeys() {
622 synchronized (users) {
623 for (User user : users) {
624 if (user.getPgpKeyId() == 0) {
625 return false;
626 }
627 }
628 }
629 return true;
630 }
631
632 public Jid createJoinJid(String nick) {
633 try {
634 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
635 } catch (final InvalidJidException e) {
636 return null;
637 }
638 }
639
640 public Jid getTrueCounterpart(Jid jid) {
641 if (jid.equals(getSelf().getFullJid())) {
642 return account.getJid().toBareJid();
643 }
644 User user = findUserByFullJid(jid);
645 return user == null ? null : user.realJid;
646 }
647
648 public String getPassword() {
649 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
650 if (this.password == null && conversation.getBookmark() != null
651 && conversation.getBookmark().getPassword() != null) {
652 return conversation.getBookmark().getPassword();
653 } else {
654 return this.password;
655 }
656 }
657
658 public void setPassword(String password) {
659 if (conversation.getBookmark() != null) {
660 conversation.getBookmark().setPassword(password);
661 } else {
662 this.password = password;
663 }
664 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
665 }
666
667 public Conversation getConversation() {
668 return this.conversation;
669 }
670
671 public List<Jid> getMembers() {
672 ArrayList<Jid> members = new ArrayList<>();
673 synchronized (users) {
674 for (User user : users) {
675 if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) {
676 members.add(user.realJid);
677 }
678 }
679 }
680 return members;
681 }
682}