anotherim/src/main/java/eu/siacs/conversations/entities/MucOptions.java

906 lines
28 KiB
Java
Raw Normal View History

2014-10-22 16:38:44 +00:00
package eu.siacs.conversations.entities;
import android.support.annotation.NonNull;
2018-07-08 18:52:55 +00:00
import android.support.annotation.Nullable;
2018-11-09 16:47:36 +00:00
import android.text.TextUtils;
2015-07-20 12:26:29 +00:00
2014-10-22 16:38:44 +00:00
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
2014-10-22 16:38:44 +00:00
import java.util.List;
2018-07-08 18:52:55 +00:00
import java.util.Locale;
import java.util.Set;
2014-10-22 16:38:44 +00:00
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.services.AvatarService;
2018-07-07 09:20:39 +00:00
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.utils.JidHelper;
import eu.siacs.conversations.utils.UIHelper;
implement client support for muc push Staying connected to a MUC room hosted on a remote server can be challenging. If a server reboots it will usually send a shut down notification to all participants. However even if a client knows that a server was shut down it doesn’t know when it comes up again. In some corner cases that shut down notification might not even be delivered successfully leaving the client in a state where it thinks it is connected but it really isn’t. The possible work around implemented in this commit is to register the clients full JID (user@domain.tld/Conversations.r4nd) as an App Server according to XEP-0357 with the room. (Conversations checks for the push:0 namespace on the room.) After cycling through a reboot the first message send to a room will trigger pubsub notifications to each registered full JID. This event will be used to trigger a XEP-0410 ping and if necessary a subsequent rejoin of the MUC. If the resource has become unavailable during down time of the MUC server the user’s server will respond with an IQ error which in turn leads to the MUC server disabling that push target. Leaving a MUC will send a `disable` command. If sending that disable command failed for some reason (network outage) and the client receives a pubsub notification for a room it is no longer joined in it will respond with an item-not-found IQ error which also disables subsequent pushes from the server. Note: We 0410-ping before a join to avoid unnecessary full joins which can be quite costly. Further client side optimazations will also surpress pings when a ping is already in flight to further save traffic.
2019-06-24 16:16:03 +00:00
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field;
2015-12-03 17:18:34 +00:00
import eu.siacs.conversations.xmpp.pep.Avatar;
2018-03-05 17:30:40 +00:00
import rocks.xmpp.addr.Jid;
2014-10-22 16:38:44 +00:00
public class MucOptions {
public static final String STATUS_CODE_SELF_PRESENCE = "110";
public static final String STATUS_CODE_ROOM_CREATED = "201";
public static final String STATUS_CODE_BANNED = "301";
public static final String STATUS_CODE_CHANGED_NICK = "303";
public static final String STATUS_CODE_KICKED = "307";
public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
public static final String STATUS_CODE_SHUTDOWN = "332";
private final Set<User> users = new HashSet<>();
private final Conversation conversation;
public OnRenameListener onRenameListener = null;
private boolean mAutoPushConfiguration = true;
private Account account;
private ServiceDiscoveryResult serviceDiscoveryResult;
private boolean isOnline = false;
private Error error = Error.NONE;
private User self;
private String password = null;
private boolean tookProposedNickFromBookmark = false;
public MucOptions(Conversation conversation) {
this.account = conversation.getAccount();
this.conversation = conversation;
this.self = new User(this, createJoinJid(getProposedNick()));
this.self.affiliation = Affiliation.of(conversation.getAttribute("affiliation"));
this.self.role = Role.of(conversation.getAttribute("role"));
}
public Account getAccount() {
return this.conversation.getAccount();
}
public boolean setSelf(User user) {
this.self = user;
final boolean roleChanged = this.conversation.setAttribute("role", user.role.toString());
final boolean affiliationChanged = this.conversation.setAttribute("affiliation", user.affiliation.toString());
return roleChanged || affiliationChanged;
}
public void changeAffiliation(Jid jid, Affiliation affiliation) {
User user = findUserByRealJid(jid);
synchronized (users) {
if (user != null && user.getRole() == Role.NONE) {
users.remove(user);
if (affiliation.ranks(Affiliation.MEMBER)) {
user.affiliation = affiliation;
users.add(user);
}
}
}
}
public void flagNoAutoPushConfiguration() {
mAutoPushConfiguration = false;
}
public boolean autoPushConfiguration() {
return mAutoPushConfiguration;
}
public boolean isSelf(Jid counterpart) {
return counterpart.equals(self.getFullJid());
}
public void resetChatState() {
synchronized (users) {
for (User user : users) {
user.chatState = Config.DEFAULT_CHATSTATE;
}
}
}
public boolean isTookProposedNickFromBookmark() {
return tookProposedNickFromBookmark;
}
void notifyOfBookmarkNick(final String nick) {
final String normalized = normalize(account.getJid(),nick);
if (normalized != null && normalized.equals(getSelf().getFullJid().getResource())) {
this.tookProposedNickFromBookmark = true;
}
}
public boolean mamSupport() {
return MessageArchiveService.Version.has(getFeatures());
}
implement client support for muc push Staying connected to a MUC room hosted on a remote server can be challenging. If a server reboots it will usually send a shut down notification to all participants. However even if a client knows that a server was shut down it doesn’t know when it comes up again. In some corner cases that shut down notification might not even be delivered successfully leaving the client in a state where it thinks it is connected but it really isn’t. The possible work around implemented in this commit is to register the clients full JID (user@domain.tld/Conversations.r4nd) as an App Server according to XEP-0357 with the room. (Conversations checks for the push:0 namespace on the room.) After cycling through a reboot the first message send to a room will trigger pubsub notifications to each registered full JID. This event will be used to trigger a XEP-0410 ping and if necessary a subsequent rejoin of the MUC. If the resource has become unavailable during down time of the MUC server the user’s server will respond with an IQ error which in turn leads to the MUC server disabling that push target. Leaving a MUC will send a `disable` command. If sending that disable command failed for some reason (network outage) and the client receives a pubsub notification for a room it is no longer joined in it will respond with an item-not-found IQ error which also disables subsequent pushes from the server. Note: We 0410-ping before a join to avoid unnecessary full joins which can be quite costly. Further client side optimazations will also surpress pings when a ping is already in flight to further save traffic.
2019-06-24 16:16:03 +00:00
public boolean push() {
return getFeatures().contains(Namespace.PUSH);
}
public boolean updateConfiguration(ServiceDiscoveryResult serviceDiscoveryResult) {
this.serviceDiscoveryResult = serviceDiscoveryResult;
String name;
Field roomConfigName = getRoomInfoForm().getFieldByName("muc#roomconfig_roomname");
if (roomConfigName != null) {
name = roomConfigName.getValue();
} else {
List<ServiceDiscoveryResult.Identity> identities = serviceDiscoveryResult.getIdentities();
String identityName = identities.size() > 0 ? identities.get(0).getName() : null;
final Jid jid = conversation.getJid();
if (identityName != null && !identityName.equals(jid == null ? null : jid.getEscapedLocal())) {
name = identityName;
} else {
name = null;
}
}
boolean changed = conversation.setAttribute("muc_name", name);
changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, this.hasFeature("muc_membersonly"));
changed |= conversation.setAttribute(Conversation.ATTRIBUTE_MODERATED, this.hasFeature("muc_moderated"));
changed |= conversation.setAttribute(Conversation.ATTRIBUTE_NON_ANONYMOUS, this.hasFeature("muc_nonanonymous"));
return changed;
}
private Data getRoomInfoForm() {
final List<Data> forms = serviceDiscoveryResult == null ? Collections.emptyList() : serviceDiscoveryResult.forms;
return forms.size() == 0 ? new Data() : forms.get(0);
}
public String getAvatar() {
return account.getRoster().getContact(conversation.getJid()).getAvatarFilename();
}
public boolean hasFeature(String feature) {
return this.serviceDiscoveryResult != null && this.serviceDiscoveryResult.features.contains(feature);
}
public boolean hasVCards() {
return hasFeature("vcard-temp");
}
public boolean canInvite() {
return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || allowInvites();
}
public boolean allowInvites() {
final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
return field != null && "1".equals(field.getValue());
}
public boolean canChangeSubject() {
return self.getRole().ranks(Role.MODERATOR) || participantsCanChangeSubject();
}
public boolean participantsCanChangeSubject() {
final Field field = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject");
return field != null && "1".equals(field.getValue());
}
public boolean allowPm() {
final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm");
if (field == null) {
return true; //fall back if field does not exists
}
if ("anyone".equals(field.getValue())) {
return true;
} else if ("participants".equals(field.getValue())) {
return self.getRole().ranks(Role.PARTICIPANT);
} else if ("moderators".equals(field.getValue())) {
return self.getRole().ranks(Role.MODERATOR);
} else {
return false;
}
}
public boolean participating() {
return self.getRole().ranks(Role.PARTICIPANT) || !moderated();
}
public boolean membersOnly() {
return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MEMBERS_ONLY, false);
}
public List<String> getFeatures() {
return this.serviceDiscoveryResult != null ? this.serviceDiscoveryResult.features : Collections.emptyList();
}
public boolean nonanonymous() {
return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_NON_ANONYMOUS, false);
}
public boolean isPrivateAndNonAnonymous() {
return membersOnly() && nonanonymous();
}
public boolean moderated() {
return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MODERATED, false);
}
public User deleteUser(Jid jid) {
User user = findUserByFullJid(jid);
if (user != null) {
synchronized (users) {
users.remove(user);
boolean realJidInMuc = false;
for (User u : users) {
if (user.realJid != null && user.realJid.equals(u.realJid)) {
realJidInMuc = true;
break;
}
}
boolean self = user.realJid != null && user.realJid.equals(account.getJid().asBareJid());
if (membersOnly()
&& nonanonymous()
&& user.affiliation.ranks(Affiliation.MEMBER)
&& user.realJid != null
&& !realJidInMuc
&& !self) {
user.role = Role.NONE;
user.avatar = null;
user.fullJid = null;
users.add(user);
}
}
}
return user;
}
//returns true if real jid was new;
public boolean updateUser(User user) {
User old;
boolean realJidFound = false;
if (user.fullJid == null && user.realJid != null) {
old = findUserByRealJid(user.realJid);
realJidFound = old != null;
if (old != null) {
if (old.fullJid != null) {
return false; //don't add. user already exists
} else {
synchronized (users) {
users.remove(old);
}
}
}
} else if (user.realJid != null) {
old = findUserByRealJid(user.realJid);
realJidFound = old != null;
synchronized (users) {
if (old != null && (old.fullJid == null || old.role == Role.NONE)) {
users.remove(old);
}
}
}
old = findUserByFullJid(user.getFullJid());
synchronized (this.users) {
if (old != null) {
users.remove(old);
}
boolean fullJidIsSelf = isOnline && user.getFullJid() != null && user.getFullJid().equals(self.getFullJid());
if ((!membersOnly() || user.getAffiliation().ranks(Affiliation.MEMBER))
&& user.getAffiliation().outranks(Affiliation.OUTCAST)
&& !fullJidIsSelf) {
this.users.add(user);
return !realJidFound && user.realJid != null;
}
}
return false;
}
public User findUserByFullJid(Jid jid) {
if (jid == null) {
return null;
}
synchronized (users) {
for (User user : users) {
if (jid.equals(user.getFullJid())) {
return user;
}
}
}
return null;
}
public User findUserByRealJid(Jid jid) {
if (jid == null) {
return null;
}
synchronized (users) {
for (User user : users) {
if (jid.equals(user.realJid)) {
return user;
}
}
}
return null;
}
public User findOrCreateUserByRealJid(Jid jid, Jid fullJid) {
User user = findUserByRealJid(jid);
if (user == null) {
user = new User(this, fullJid);
user.setRealJid(jid);
}
return user;
}
public User findUser(ReadByMarker readByMarker) {
if (readByMarker.getRealJid() != null) {
return findOrCreateUserByRealJid(readByMarker.getRealJid().asBareJid(), readByMarker.getFullJid());
} else if (readByMarker.getFullJid() != null) {
return findUserByFullJid(readByMarker.getFullJid());
} else {
return null;
}
}
public boolean isContactInRoom(Contact contact) {
return findUserByRealJid(contact.getJid().asBareJid()) != null;
}
public boolean isUserInRoom(Jid jid) {
return findUserByFullJid(jid) != null;
}
public boolean setOnline() {
boolean before = this.isOnline;
this.isOnline = true;
return !before;
}
public ArrayList<User> getUsers() {
return getUsers(true);
}
public ArrayList<User> getUsers(boolean includeOffline) {
synchronized (users) {
ArrayList<User> users = new ArrayList<>();
for (User user : this.users) {
if (!user.isDomain() && (includeOffline || user.getRole().ranks(Role.PARTICIPANT))) {
users.add(user);
}
}
return users;
}
}
public ArrayList<User> getUsersWithChatState(ChatState state, int max) {
synchronized (users) {
ArrayList<User> list = new ArrayList<>();
for (User user : users) {
if (user.chatState == state) {
list.add(user);
if (list.size() >= max) {
break;
}
}
}
return list;
}
}
public List<User> getUsers(int max) {
ArrayList<User> subset = new ArrayList<>();
HashSet<Jid> jids = new HashSet<>();
jids.add(account.getJid().asBareJid());
synchronized (users) {
for (User user : users) {
if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) {
subset.add(user);
}
if (subset.size() >= max) {
break;
}
}
}
return subset;
}
public static List<User> sub(List<User> users, int max) {
ArrayList<User> subset = new ArrayList<>();
HashSet<Jid> jids = new HashSet<>();
for (User user : users) {
jids.add(user.getAccount().getJid().asBareJid());
if (user.getRealJid() == null || (user.getRealJid().getLocal() != null && jids.add(user.getRealJid()))) {
subset.add(user);
}
if (subset.size() >= max) {
break;
}
}
return subset;
}
public int getUserCount() {
synchronized (users) {
return users.size();
}
}
private String getProposedNick() {
final Bookmark bookmark = this.conversation.getBookmark();
final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick());
if (bookmarkedNick != null) {
this.tookProposedNickFromBookmark = true;
return bookmarkedNick;
} else if (!conversation.getJid().isBareJid()) {
return conversation.getJid().getResource();
} else {
final String displayName = normalize(account.getJid(), account.getDisplayName());
if (displayName == null) {
2018-11-09 16:47:36 +00:00
return JidHelper.localPartOrFallback(account.getJid());
} else {
return displayName;
}
}
}
private static String normalize(Jid account, String nick) {
if (account == null || TextUtils.isEmpty(nick)) {
return null;
}
try {
return account.withResource(nick).getResource();
} catch (IllegalArgumentException e) {
return null;
}
}
public String getActualNick() {
if (this.self.getName() != null) {
return this.self.getName();
} else {
return this.getProposedNick();
}
}
public boolean online() {
return this.isOnline;
}
public Error getError() {
return this.error;
}
public void setError(Error error) {
this.isOnline = isOnline && error == Error.NONE;
this.error = error;
}
public void setOnRenameListener(OnRenameListener listener) {
this.onRenameListener = listener;
}
public void setOffline() {
synchronized (users) {
this.users.clear();
}
this.error = Error.NO_RESPONSE;
this.isOnline = false;
}
public User getSelf() {
return self;
}
public boolean setSubject(String subject) {
return this.conversation.setAttribute("subject", subject);
}
public String getSubject() {
return this.conversation.getAttribute("subject");
}
public String getName() {
return this.conversation.getAttribute("muc_name");
}
private List<User> getFallbackUsersFromCryptoTargets() {
List<User> users = new ArrayList<>();
for (Jid jid : conversation.getAcceptedCryptoTargets()) {
User user = new User(this, null);
user.setRealJid(jid);
users.add(user);
}
return users;
}
public List<User> getUsersRelevantForNameAndAvatar() {
final List<User> users;
if (isOnline) {
users = getUsers(5);
} else {
users = getFallbackUsersFromCryptoTargets();
}
return users;
}
String createNameFromParticipants() {
List<User> users = getUsersRelevantForNameAndAvatar();
if (users.size() >= 2) {
StringBuilder builder = new StringBuilder();
for (User user : users) {
if (builder.length() != 0) {
builder.append(", ");
}
String name = UIHelper.getDisplayName(user);
if (name != null) {
builder.append(name.split("\\s+")[0]);
}
}
return builder.toString();
} else {
return null;
}
}
public long[] getPgpKeyIds() {
List<Long> ids = new ArrayList<>();
for (User user : this.users) {
if (user.getPgpKeyId() != 0) {
ids.add(user.getPgpKeyId());
}
}
ids.add(account.getPgpId());
long[] primitiveLongArray = new long[ids.size()];
for (int i = 0; i < ids.size(); ++i) {
primitiveLongArray[i] = ids.get(i);
}
return primitiveLongArray;
}
public boolean pgpKeysInUse() {
synchronized (users) {
for (User user : users) {
if (user.getPgpKeyId() != 0) {
return true;
}
}
}
return false;
}
public boolean everybodyHasKeys() {
synchronized (users) {
for (User user : users) {
if (user.getPgpKeyId() == 0) {
return false;
}
}
}
return true;
}
public Jid createJoinJid(String nick) {
try {
return conversation.getJid().withResource(nick);
} catch (final IllegalArgumentException e) {
return null;
}
}
public Jid getTrueCounterpart(Jid jid) {
if (jid.equals(getSelf().getFullJid())) {
return account.getJid().asBareJid();
}
User user = findUserByFullJid(jid);
return user == null ? null : user.realJid;
}
public String getPassword() {
this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
if (this.password == null && conversation.getBookmark() != null
&& conversation.getBookmark().getPassword() != null) {
return conversation.getBookmark().getPassword();
} else {
return this.password;
}
}
public void setPassword(String password) {
if (conversation.getBookmark() != null) {
conversation.getBookmark().setPassword(password);
} else {
this.password = password;
}
conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
}
public Conversation getConversation() {
return this.conversation;
}
public List<Jid> getMembers(final boolean includeDomains) {
ArrayList<Jid> members = new ArrayList<>();
synchronized (users) {
for (User user : users) {
if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null && !user.realJid.asBareJid().equals(conversation.account.getJid().asBareJid()) && (!user.isDomain() || includeDomains)) {
members.add(user.realJid);
}
}
}
return members;
}
public enum Affiliation {
OWNER(4, R.string.owner),
ADMIN(3, R.string.admin),
MEMBER(2, R.string.member),
OUTCAST(0, R.string.outcast),
NONE(1, R.string.no_affiliation);
private int resId;
private int rank;
Affiliation(int rank, int resId) {
this.resId = resId;
this.rank = rank;
}
public static Affiliation of(@Nullable String value) {
if (value == null) {
return NONE;
}
try {
return Affiliation.valueOf(value.toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
return NONE;
}
}
public int getResId() {
return resId;
}
@Override
public String toString() {
return name().toLowerCase(Locale.US);
}
public boolean outranks(Affiliation affiliation) {
return rank > affiliation.rank;
}
public boolean ranks(Affiliation affiliation) {
return rank >= affiliation.rank;
}
}
public enum Role {
MODERATOR(R.string.moderator, 3),
VISITOR(R.string.visitor, 1),
PARTICIPANT(R.string.participant, 2),
NONE(R.string.no_role, 0);
private int resId;
private int rank;
Role(int resId, int rank) {
this.resId = resId;
this.rank = rank;
}
public static Role of(@Nullable String value) {
if (value == null) {
return NONE;
}
try {
return Role.valueOf(value.toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
return NONE;
}
}
public int getResId() {
return resId;
}
@Override
public String toString() {
return name().toLowerCase(Locale.US);
}
public boolean ranks(Role role) {
return rank >= role.rank;
}
}
public enum Error {
NO_RESPONSE,
SERVER_NOT_FOUND,
REMOTE_SERVER_TIMEOUT,
NONE,
NICK_IN_USE,
PASSWORD_REQUIRED,
BANNED,
MEMBERS_ONLY,
RESOURCE_CONSTRAINT,
KICKED,
SHUTDOWN,
DESTROYED,
INVALID_NICK,
UNKNOWN,
NON_ANONYMOUS
}
private interface OnEventListener {
void onSuccess();
void onFailure();
}
public interface OnRenameListener extends OnEventListener {
}
public static class User implements Comparable<User>, AvatarService.Avatarable {
private Role role = Role.NONE;
private Affiliation affiliation = Affiliation.NONE;
private Jid realJid;
private Jid fullJid;
private long pgpKeyId = 0;
private Avatar avatar;
private MucOptions options;
private ChatState chatState = Config.DEFAULT_CHATSTATE;
public User(MucOptions options, Jid fullJid) {
this.options = options;
this.fullJid = fullJid;
}
public String getName() {
return fullJid == null ? null : fullJid.getResource();
}
public Role getRole() {
return this.role;
}
public void setRole(String role) {
this.role = Role.of(role);
}
public Affiliation getAffiliation() {
return this.affiliation;
}
public void setAffiliation(String affiliation) {
this.affiliation = Affiliation.of(affiliation);
}
public long getPgpKeyId() {
if (this.pgpKeyId != 0) {
return this.pgpKeyId;
} else if (realJid != null) {
return getAccount().getRoster().getContact(realJid).getPgpKeyId();
} else {
return 0;
}
}
public void setPgpKeyId(long id) {
this.pgpKeyId = id;
}
public Contact getContact() {
if (fullJid != null) {
return getAccount().getRoster().getContactFromContactList(realJid);
} else if (realJid != null) {
return getAccount().getRoster().getContact(realJid);
} else {
return null;
}
}
public boolean setAvatar(Avatar avatar) {
if (this.avatar != null && this.avatar.equals(avatar)) {
return false;
} else {
this.avatar = avatar;
return true;
}
}
public String getAvatar() {
if (avatar != null) {
return avatar.getFilename();
}
Avatar avatar = realJid != null ? getAccount().getRoster().getContact(realJid).getAvatar() : null;
return avatar == null ? null : avatar.getFilename();
}
public Account getAccount() {
return options.getAccount();
}
public Conversation getConversation() {
return options.getConversation();
}
public Jid getFullJid() {
return fullJid;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (role != user.role) return false;
if (affiliation != user.affiliation) return false;
if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null)
return false;
return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null;
}
public boolean isDomain() {
return realJid != null && realJid.getLocal() == null && role == Role.NONE;
}
@Override
public int hashCode() {
int result = role != null ? role.hashCode() : 0;
result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0);
result = 31 * result + (realJid != null ? realJid.hashCode() : 0);
result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "[fulljid:" + String.valueOf(fullJid) + ",realjid:" + String.valueOf(realJid) + ",affiliation" + affiliation.toString() + "]";
}
public boolean realJidMatchesAccount() {
return realJid != null && realJid.equals(options.account.getJid().asBareJid());
}
@Override
public int compareTo(@NonNull User another) {
if (another.getAffiliation().outranks(getAffiliation())) {
return 1;
} else if (getAffiliation().outranks(another.getAffiliation())) {
return -1;
} else {
return getComparableName().compareToIgnoreCase(another.getComparableName());
}
}
public String getComparableName() {
Contact contact = getContact();
if (contact != null) {
return contact.getDisplayName();
} else {
String name = getName();
return name == null ? "" : name;
}
}
public Jid getRealJid() {
return realJid;
}
public void setRealJid(Jid jid) {
this.realJid = jid != null ? jid.asBareJid() : null;
}
public boolean setChatState(ChatState chatState) {
if (this.chatState == chatState) {
return false;
}
this.chatState = chatState;
return true;
}
@Override
public int getAvatarBackgroundColor() {
final String seed = realJid != null ? realJid.asBareJid().toString() : null;
return UIHelper.getColorForName(seed == null ? getName() : seed);
}
}
}