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