MucOptions.java

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