1package eu.siacs.conversations.entities;
2
3import android.text.TextUtils;
4
5import androidx.annotation.NonNull;
6import androidx.annotation.Nullable;
7
8import java.util.ArrayList;
9import java.util.Collections;
10import java.util.HashSet;
11import java.util.List;
12import java.util.Locale;
13import java.util.Set;
14
15import eu.siacs.conversations.Config;
16import eu.siacs.conversations.R;
17import eu.siacs.conversations.services.AvatarService;
18import eu.siacs.conversations.services.MessageArchiveService;
19import eu.siacs.conversations.utils.JidHelper;
20import eu.siacs.conversations.utils.UIHelper;
21import eu.siacs.conversations.xmpp.Jid;
22import eu.siacs.conversations.xmpp.chatstate.ChatState;
23import eu.siacs.conversations.xmpp.forms.Data;
24import eu.siacs.conversations.xmpp.forms.Field;
25import eu.siacs.conversations.xmpp.pep.Avatar;
26
27public class MucOptions {
28
29 public static final String STATUS_CODE_SELF_PRESENCE = "110";
30 public static final String STATUS_CODE_ROOM_CREATED = "201";
31 public static final String STATUS_CODE_BANNED = "301";
32 public static final String STATUS_CODE_CHANGED_NICK = "303";
33 public static final String STATUS_CODE_KICKED = "307";
34 public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
35 public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
36 public static final String STATUS_CODE_SHUTDOWN = "332";
37 private final Set<User> users = new HashSet<>();
38 private final Conversation conversation;
39 public OnRenameListener onRenameListener = null;
40 private boolean mAutoPushConfiguration = true;
41 private final Account account;
42 private ServiceDiscoveryResult serviceDiscoveryResult;
43 private boolean isOnline = false;
44 private Error error = Error.NONE;
45 private User self;
46 private String password = null;
47
48 private boolean tookProposedNickFromBookmark = false;
49
50 public MucOptions(Conversation conversation) {
51 this.account = conversation.getAccount();
52 this.conversation = conversation;
53 this.self = new User(this, createJoinJid(getProposedNick()));
54 this.self.affiliation = Affiliation.of(conversation.getAttribute("affiliation"));
55 this.self.role = Role.of(conversation.getAttribute("role"));
56 }
57
58 public Account getAccount() {
59 return this.conversation.getAccount();
60 }
61
62 public boolean setSelf(User user) {
63 this.self = user;
64 final boolean roleChanged = this.conversation.setAttribute("role", user.role.toString());
65 final boolean affiliationChanged = this.conversation.setAttribute("affiliation", user.affiliation.toString());
66 return roleChanged || affiliationChanged;
67 }
68
69 public void changeAffiliation(Jid jid, Affiliation affiliation) {
70 User user = findUserByRealJid(jid);
71 synchronized (users) {
72 if (user != null && user.getRole() == Role.NONE) {
73 users.remove(user);
74 if (affiliation.ranks(Affiliation.MEMBER)) {
75 user.affiliation = affiliation;
76 users.add(user);
77 }
78 }
79 }
80 }
81
82 public void flagNoAutoPushConfiguration() {
83 mAutoPushConfiguration = false;
84 }
85
86 public boolean autoPushConfiguration() {
87 return mAutoPushConfiguration;
88 }
89
90 public boolean isSelf(Jid counterpart) {
91 return counterpart.equals(self.getFullJid());
92 }
93
94 public void resetChatState() {
95 synchronized (users) {
96 for (User user : users) {
97 user.chatState = Config.DEFAULT_CHAT_STATE;
98 }
99 }
100 }
101
102 public boolean isTookProposedNickFromBookmark() {
103 return tookProposedNickFromBookmark;
104 }
105
106 void notifyOfBookmarkNick(final String nick) {
107 final String normalized = normalize(account.getJid(),nick);
108 if (normalized != null && normalized.equals(getSelf().getFullJid().getResource())) {
109 this.tookProposedNickFromBookmark = true;
110 }
111 }
112
113 public boolean mamSupport() {
114 return MessageArchiveService.Version.has(getFeatures());
115 }
116
117 public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) {
118 this.serviceDiscoveryResult = serviceDiscoveryResult;
119 String name;
120 Field roomConfigName = getRoomInfoForm().getFieldByName("muc#roomconfig_roomname");
121 if (roomConfigName != null) {
122 name = roomConfigName.getValue();
123 } else {
124 List<ServiceDiscoveryResult.Identity> identities = serviceDiscoveryResult.getIdentities();
125 String identityName = identities.size() > 0 ? identities.get(0).getName() : null;
126 final Jid jid = conversation.getJid();
127 if (identityName != null && !identityName.equals(jid == null ? null : jid.getEscapedLocal())) {
128 name = identityName;
129 } else {
130 name = null;
131 }
132 }
133 boolean changed = conversation.setAttribute("muc_name", name);
134 changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, this.hasFeature("muc_membersonly"));
135 changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MODERATED, this.hasFeature("muc_moderated"));
136 changed |= conversation.setAttribute(Conversation.ATTRIBUTE_NON_ANONYMOUS, this.hasFeature("muc_nonanonymous"));
137 return changed;
138 }
139
140 private Data getRoomInfoForm() {
141 final List<Data> forms = serviceDiscoveryResult == null ? Collections.emptyList() : serviceDiscoveryResult.forms;
142 return forms.size() == 0 ? new Data() : forms.get(0);
143 }
144
145 public String getAvatar() {
146 return account.getRoster().getContact(conversation.getJid()).getAvatarFilename();
147 }
148
149 public boolean hasFeature(String feature) {
150 return this.serviceDiscoveryResult != null && this.serviceDiscoveryResult.features.contains(feature);
151 }
152
153 public boolean hasVCards() {
154 return hasFeature("vcard-temp");
155 }
156
157 public boolean canInvite() {
158 return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites();
159 }
160
161 public boolean allowInvites() {
162 final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
163 return field != null && "1".equals(field.getValue());
164 }
165
166 public boolean canChangeSubject() {
167 return self.getRole().ranks(Role.MODERATOR) || participantsCanChangeSubject();
168 }
169
170 public boolean participantsCanChangeSubject() {
171 final Field field = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject");
172 return field != null && "1".equals(field.getValue());
173 }
174
175 public boolean allowPm() {
176 final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm");
177 if (field == null) {
178 return true; //fall back if field does not exists
179 }
180 if ("anyone".equals(field.getValue())) {
181 return true;
182 } else if ("participants".equals(field.getValue())) {
183 return self.getRole().ranks(Role.PARTICIPANT);
184 } else if ("moderators".equals(field.getValue())) {
185 return self.getRole().ranks(Role.MODERATOR);
186 } else {
187 return false;
188 }
189 }
190
191 public boolean participating() {
192 return self.getRole().ranks(Role.PARTICIPANT) || !moderated();
193 }
194
195 public boolean membersOnly() {
196 return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, false);
197 }
198
199 public List<String> getFeatures() {
200 return this.serviceDiscoveryResult != null ? this.serviceDiscoveryResult.features : Collections.emptyList();
201 }
202
203 public boolean nonanonymous() {
204 return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_NON_ANONYMOUS, false);
205 }
206
207 public boolean isPrivateAndNonAnonymous() {
208 return membersOnly() && nonanonymous();
209 }
210
211 public boolean moderated() {
212 return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MODERATED, false);
213 }
214
215 public boolean stableId() {
216 return getFeatures().contains("http://jabber.org/protocol/muc#stable_id");
217 }
218
219 public User deleteUser(Jid jid) {
220 User user = findUserByFullJid(jid);
221 if (user != null) {
222 synchronized (users) {
223 users.remove(user);
224 boolean realJidInMuc = false;
225 for (User u : users) {
226 if (user.realJid != null && user.realJid.equals(u.realJid)) {
227 realJidInMuc = true;
228 break;
229 }
230 }
231 boolean self = user.realJid != null && user.realJid.equals(account.getJid().asBareJid());
232 if (membersOnly()
233 && nonanonymous()
234 && user.affiliation.ranks(Affiliation.MEMBER)
235 && user.realJid != null
236 && !realJidInMuc
237 && !self) {
238 user.role = Role.NONE;
239 user.avatar = null;
240 user.fullJid = null;
241 users.add(user);
242 }
243 }
244 }
245 return user;
246 }
247
248 //returns true if real jid was new;
249 public boolean updateUser(User user) {
250 User old;
251 boolean realJidFound = false;
252 if (user.fullJid == null && user.realJid != null) {
253 old = findUserByRealJid(user.realJid);
254 realJidFound = old != null;
255 if (old != null) {
256 if (old.fullJid != null) {
257 return false; //don't add. user already exists
258 } else {
259 synchronized (users) {
260 users.remove(old);
261 }
262 }
263 }
264 } else if (user.realJid != null) {
265 old = findUserByRealJid(user.realJid);
266 realJidFound = old != null;
267 synchronized (users) {
268 if (old != null && (old.fullJid == null || old.role == Role.NONE)) {
269 users.remove(old);
270 }
271 }
272 }
273 old = findUserByFullJid(user.getFullJid());
274
275 synchronized (this.users) {
276 if (old != null) {
277 users.remove(old);
278 }
279 boolean fullJidIsSelf = isOnline && user.getFullJid() != null && user.getFullJid().equals(self.getFullJid());
280 if ((!membersOnly() || user.getAffiliation().ranks(Affiliation.MEMBER))
281 && user.getAffiliation().outranks(Affiliation.OUTCAST)
282 && !fullJidIsSelf) {
283 this.users.add(user);
284 return !realJidFound && user.realJid != null;
285 }
286 }
287 return false;
288 }
289
290 public User findUserByFullJid(Jid jid) {
291 if (jid == null) {
292 return null;
293 }
294 synchronized (users) {
295 for (User user : users) {
296 if (jid.equals(user.getFullJid())) {
297 return user;
298 }
299 }
300 }
301 return null;
302 }
303
304 public User findUserByRealJid(Jid jid) {
305 if (jid == null) {
306 return null;
307 }
308 synchronized (users) {
309 for (User user : users) {
310 if (jid.equals(user.realJid)) {
311 return user;
312 }
313 }
314 }
315 return null;
316 }
317
318 public User findOrCreateUserByRealJid(Jid jid, Jid fullJid) {
319 User user = findUserByRealJid(jid);
320 if (user == null) {
321 user = new User(this, fullJid);
322 user.setRealJid(jid);
323 }
324 return user;
325 }
326
327 public User findUser(ReadByMarker readByMarker) {
328 if (readByMarker.getRealJid() != null) {
329 return findOrCreateUserByRealJid(readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid());
330 } else if (readByMarker.getFullJid() != null) {
331 return findUserByFullJid(readByMarker.getFullJid());
332 } else {
333 return null;
334 }
335 }
336
337 public boolean isContactInRoom(Contact contact) {
338 return contact != null && findUserByRealJid(contact.getJid().asBareJid()) != null;
339 }
340
341 public boolean isUserInRoom(Jid jid) {
342 return findUserByFullJid(jid) != null;
343 }
344
345 public boolean setOnline() {
346 boolean before = this.isOnline;
347 this.isOnline = true;
348 return !before;
349 }
350
351 public ArrayList<User> getUsers() {
352 return getUsers(true);
353 }
354
355 public ArrayList<User> getUsers(boolean includeOffline) {
356 synchronized (users) {
357 ArrayList<User> users = new ArrayList<>();
358 for (User user : this.users) {
359 if (!user.isDomain() && (includeOffline || user.getRole().ranks(Role.PARTICIPANT))) {
360 users.add(user);
361 }
362 }
363 return users;
364 }
365 }
366
367 public ArrayList<User> getUsersWithChatState(ChatState state, int max) {
368 synchronized (users) {
369 ArrayList<User> list = new ArrayList<>();
370 for (User user : users) {
371 if (user.chatState == state) {
372 list.add(user);
373 if (list.size() >= max) {
374 break;
375 }
376 }
377 }
378 return list;
379 }
380 }
381
382 public List<User> getUsers(int max) {
383 ArrayList<User> subset = new ArrayList<>();
384 HashSet<Jid> jids = new HashSet<>();
385 jids.add(account.getJid().asBareJid());
386 synchronized (users) {
387 for (User user : users) {
388 if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) {
389 subset.add(user);
390 }
391 if (subset.size() >= max) {
392 break;
393 }
394 }
395 }
396 return subset;
397 }
398
399 public static List<User> sub(List<User> users, int max) {
400 ArrayList<User> subset = new ArrayList<>();
401 HashSet<Jid> jids = new HashSet<>();
402 for (User user : users) {
403 jids.add(user.getAccount().getJid().asBareJid());
404 if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) {
405 subset.add(user);
406 }
407 if (subset.size() >= max) {
408 break;
409 }
410 }
411 return subset;
412 }
413
414 public int getUserCount() {
415 synchronized (users) {
416 return users.size();
417 }
418 }
419
420 public String getProposedNick() {
421 final Bookmark bookmark = this.conversation.getBookmark();
422 final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick());
423 if (bookmarkedNick != null) {
424 this.tookProposedNickFromBookmark = true;
425 return bookmarkedNick;
426 } else if (!conversation.getJid().isBareJid()) {
427 return conversation.getJid().getResource();
428 } else {
429 return defaultNick(account);
430 }
431 }
432
433 public static String defaultNick(final Account account) {
434 final String displayName = normalize(account.getJid(), account.getDisplayName());
435 if (displayName == null) {
436 return JidHelper.localPartOrFallback(account.getJid());
437 } else {
438 return displayName;
439 }
440 }
441
442 private static String normalize(Jid account, String nick) {
443 if (account == null || TextUtils.isEmpty(nick)) {
444 return null;
445 }
446 try {
447 return account.withResource(nick).getResource();
448 } catch (IllegalArgumentException e) {
449 return null;
450 }
451
452 }
453
454 public String getActualNick() {
455 if (this.self.getName() != null) {
456 return this.self.getName();
457 } else {
458 return this.getProposedNick();
459 }
460 }
461
462 public boolean online() {
463 return this.isOnline;
464 }
465
466 public Error getError() {
467 return this.error;
468 }
469
470 public void setError(Error error) {
471 this.isOnline = isOnline && error == Error.NONE;
472 this.error = error;
473 }
474
475 public void setOnRenameListener(OnRenameListener listener) {
476 this.onRenameListener = listener;
477 }
478
479 public void setOffline() {
480 synchronized (users) {
481 this.users.clear();
482 }
483 this.error = Error.NO_RESPONSE;
484 this.isOnline = false;
485 }
486
487 public User getSelf() {
488 return self;
489 }
490
491 public boolean setSubject(String subject) {
492 return this.conversation.setAttribute("subject", subject);
493 }
494
495 public String getSubject() {
496 return this.conversation.getAttribute("subject");
497 }
498
499 public String getName() {
500 return this.conversation.getAttribute("muc_name");
501 }
502
503 private List<User> getFallbackUsersFromCryptoTargets() {
504 List<User> users = new ArrayList<>();
505 for (Jid jid : conversation.getAcceptedCryptoTargets()) {
506 User user = new User(this, null);
507 user.setRealJid(jid);
508 users.add(user);
509 }
510 return users;
511 }
512
513 public List<User> getUsersRelevantForNameAndAvatar() {
514 final List<User> users;
515 if (isOnline) {
516 users = getUsers(5);
517 } else {
518 users = getFallbackUsersFromCryptoTargets();
519 }
520 return users;
521 }
522
523 String createNameFromParticipants() {
524 List<User> users = getUsersRelevantForNameAndAvatar();
525 if (users.size() >= 2) {
526 StringBuilder builder = new StringBuilder();
527 for (User user : users) {
528 if (builder.length() != 0) {
529 builder.append(", ");
530 }
531 String name = UIHelper.getDisplayName(user);
532 if (name != null) {
533 builder.append(name.split("\\s+")[0]);
534 }
535 }
536 return builder.toString();
537 } else {
538 return null;
539 }
540 }
541
542 public long[] getPgpKeyIds() {
543 List<Long> ids = new ArrayList<>();
544 for (User user : this.users) {
545 if (user.getPgpKeyId() != 0) {
546 ids.add(user.getPgpKeyId());
547 }
548 }
549 ids.add(account.getPgpId());
550 long[] primitiveLongArray = new long[ids.size()];
551 for (int i = 0; i < ids.size(); ++i) {
552 primitiveLongArray[i] = ids.get(i);
553 }
554 return primitiveLongArray;
555 }
556
557 public boolean pgpKeysInUse() {
558 synchronized (users) {
559 for (User user : users) {
560 if (user.getPgpKeyId() != 0) {
561 return true;
562 }
563 }
564 }
565 return false;
566 }
567
568 public boolean everybodyHasKeys() {
569 synchronized (users) {
570 for (User user : users) {
571 if (user.getPgpKeyId() == 0) {
572 return false;
573 }
574 }
575 }
576 return true;
577 }
578
579 public Jid createJoinJid(String nick) {
580 try {
581 return conversation.getJid().withResource(nick);
582 } catch (final IllegalArgumentException e) {
583 return null;
584 }
585 }
586
587 public Jid getTrueCounterpart(Jid jid) {
588 if (jid.equals(getSelf().getFullJid())) {
589 return account.getJid().asBareJid();
590 }
591 User user = findUserByFullJid(jid);
592 return user == null ? null : user.realJid;
593 }
594
595 public String getPassword() {
596 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
597 if (this.password == null && conversation.getBookmark() != null
598 && conversation.getBookmark().getPassword() != null) {
599 return conversation.getBookmark().getPassword();
600 } else {
601 return this.password;
602 }
603 }
604
605 public void setPassword(String password) {
606 if (conversation.getBookmark() != null) {
607 conversation.getBookmark().setPassword(password);
608 } else {
609 this.password = password;
610 }
611 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
612 }
613
614 public Conversation getConversation() {
615 return this.conversation;
616 }
617
618 public List<Jid> getMembers(final boolean includeDomains) {
619 ArrayList<Jid> members = new ArrayList<>();
620 synchronized (users) {
621 for (User user : users) {
622 if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null && !user.realJid.asBareJid().equals(conversation.account.getJid().asBareJid()) && (!user.isDomain() || includeDomains)) {
623 members.add(user.realJid);
624 }
625 }
626 }
627 return members;
628 }
629
630 public enum Affiliation {
631 OWNER(4, R.string.owner),
632 ADMIN(3, R.string.admin),
633 MEMBER(2, R.string.member),
634 OUTCAST(0, R.string.outcast),
635 NONE(1, R.string.no_affiliation);
636
637 private final int resId;
638 private final int rank;
639
640 Affiliation(int rank, int resId) {
641 this.resId = resId;
642 this.rank = rank;
643 }
644
645 public static Affiliation of(@Nullable String value) {
646 if (value == null) {
647 return NONE;
648 }
649 try {
650 return Affiliation.valueOf(value.toUpperCase(Locale.US));
651 } catch (IllegalArgumentException e) {
652 return NONE;
653 }
654 }
655
656 public int getResId() {
657 return resId;
658 }
659
660 @Override
661 public String toString() {
662 return name().toLowerCase(Locale.US);
663 }
664
665 public boolean outranks(Affiliation affiliation) {
666 return rank > affiliation.rank;
667 }
668
669 public boolean ranks(Affiliation affiliation) {
670 return rank >= affiliation.rank;
671 }
672 }
673
674 public enum Role {
675 MODERATOR(R.string.moderator, 3),
676 VISITOR(R.string.visitor, 1),
677 PARTICIPANT(R.string.participant, 2),
678 NONE(R.string.no_role, 0);
679
680 private final int resId;
681 private final int rank;
682
683 Role(int resId, int rank) {
684 this.resId = resId;
685 this.rank = rank;
686 }
687
688 public static Role of(@Nullable String value) {
689 if (value == null) {
690 return NONE;
691 }
692 try {
693 return Role.valueOf(value.toUpperCase(Locale.US));
694 } catch (IllegalArgumentException e) {
695 return NONE;
696 }
697 }
698
699 public int getResId() {
700 return resId;
701 }
702
703 @Override
704 public String toString() {
705 return name().toLowerCase(Locale.US);
706 }
707
708 public boolean ranks(Role role) {
709 return rank >= role.rank;
710 }
711 }
712
713 public enum Error {
714 NO_RESPONSE,
715 SERVER_NOT_FOUND,
716 REMOTE_SERVER_TIMEOUT,
717 NONE,
718 NICK_IN_USE,
719 PASSWORD_REQUIRED,
720 BANNED,
721 MEMBERS_ONLY,
722 RESOURCE_CONSTRAINT,
723 KICKED,
724 SHUTDOWN,
725 DESTROYED,
726 INVALID_NICK,
727 UNKNOWN,
728 NON_ANONYMOUS
729 }
730
731 private interface OnEventListener {
732 void onSuccess();
733
734 void onFailure();
735 }
736
737 public interface OnRenameListener extends OnEventListener {
738
739 }
740
741 public static class User implements Comparable<User>, AvatarService.Avatarable {
742 private Role role = Role.NONE;
743 private Affiliation affiliation = Affiliation.NONE;
744 private Jid realJid;
745 private Jid fullJid;
746 private long pgpKeyId = 0;
747 private Avatar avatar;
748 private final MucOptions options;
749 private ChatState chatState = Config.DEFAULT_CHAT_STATE;
750
751 public User(MucOptions options, Jid fullJid) {
752 this.options = options;
753 this.fullJid = fullJid;
754 }
755
756 public String getName() {
757 return fullJid == null ? null : fullJid.getResource();
758 }
759
760 public Role getRole() {
761 return this.role;
762 }
763
764 public void setRole(String role) {
765 this.role = Role.of(role);
766 }
767
768 public Affiliation getAffiliation() {
769 return this.affiliation;
770 }
771
772 public void setAffiliation(String affiliation) {
773 this.affiliation = Affiliation.of(affiliation);
774 }
775
776 public long getPgpKeyId() {
777 if (this.pgpKeyId != 0) {
778 return this.pgpKeyId;
779 } else if (realJid != null) {
780 return getAccount().getRoster().getContact(realJid).getPgpKeyId();
781 } else {
782 return 0;
783 }
784 }
785
786 public void setPgpKeyId(long id) {
787 this.pgpKeyId = id;
788 }
789
790 public Contact getContact() {
791 if (fullJid != null) {
792 return getAccount().getRoster().getContactFromContactList(realJid);
793 } else if (realJid != null) {
794 return getAccount().getRoster().getContact(realJid);
795 } else {
796 return null;
797 }
798 }
799
800 public boolean setAvatar(Avatar avatar) {
801 if (this.avatar != null && this.avatar.equals(avatar)) {
802 return false;
803 } else {
804 this.avatar = avatar;
805 return true;
806 }
807 }
808
809 public String getAvatar() {
810 if (avatar != null) {
811 return avatar.getFilename();
812 }
813 Avatar avatar = realJid != null ? getAccount().getRoster().getContact(realJid).getAvatar() : null;
814 return avatar == null ? null : avatar.getFilename();
815 }
816
817 public Account getAccount() {
818 return options.getAccount();
819 }
820
821 public Conversation getConversation() {
822 return options.getConversation();
823 }
824
825 public Jid getFullJid() {
826 return fullJid;
827 }
828
829 @Override
830 public boolean equals(Object o) {
831 if (this == o) return true;
832 if (o == null || getClass() != o.getClass()) return false;
833
834 User user = (User) o;
835
836 if (role != user.role) return false;
837 if (affiliation != user.affiliation) return false;
838 if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null)
839 return false;
840 return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null;
841
842 }
843
844 public boolean isDomain() {
845 return realJid != null && realJid.getLocal() == null && role == Role.NONE;
846 }
847
848 @Override
849 public int hashCode() {
850 int result = role != null ? role.hashCode() : 0;
851 result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0);
852 result = 31 * result + (realJid != null ? realJid.hashCode() : 0);
853 result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0);
854 return result;
855 }
856
857 @Override
858 public String toString() {
859 return "[fulljid:" + fullJid + ",realjid:" + realJid + ",affiliation" + affiliation.toString() + "]";
860 }
861
862 public boolean realJidMatchesAccount() {
863 return realJid != null && realJid.equals(options.account.getJid().asBareJid());
864 }
865
866 @Override
867 public int compareTo(@NonNull User another) {
868 if (another.getAffiliation().outranks(getAffiliation())) {
869 return 1;
870 } else if (getAffiliation().outranks(another.getAffiliation())) {
871 return -1;
872 } else {
873 return getComparableName().compareToIgnoreCase(another.getComparableName());
874 }
875 }
876
877 public String getComparableName() {
878 Contact contact = getContact();
879 if (contact != null) {
880 return contact.getDisplayName();
881 } else {
882 String name = getName();
883 return name == null ? "" : name;
884 }
885 }
886
887 public Jid getRealJid() {
888 return realJid;
889 }
890
891 public void setRealJid(Jid jid) {
892 this.realJid = jid != null ? jid.asBareJid() : null;
893 }
894
895 public boolean setChatState(ChatState chatState) {
896 if (this.chatState == chatState) {
897 return false;
898 }
899 this.chatState = chatState;
900 return true;
901 }
902
903 @Override
904 public int getAvatarBackgroundColor() {
905 final String seed = realJid != null ? realJid.asBareJid().toString() : null;
906 return UIHelper.getColorForName(seed == null ? getName() : seed);
907 }
908
909 @Override
910 public String getAvatarName() {
911 return getConversation().getName().toString();
912 }
913 }
914}