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