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