keep conference members in memory and show them in conference details

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java  |   1 
src/main/java/eu/siacs/conversations/entities/MucOptions.java            | 174 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |   4 
src/main/java/eu/siacs/conversations/parser/PresenceParser.java          |  11 
src/main/java/eu/siacs/conversations/services/AvatarService.java         |   2 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  26 
src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java   |  37 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java        |  22 
src/main/java/eu/siacs/conversations/ui/XmppActivity.java                |   4 
src/main/res/values/strings.xml                                          |   4 
10 files changed, 186 insertions(+), 99 deletions(-)

Detailed changes

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

@@ -5,9 +5,7 @@ import android.annotation.SuppressLint;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 import eu.siacs.conversations.R;
@@ -28,6 +26,15 @@ public class MucOptions {
 		this.self = user;
 	}
 
+	public void changeAffiliation(Jid jid, Affiliation affiliation) {
+		User user = findUserByRealJid(jid);
+		if (user != null) {
+			users.remove(user);
+			user.affiliation = affiliation;
+			users.add(user);
+		}
+	}
+
 	public enum Affiliation {
 		OWNER("owner", 4, R.string.owner),
 		ADMIN("admin", 3, R.string.admin),
@@ -127,7 +134,7 @@ public class MucOptions {
 	public static class User {
 		private Role role = Role.NONE;
 		private Affiliation affiliation = Affiliation.NONE;
-		private Jid jid;
+		private Jid realJid;
 		private Jid fullJid;
 		private long pgpKeyId = 0;
 		private Avatar avatar;
@@ -139,15 +146,15 @@ public class MucOptions {
 		}
 
 		public String getName() {
-			return this.fullJid.getResourcepart();
+			return fullJid == null ? null : fullJid.getResourcepart();
 		}
 
-		public void setJid(Jid jid) {
-			this.jid = jid;
+		public void setRealJid(Jid jid) {
+			this.realJid = jid != null ? jid.toBareJid() : null;
 		}
 
-		public Jid getJid() {
-			return this.jid;
+		public Jid getRealJid() {
+			return this.realJid;
 		}
 
 		public Role getRole() {
@@ -155,6 +162,10 @@ public class MucOptions {
 		}
 
 		public void setRole(String role) {
+			if (role == null) {
+				this.role = Role.NONE;
+				return;
+			}
 			role = role.toLowerCase();
 			switch (role) {
 				case "moderator":
@@ -172,26 +183,15 @@ public class MucOptions {
 			}
 		}
 
-		@Override
-		public boolean equals(Object other) {
-			if (this == other) {
-				return true;
-			} else if (!(other instanceof User)) {
-				return false;
-			} else {
-				User o = (User) other;
-				return getName() != null && getName().equals(o.getName())
-						&& jid != null && jid.equals(o.jid)
-						&& affiliation == o.affiliation
-						&& role == o.role;
-			}
-		}
-
 		public Affiliation getAffiliation() {
 			return this.affiliation;
 		}
 
 		public void setAffiliation(String affiliation) {
+			if (affiliation == null) {
+				this.affiliation = Affiliation.NONE;
+				return;
+			}
 			affiliation = affiliation.toLowerCase();
 			switch (affiliation) {
 				case "admin":
@@ -220,7 +220,7 @@ public class MucOptions {
 		}
 
 		public Contact getContact() {
-			return getAccount().getRoster().getContactFromRoster(getJid());
+			return getAccount().getRoster().getContactFromRoster(getRealJid());
 		}
 
 		public boolean setAvatar(Avatar avatar) {
@@ -243,11 +243,39 @@ public class MucOptions {
 		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;
+
+		}
+
+		@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()+"]";
+		}
 	}
 
 	private Account account;
-	private final Map<String, User> users = Collections.synchronizedMap(new LinkedHashMap<String, User>());
-	private final Set<Jid> members = Collections.synchronizedSet(new HashSet<Jid>());
+	private final Set<User> users = Collections.synchronizedSet(new HashSet<User>());
 	private final List<String> features = new ArrayList<>();
 	private Data form = new Data();
 	private Conversation conversation;
@@ -315,20 +343,66 @@ public class MucOptions {
 		return hasFeature("muc_moderated");
 	}
 
-	public User deleteUser(String name) {
-		return this.users.remove(name);
+	public User deleteUser(Jid jid) {
+		User user = findUserByFullJid(jid);
+		if (user != null) {
+			users.remove(user);
+			if (user.affiliation.ranks(Affiliation.MEMBER)) {
+				user.role = Role.NONE;
+				user.avatar = null;
+				user.fullJid = null;
+				users.add(user);
+			}
+		}
+		return user;
 	}
 
 	public void addUser(User user) {
-		this.users.put(user.getName(), user);
+		User old;
+		if (user.fullJid == null && user.realJid != null) {
+			old = findUserByRealJid(user.realJid);
+			if (old != null) {
+				return; //don't add. user already exists
+			}
+		} else if (user.realJid != null) {
+			old = findUserByRealJid(user.realJid);
+			if (old != null && old.fullJid == null) {
+				users.remove(old);
+			}
+		}
+		old = findUserByFullJid(user.getFullJid());
+		if (old != null) {
+			users.remove(old);
+		}
+		this.users.add(user);
+	}
+
+	public User findUserByFullJid(Jid jid) {
+		if (jid == null) {
+			return null;
+		}
+		for(User user : users) {
+			if (jid.equals(user.getFullJid())) {
+				return user;
+			}
+		}
+		return null;
 	}
 
-	public User findUser(String name) {
-		return this.users.get(name);
+	public User findUserByRealJid(Jid jid) {
+		if (jid == null) {
+			return null;
+		}
+		for(User user : users) {
+			if (jid.equals(user.getRealJid())) {
+				return user;
+			}
+		}
+		return null;
 	}
 
-	public boolean isUserInRoom(String name) {
-		return findUser(name) != null;
+	public boolean isUserInRoom(Jid jid) {
+		return findUserByFullJid(jid) != null;
 	}
 
 	public void setError(Error error) {
@@ -341,7 +415,7 @@ public class MucOptions {
 	}
 
 	public ArrayList<User> getUsers() {
-		return new ArrayList<>(users.values());
+		return new ArrayList<>(users);
 	}
 
 	public List<User> getUsers(int max) {
@@ -410,7 +484,7 @@ public class MucOptions {
 				Contact contact = user.getContact();
 				if (contact != null && !contact.getDisplayName().isEmpty()) {
 					names.add(contact.getDisplayName().split("\\s+")[0]);
-				} else {
+				} else if (user.getName() != null){
 					names.add(user.getName());
 				}
 			}
@@ -429,7 +503,7 @@ public class MucOptions {
 
 	public long[] getPgpKeyIds() {
 		List<Long> ids = new ArrayList<>();
-		for (User user : this.users.values()) {
+		for (User user : this.users) {
 			if (user.getPgpKeyId() != 0) {
 				ids.add(user.getPgpKeyId());
 			}
@@ -443,7 +517,7 @@ public class MucOptions {
 	}
 
 	public boolean pgpKeysInUse() {
-		for (User user : this.users.values()) {
+		for (User user : this.users) {
 			if (user.getPgpKeyId() != 0) {
 				return true;
 			}
@@ -452,7 +526,7 @@ public class MucOptions {
 	}
 
 	public boolean everybodyHasKeys() {
-		for (User user : this.users.values()) {
+		for (User user : this.users) {
 			if (user.getPgpKeyId() == 0) {
 				return false;
 			}
@@ -468,12 +542,12 @@ public class MucOptions {
 		}
 	}
 
-	public Jid getTrueCounterpart(String name) {
-		if (name.equals(getSelf().getName())) {
+	public Jid getTrueCounterpart(Jid jid) {
+		if (jid.equals(getSelf().getFullJid())) {
 			return account.getJid().toBareJid();
 		}
-		User user = findUser(name);
-		return user == null ? null : user.getJid();
+		User user = findUserByFullJid(jid);
+		return user == null ? null : user.getRealJid();
 	}
 
 	public String getPassword() {
@@ -499,11 +573,21 @@ public class MucOptions {
 		return this.conversation;
 	}
 
-	public void putMember(Jid jid) {
-		members.add(jid);
+	public void putMember(Jid fullJid, Jid realJid, String affiliation, String role) {
+		User user = new User(this, fullJid);
+		user.setRealJid(realJid);
+		user.setAffiliation(affiliation);
+		user.setRole(role);
+		addUser(user);
 	}
 
 	public List<Jid> getMembers() {
-		return new ArrayList<>(members);
+		ArrayList<Jid> members = new ArrayList<>();
+		for(User user : users) {
+			if (user.affiliation.ranks(Affiliation.MEMBER) && user.getRealJid() != null) {
+				members.add(user.getRealJid());
+			}
+		}
+		return members;
 	}
 }

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -402,7 +402,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 			} else if (axolotlEncrypted != null && Config.supportOmemo()) {
 				Jid origin;
 				if (conversation.getMode() == Conversation.MODE_MULTI) {
-					origin = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
+					origin = conversation.getMucOptions().getTrueCounterpart(counterpart);
 					if (origin == null) {
 						Log.d(Config.LOGTAG,"axolotl message in non anonymous conference received");
 						return;
@@ -430,7 +430,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
 			message.setOob(isOob);
 			message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
 			if (conversation.getMode() == Conversation.MODE_MULTI) {
-				Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart.getResourcepart());
+				Jid trueCounterpart = conversation.getMucOptions().getTrueCounterpart(counterpart);
 				message.setTrueCounterpart(trueCounterpart);
 				if (!isTypeGroupChat) {
 					message.setType(Message.TYPE_PRIVATE);

src/main/java/eu/siacs/conversations/parser/PresenceParser.java 🔗

@@ -14,15 +14,12 @@ import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.MucOptions;
 import eu.siacs.conversations.entities.Presence;
-import eu.siacs.conversations.entities.ServiceDiscoveryResult;
 import eu.siacs.conversations.generator.PresenceGenerator;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
 import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
 import eu.siacs.conversations.xmpp.jid.Jid;
 import eu.siacs.conversations.xmpp.pep.Avatar;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
 import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
 
 public class PresenceParser extends AbstractParser implements
@@ -67,11 +64,7 @@ public class PresenceParser extends AbstractParser implements
 						MucOptions.User user = new MucOptions.User(mucOptions, from);
 						user.setAffiliation(item.getAttribute("affiliation"));
 						user.setRole(item.getAttribute("role"));
-						Jid real = item.getAttributeAsJid("jid");
-						if (real != null) {
-							user.setJid(real);
-							mucOptions.putMember(real.toBareJid());
-						}
+						user.setRealJid(item.getAttributeAsJid("jid"));
 						if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) {
 							mucOptions.setOnline();
 							mucOptions.setSelf(user);
@@ -127,7 +120,7 @@ public class PresenceParser extends AbstractParser implements
 						Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
 					}
 				} else if (!from.isBareJid()){
-					MucOptions.User user = mucOptions.deleteUser(from.getResourcepart());
+					MucOptions.User user = mucOptions.deleteUser(from);
 					if (user != null) {
 						mXmppConnectionService.getAvatarService().clear(user);
 					}

src/main/java/eu/siacs/conversations/services/AvatarService.java 🔗

@@ -246,7 +246,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 			if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) {
 				return get(c, size, cachedOnly);
 			} else if (message.getConversation().getMode() == Conversation.MODE_MULTI){
-				MucOptions.User user = conversation.getMucOptions().findUser(message.getCounterpart().getResourcepart());
+				MucOptions.User user = conversation.getMucOptions().findUserByFullJid(message.getCounterpart());
 				if (user != null) {
 					return getImpl(user,size,cachedOnly);
 				}

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -1899,11 +1899,26 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
+
 				Element query = packet.query("http://jabber.org/protocol/muc#admin");
 				if (packet.getType() == IqPacket.TYPE.RESULT && query != null) {
+					final String local = conversation.getJid().getLocalpart();
+					final String domain = conversation.getJid().getDomainpart();
 					for(Element child : query.getChildren()) {
 						if ("item".equals(child.getName())) {
-							conversation.getMucOptions().putMember(child.getAttributeAsJid("jid"));
+							String affiliation = child.getAttribute("affiliation");
+							String role = child.getAttribute("role");
+							String nick = child.getAttribute("nick");
+							Jid fullJid;
+							try {
+								fullJid = nick != null ? Jid.fromParts(local, domain, nick) : null;
+							} catch (InvalidJidException e) {
+								fullJid = null;
+							}
+							Jid realJid = child.getAttributeAsJid("jid");
+							if (realJid != null && !realJid.equals(account.getJid().toBareJid())) {
+								conversation.getMucOptions().putMember(fullJid, realJid, affiliation, role);
+							}
 						}
 					}
 				} else {
@@ -2175,13 +2190,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		}
 	}
 
-	public void changeAffiliationInConference(final Conversation conference, Jid user, MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) {
+	public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) {
 		final Jid jid = user.toBareJid();
 		IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString());
 		sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() {
 			@Override
 			public void onIqPacketReceived(Account account, IqPacket packet) {
 				if (packet.getType() == IqPacket.TYPE.RESULT) {
+					conference.getMucOptions().changeAffiliation(jid, affiliation);
 					callback.onAffiliationChangedSuccessful(jid);
 				} else {
 					callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation);
@@ -2193,8 +2209,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 	public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) {
 		List<Jid> jids = new ArrayList<>();
 		for (MucOptions.User user : conference.getMucOptions().getUsers()) {
-			if (user.getAffiliation() == before && user.getJid() != null) {
-				jids.add(user.getJid());
+			if (user.getAffiliation() == before && user.getRealJid() != null) {
+				jids.add(user.getRealJid());
 			}
 		}
 		IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString());
@@ -2574,7 +2590,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 							} else {
 								Conversation conversation = find(account, avatar.owner.toBareJid());
 								if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
-									MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart());
+									MucOptions.User user = conversation.getMucOptions().findUserByFullJid(avatar.owner);
 									if (user != null) {
 										if (user.setAvatar(avatar)) {
 											getAvatarService().clear(user);

src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java 🔗

@@ -1,6 +1,5 @@
 package eu.siacs.conversations.ui;
 
-import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
@@ -8,7 +7,6 @@ import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentSender.SendIntentException;
-import android.os.Build;
 import android.os.Bundle;
 import android.view.ContextMenu;
 import android.view.LayoutInflater;
@@ -365,13 +363,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 			final Contact contact = user.getContact();
 			if (contact != null) {
 				name = contact.getDisplayName();
-			} else if (user.getJid() != null){
-				name = user.getJid().toBareJid().toString();
+			} else if (user.getRealJid() != null){
+				name = user.getRealJid().toBareJid().toString();
 			} else {
 				name = user.getName();
 			}
 			menu.setHeaderTitle(name);
-			if (user.getJid() != null) {
+			if (user.getRealJid() != null) {
 				MenuItem showContactDetails = menu.findItem(R.id.action_contact_details);
 				MenuItem startConversation = menu.findItem(R.id.start_conversation);
 				MenuItem giveMembership = menu.findItem(R.id.give_membership);
@@ -424,22 +422,22 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 				startConversation(mSelectedUser);
 				return true;
 			case R.id.give_admin_privileges:
-				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.ADMIN,this);
+				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.ADMIN,this);
 				return true;
 			case R.id.give_membership:
-				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.MEMBER,this);
+				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.MEMBER,this);
 				return true;
 			case R.id.remove_membership:
-				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.NONE,this);
+				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.NONE,this);
 				return true;
 			case R.id.remove_admin_privileges:
-				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.MEMBER,this);
+				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.MEMBER,this);
 				return true;
 			case R.id.remove_from_room:
 				removeFromRoom(mSelectedUser);
 				return true;
 			case R.id.ban_from_conference:
-				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getJid(), MucOptions.Affiliation.OUTCAST,this);
+				xmppConnectionService.changeAffiliationInConference(mConversation,mSelectedUser.getRealJid(), MucOptions.Affiliation.OUTCAST,this);
 				xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,this);
 				return true;
 			case R.id.send_private_message:
@@ -452,7 +450,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 
 	private void removeFromRoom(final User user) {
 		if (mConversation.getMucOptions().membersOnly()) {
-			xmppConnectionService.changeAffiliationInConference(mConversation,user.getJid(), MucOptions.Affiliation.NONE,this);
+			xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.NONE,this);
 			xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,ConferenceDetailsActivity.this);
 		} else {
 			AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -462,7 +460,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 			builder.setPositiveButton(R.string.ban_now,new DialogInterface.OnClickListener() {
 				@Override
 				public void onClick(DialogInterface dialog, int which) {
-					xmppConnectionService.changeAffiliationInConference(mConversation,user.getJid(), MucOptions.Affiliation.OUTCAST,ConferenceDetailsActivity.this);
+					xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.OUTCAST,ConferenceDetailsActivity.this);
 					xmppConnectionService.changeRoleInConference(mConversation,mSelectedUser.getName(), MucOptions.Role.NONE,ConferenceDetailsActivity.this);
 				}
 			});
@@ -471,8 +469,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 	}
 
 	protected void startConversation(User user) {
-		if (user.getJid() != null) {
-			Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(),user.getJid().toBareJid(),false);
+		if (user.getRealJid() != null) {
+			Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(),user.getRealJid().toBareJid(),false);
 			switchToConversation(conversation);
 		}
 	}
@@ -577,8 +575,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 		final ArrayList<User> users = mucOptions.getUsers();
 		Collections.sort(users,new Comparator<User>() {
 			@Override
-			public int compare(User lhs, User rhs) {
-				return lhs.getName().compareToIgnoreCase(rhs.getName());
+			public int compare(User l, User r) {
+				return l.getName() == null || r.getName() == null ? 0 : l.getName().compareToIgnoreCase(r.getName());
 			}
 		});
 		for (final User user : users) {
@@ -607,11 +605,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 				tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
 			}
 			Contact contact = user.getContact();
+			String name = user.getName();
 			if (contact != null) {
 				tvDisplayName.setText(contact.getDisplayName());
-				tvStatus.setText(user.getName() + " \u2022 " + getStatus(user));
+				tvStatus.setText((name != null ? name+ " \u2022 " : "") + getStatus(user));
 			} else {
-				tvDisplayName.setText(user.getName());
+				tvDisplayName.setText(name == null ? "" : name);
 				tvStatus.setText(getStatus(user));
 
 			}
@@ -657,7 +656,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 
 	@Override
 	public void onAffiliationChangedSuccessful(Jid jid) {
-
+		refreshUi();
 	}
 
 	@Override

src/main/java/eu/siacs/conversations/ui/ConversationFragment.java 🔗

@@ -461,12 +461,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 			public void onContactPictureClicked(Message message) {
 				if (message.getStatus() <= Message.STATUS_RECEIVED) {
 					if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
-						if (message.getCounterpart() != null) {
-							String user = message.getCounterpart().isBareJid() ? message.getCounterpart().toString() : message.getCounterpart().getResourcepart();
+						Jid user = message.getCounterpart();
+						if (user != null && !user.isBareJid()) {
 							if (!message.getConversation().getMucOptions().isUserInRoom(user)) {
-								Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user),Toast.LENGTH_SHORT).show();
+								Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user.getResourcepart()),Toast.LENGTH_SHORT).show();
 							}
-							highlightInConference(user);
+							highlightInConference(user.getResourcepart());
 						}
 					} else {
 						if (!message.getContact().isSelf()) {
@@ -495,14 +495,12 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
 					public void onContactPictureLongClicked(Message message) {
 						if (message.getStatus() <= Message.STATUS_RECEIVED) {
 							if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
-								if (message.getCounterpart() != null) {
-									String user = message.getCounterpart().getResourcepart();
-									if (user != null) {
-										if (message.getConversation().getMucOptions().isUserInRoom(user)) {
-											privateMessageWith(message.getCounterpart());
-										} else {
-											Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user), Toast.LENGTH_SHORT).show();
-										}
+								Jid user = message.getCounterpart();
+								if (user != null && !user.isBareJid()) {
+									if (message.getConversation().getMucOptions().isUserInRoom(user)) {
+										privateMessageWith(user);
+									} else {
+										Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show();
 									}
 								}
 							}

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -42,7 +42,6 @@ import android.preference.PreferenceManager;
 import android.text.InputType;
 import android.util.DisplayMetrics;
 import android.util.Log;
-import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
@@ -68,7 +67,6 @@ import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.RunnableFuture;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -481,7 +479,7 @@ public abstract class XmppActivity extends Activity {
 		List<String> contacts = new ArrayList<>();
 		if (conversation.getMode() == Conversation.MODE_MULTI) {
 			for (MucOptions.User user : conversation.getMucOptions().getUsers()) {
-				Jid jid = user.getJid();
+				Jid jid = user.getRealJid();
 				if (jid != null) {
 					contacts.add(jid.toBareJid().toString());
 				}

src/main/res/values/strings.xml 🔗

@@ -447,7 +447,7 @@
 	<string name="disable_all_accounts">Disable all accounts</string>
 	<string name="perform_action_with">Perform action with</string>
 	<string name="no_affiliation">No affiliation</string>
-	<string name="no_role">No role</string>
+	<string name="no_role">Offline</string>
 	<string name="outcast">Outcast</string>
 	<string name="member">Member</string>
 	<string name="advanced_mode">Advanced mode</string>
@@ -542,7 +542,7 @@
 	<string name="pref_use_white_background_summary">Show received messages as black text on a white background</string>
 	<string name="account_status_tor_unavailable">Tor network unavailable</string>
 	<string name="account_status_bind_failure">Bind failure</string>
-	<string name="account_status_host_unknown">Domain not recognized</string>
+	<string name="account_status_host_unknown">Host unknown</string>
 	<string name="server_info_broken">Broken</string>
 	<string name="pref_presence_settings">Presence</string>
 	<string name="pref_away_when_screen_off">Away when screen is off</string>