parse vcard avatars from muc presences

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/MucOptions.java            | 277 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |   2 
src/main/java/eu/siacs/conversations/parser/PresenceParser.java          | 117 
src/main/java/eu/siacs/conversations/services/AvatarService.java         |  50 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |  22 
src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java   |   8 
6 files changed, 295 insertions(+), 181 deletions(-)

Detailed changes

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

@@ -4,20 +4,25 @@ import android.annotation.SuppressLint;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
 
 import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.PgpEngine;
-import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.forms.Data;
 import eu.siacs.conversations.xmpp.forms.Field;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
-import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
+import eu.siacs.conversations.xmpp.pep.Avatar;
 
 @SuppressLint("DefaultLocale")
 public class MucOptions {
 
+	public Account getAccount() {
+		return this.conversation.getAccount();
+	}
+
+	public void setSelf(User user) {
+		this.self = user;
+	}
+
 	public enum Affiliation {
 		OWNER("owner", 4, R.string.owner),
 		ADMIN("admin", 3, R.string.admin),
@@ -100,28 +105,31 @@ public class MucOptions {
 	public static final String STATUS_CODE_LOST_MEMBERSHIP = "321";
 
 	private interface OnEventListener {
-		public void onSuccess();
+		void onSuccess();
 
-		public void onFailure();
+		void onFailure();
 	}
 
 	public interface OnRenameListener extends OnEventListener {
 
 	}
 
-	public class User {
+	public static class User {
 		private Role role = Role.NONE;
 		private Affiliation affiliation = Affiliation.NONE;
-		private String name;
 		private Jid jid;
+		private Jid fullJid;
 		private long pgpKeyId = 0;
+		private Avatar avatar;
+		private MucOptions options;
 
-		public String getName() {
-			return name;
+		public User(MucOptions options, Jid from) {
+			this.options = options;
+			this.fullJid = from;
 		}
 
-		public void setName(String user) {
-			this.name = user;
+		public String getName() {
+			return this.fullJid.getResourcepart();
 		}
 
 		public void setJid(Jid jid) {
@@ -162,7 +170,7 @@ public class MucOptions {
 				return false;
 			} else {
 				User o = (User) other;
-				return name != null && name.equals(o.name)
+				return getName() != null && getName().equals(o.getName())
 						&& jid != null && jid.equals(o.jid)
 						&& affiliation == o.affiliation
 						&& role == o.role;
@@ -202,26 +210,43 @@ public class MucOptions {
 		}
 
 		public Contact getContact() {
-			return account.getRoster().getContactFromRoster(getJid());
+			return getAccount().getRoster().getContactFromRoster(getJid());
+		}
+
+		public void setAvatar(Avatar avatar) {
+			this.avatar = avatar;
+		}
+
+		public String getAvatar() {
+			return avatar == null ? null : avatar.getFilename();
+		}
+
+		public Account getAccount() {
+			return options.getAccount();
+		}
+
+		public Jid getFullJid() {
+			return fullJid;
 		}
 	}
 
 	private Account account;
-	private List<User> users = new CopyOnWriteArrayList<>();
+	private final List<User> users = new ArrayList<>();
 	private List<String> features = new ArrayList<>();
 	private Data form = new Data();
 	private Conversation conversation;
 	private boolean isOnline = false;
 	private int error = ERROR_UNKNOWN;
-	private OnRenameListener onRenameListener = null;
-	private User self = new User();
+	public OnRenameListener onRenameListener = null;
+	private User self;
 	private String subject = null;
 	private String password = null;
-	private boolean mNickChangingInProgress = false;
+	public boolean mNickChangingInProgress = false;
 
 	public MucOptions(Conversation conversation) {
 		this.account = conversation.getAccount();
 		this.conversation = conversation;
+		this.self = new User(this,conversation.getJid());
 	}
 
 	public void updateFeatures(ArrayList<String> features) {
@@ -273,135 +298,67 @@ public class MucOptions {
 	}
 
 	public void deleteUser(String name) {
-		for (int i = 0; i < users.size(); ++i) {
-			if (users.get(i).getName().equals(name)) {
-				users.remove(i);
-				return;
+		synchronized (this.users) {
+			for (int i = 0; i < users.size(); ++i) {
+				if (users.get(i).getName().equals(name)) {
+					users.remove(i);
+					return;
+				}
 			}
 		}
 	}
 
 	public void addUser(User user) {
-		for (int i = 0; i < users.size(); ++i) {
-			if (users.get(i).getName().equals(user.getName())) {
-				users.set(i, user);
-				return;
+		synchronized (this.users) {
+			for (int i = 0; i < users.size(); ++i) {
+				if (users.get(i).getName().equals(user.getName())) {
+					users.set(i, user);
+					return;
+				}
 			}
+			users.add(user);
 		}
-		users.add(user);
 	}
 
-	public boolean isUserInRoom(String name) {
-		for (int i = 0; i < users.size(); ++i) {
-			if (users.get(i).getName().equals(name)) {
-				return true;
-			}
+	public User findUser(String name) {
+		if (name == null) {
+			return null;
 		}
-		return false;
-	}
-
-	public void processPacket(PresencePacket packet, PgpEngine pgp) {
-		final Jid from = packet.getFrom();
-		if (!from.isBareJid()) {
-			final String name = from.getResourcepart();
-			final String type = packet.getAttribute("type");
-			final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
-			final List<String> codes = getStatusCodes(x);
-			if (type == null) {
-				User user = new User();
-				if (x != null) {
-					Element item = x.findChild("item");
-					if (item != null && name != null) {
-						user.setName(name);
-						user.setAffiliation(item.getAttribute("affiliation"));
-						user.setRole(item.getAttribute("role"));
-						user.setJid(item.getAttributeAsJid("jid"));
-						if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) {
-							this.isOnline = true;
-							this.error = ERROR_NO_ERROR;
-							self = user;
-							if (mNickChangingInProgress) {
-								if (onRenameListener != null) {
-									onRenameListener.onSuccess();
-								}
-								mNickChangingInProgress = false;
-							}
-						} else {
-							addUser(user);
-						}
-						if (pgp != null) {
-							Element signed = packet.findChild("x", "jabber:x:signed");
-							if (signed != null) {
-								Element status = packet.findChild("status");
-								String msg = status == null ? "" : status.getContent();
-								long keyId = pgp.fetchKeyId(account, msg, signed.getContent());
-								if (keyId != 0) {
-									user.setPgpKeyId(keyId);
-								}
-							}
-						}
-					}
-				}
-			} else if (type.equals("unavailable")) {
-				if (codes.contains(STATUS_CODE_SELF_PRESENCE) ||
-						packet.getFrom().equals(this.conversation.getJid())) {
-					if (codes.contains(STATUS_CODE_CHANGED_NICK)) {
-						this.mNickChangingInProgress = true;
-					} else if (codes.contains(STATUS_CODE_KICKED)) {
-						setError(KICKED_FROM_ROOM);
-					} else if (codes.contains(STATUS_CODE_BANNED)) {
-						setError(ERROR_BANNED);
-					} else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) {
-						setError(ERROR_MEMBERS_ONLY);
-					} else {
-						setError(ERROR_UNKNOWN);
-					}
-				} else {
-					deleteUser(name);
-				}
-			} else if (type.equals("error")) {
-				Element error = packet.findChild("error");
-				if (error != null && error.hasChild("conflict")) {
-					if (isOnline) {
-						if (onRenameListener != null) {
-							onRenameListener.onFailure();
-						}
-					} else {
-						setError(ERROR_NICK_IN_USE);
-					}
-				} else if (error != null && error.hasChild("not-authorized")) {
-					setError(ERROR_PASSWORD_REQUIRED);
-				} else if (error != null && error.hasChild("forbidden")) {
-					setError(ERROR_BANNED);
-				} else if (error != null && error.hasChild("registration-required")) {
-					setError(ERROR_MEMBERS_ONLY);
+		synchronized (this.users) {
+			for (User user : users) {
+				if (user.getName().equals(name)) {
+					return user;
 				}
 			}
 		}
+		return null;
 	}
 
-	private void setError(int error) {
-		this.isOnline = false;
+	public boolean isUserInRoom(String name) {
+		return findUser(name) != null;
+	}
+
+	public void setError(int error) {
+		this.isOnline = error == ERROR_NO_ERROR;
 		this.error = error;
 	}
 
-	private List<String> getStatusCodes(Element x) {
-		List<String> codes = new ArrayList<>();
-		if (x != null) {
-			for (Element child : x.getChildren()) {
-				if (child.getName().equals("status")) {
-					String code = child.getAttribute("code");
-					if (code != null) {
-						codes.add(code);
-					}
-				}
-			}
+	public ArrayList<User> getUsers() {
+		synchronized (this.users) {
+			return new ArrayList(this.users);
 		}
-		return codes;
 	}
 
-	public List<User> getUsers() {
-		return this.users;
+	public List<User> getUsers(int max) {
+		synchronized (this.users) {
+			return new ArrayList<>(users.subList(0,Math.min(users.size(),5)));
+		}
+	}
+
+	public int getUserCount() {
+		synchronized (this.users) {
+			return this.users.size();
+		}
 	}
 
 	public String getProposedNick() {
@@ -455,34 +412,38 @@ public class MucOptions {
 	}
 
 	public String createNameFromParticipants() {
-		if (users.size() >= 2) {
-			List<String> names = new ArrayList<String>();
-			for (User user : users) {
-				Contact contact = user.getContact();
-				if (contact != null && !contact.getDisplayName().isEmpty()) {
-					names.add(contact.getDisplayName().split("\\s+")[0]);
-				} else {
-					names.add(user.getName());
+		synchronized (this.users) {
+			if (users.size() >= 2) {
+				List<String> names = new ArrayList<String>();
+				for (User user : users) {
+					Contact contact = user.getContact();
+					if (contact != null && !contact.getDisplayName().isEmpty()) {
+						names.add(contact.getDisplayName().split("\\s+")[0]);
+					} else {
+						names.add(user.getName());
+					}
 				}
-			}
-			StringBuilder builder = new StringBuilder();
-			for (int i = 0; i < names.size(); ++i) {
-				builder.append(names.get(i));
-				if (i != names.size() - 1) {
-					builder.append(", ");
+				StringBuilder builder = new StringBuilder();
+				for (int i = 0; i < names.size(); ++i) {
+					builder.append(names.get(i));
+					if (i != names.size() - 1) {
+						builder.append(", ");
+					}
 				}
+				return builder.toString();
+			} else {
+				return null;
 			}
-			return builder.toString();
-		} else {
-			return null;
 		}
 	}
 
 	public long[] getPgpKeyIds() {
 		List<Long> ids = new ArrayList<>();
-		for (User user : getUsers()) {
-			if (user.getPgpKeyId() != 0) {
-				ids.add(user.getPgpKeyId());
+		synchronized (this.users) {
+			for (User user : this.users) {
+				if (user.getPgpKeyId() != 0) {
+					ids.add(user.getPgpKeyId());
+				}
 			}
 		}
 		ids.add(account.getPgpId());
@@ -494,18 +455,22 @@ public class MucOptions {
 	}
 
 	public boolean pgpKeysInUse() {
-		for (User user : getUsers()) {
-			if (user.getPgpKeyId() != 0) {
-				return true;
+		synchronized (this.users) {
+			for (User user : this.users) {
+				if (user.getPgpKeyId() != 0) {
+					return true;
+				}
 			}
 		}
 		return false;
 	}
 
 	public boolean everybodyHasKeys() {
-		for (User user : getUsers()) {
-			if (user.getPgpKeyId() == 0) {
-				return false;
+		synchronized (this.users) {
+			for (User user : this.users) {
+				if (user.getPgpKeyId() == 0) {
+					return false;
+				}
 			}
 		}
 		return true;
@@ -520,9 +485,11 @@ public class MucOptions {
 	}
 
 	public Jid getTrueCounterpart(String counterpart) {
-		for (User user : this.getUsers()) {
-			if (user.getName().equals(counterpart)) {
-				return user.getJid();
+		synchronized (this.users) {
+			for (User user : this.users) {
+				if (user.getName().equals(counterpart)) {
+					return user.getJid();
+				}
 			}
 		}
 		return null;

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

@@ -169,7 +169,7 @@ public class MessageParser extends AbstractParser implements
 		if ("urn:xmpp:avatar:metadata".equals(node)) {
 			Avatar avatar = Avatar.parseMetadata(items);
 			if (avatar != null) {
-				avatar.owner = from;
+				avatar.owner = from.toBareJid();
 				if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
 					if (account.getJid().toBareJid().equals(from)) {
 						if (account.setAvatar(avatar.getFilename())) {

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

@@ -1,7 +1,11 @@
 package eu.siacs.conversations.parser;
 
+import android.util.Log;
+
 import java.util.ArrayList;
+import java.util.List;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.PgpEngine;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
@@ -25,19 +29,18 @@ public class PresenceParser extends AbstractParser implements
 	}
 
 	public void parseConferencePresence(PresencePacket packet, Account account) {
-		PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine();
 		final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().toBareJid());
 		if (conversation != null) {
 			final MucOptions mucOptions = conversation.getMucOptions();
 			boolean before = mucOptions.online();
-			int count = mucOptions.getUsers().size();
-			final ArrayList<MucOptions.User> tileUserBefore = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5)));
-			mucOptions.processPacket(packet, mPgpEngine);
-			final ArrayList<MucOptions.User> tileUserAfter = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5)));
+			int count = mucOptions.getUserCount();
+			final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5);
+			processConferencePresence(packet, mucOptions);
+			final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5);
 			if (!tileUserAfter.equals(tileUserBefore)) {
 				mXmppConnectionService.getAvatarService().clear(conversation);
 			}
-			if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUsers().size())) {
+			if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) {
 				mXmppConnectionService.updateConversationUi();
 			} else if (mucOptions.online()) {
 				mXmppConnectionService.updateMucRosterUi();
@@ -45,6 +48,108 @@ public class PresenceParser extends AbstractParser implements
 		}
 	}
 
+	private void processConferencePresence(PresencePacket packet, MucOptions mucOptions) {
+		final Jid from = packet.getFrom();
+		if (!from.isBareJid()) {
+			final String type = packet.getAttribute("type");
+			final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
+			Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
+			final List<String> codes = getStatusCodes(x);
+			if (type == null) {
+				if (x != null) {
+					Element item = x.findChild("item");
+					if (item != null && !from.isBareJid()) {
+						MucOptions.User user = new MucOptions.User(mucOptions,from);
+						user.setAffiliation(item.getAttribute("affiliation"));
+						user.setRole(item.getAttribute("role"));
+						user.setJid(item.getAttributeAsJid("jid"));
+						if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) {
+							mucOptions.setError(MucOptions.ERROR_NO_ERROR);
+							mucOptions.setSelf(user);
+							if (mucOptions.mNickChangingInProgress) {
+								if (mucOptions.onRenameListener != null) {
+									mucOptions.onRenameListener.onSuccess();
+								}
+								mucOptions.mNickChangingInProgress = false;
+							}
+						} else {
+							mucOptions.addUser(user);
+						}
+						if (mXmppConnectionService.getPgpEngine() != null) {
+							Element signed = packet.findChild("x", "jabber:x:signed");
+							if (signed != null) {
+								Element status = packet.findChild("status");
+								String msg = status == null ? "" : status.getContent();
+								long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent());
+								if (keyId != 0) {
+									user.setPgpKeyId(keyId);
+								}
+							}
+						}
+						if (avatar != null) {
+							avatar.owner = from;
+							if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+								user.setAvatar(avatar);
+							} else {
+								mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
+							}
+							Log.d(Config.LOGTAG, "user " + avatar.owner + " has avatar");
+						}
+					}
+				}
+			} else if (type.equals("unavailable")) {
+				if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) ||
+						packet.getFrom().equals(mucOptions.getConversation().getJid())) {
+					if (codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) {
+						mucOptions.mNickChangingInProgress = true;
+					} else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) {
+						mucOptions.setError(MucOptions.KICKED_FROM_ROOM);
+					} else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) {
+						mucOptions.setError(MucOptions.ERROR_BANNED);
+					} else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) {
+						mucOptions.setError(MucOptions.ERROR_MEMBERS_ONLY);
+					} else {
+						mucOptions.setError(MucOptions.ERROR_UNKNOWN);
+					}
+				} else if (!from.isBareJid()){
+					mucOptions.deleteUser(from.getResourcepart());
+				}
+			} else if (type.equals("error")) {
+				Element error = packet.findChild("error");
+				if (error != null && error.hasChild("conflict")) {
+					if (mucOptions.online()) {
+						if (mucOptions.onRenameListener != null) {
+							mucOptions.onRenameListener.onFailure();
+						}
+					} else {
+						mucOptions.setError(MucOptions.ERROR_NICK_IN_USE);
+					}
+				} else if (error != null && error.hasChild("not-authorized")) {
+					mucOptions.setError(MucOptions.ERROR_PASSWORD_REQUIRED);
+				} else if (error != null && error.hasChild("forbidden")) {
+					mucOptions.setError(MucOptions.ERROR_BANNED);
+				} else if (error != null && error.hasChild("registration-required")) {
+					mucOptions.setError(MucOptions.ERROR_MEMBERS_ONLY);
+				}
+			}
+		}
+	}
+
+	private static List<String> getStatusCodes(Element x) {
+		List<String> codes = new ArrayList<>();
+		if (x != null) {
+			for (Element child : x.getChildren()) {
+				if (child.getName().equals("status")) {
+					String code = child.getAttribute("code");
+					if (code != null) {
+						codes.add(code);
+					}
+				}
+			}
+		}
+		return codes;
+	}
+
 	public void parseContactPresence(final PresencePacket packet, final Account account) {
 		final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
 		final Jid from = packet.getFrom();

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

@@ -58,6 +58,22 @@ public class AvatarService {
 		return avatar;
 	}
 
+	public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) {
+		final String KEY = key(user, size);
+		Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
+		if (avatar != null || cachedOnly) {
+			return avatar;
+		}
+		if (user.getAvatar() != null) {
+			avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size);
+		}
+		if (avatar == null) {
+			avatar = get(user.getName(), size, cachedOnly);
+		}
+		this.mXmppConnectionService.getBitmapCache().put(KEY, avatar);
+		return avatar;
+	}
+
 	public void clear(Contact contact) {
 		synchronized (this.sizes) {
 			for (Integer size : sizes) {
@@ -77,6 +93,16 @@ public class AvatarService {
 				+ contact.getJid() + "_" + String.valueOf(size);
 	}
 
+	private String key(MucOptions.User user, int size) {
+		synchronized (this.sizes) {
+			if (!this.sizes.contains(size)) {
+				this.sizes.add(size);
+			}
+		}
+		return PREFIX_CONTACT + "_" + user.getAccount().getJid().toBareJid() + "_"
+				+ user.getFullJid() + "_" + String.valueOf(size);
+	}
+
 	public Bitmap get(ListItem item, int size) {
 		return get(item,size,false);
 	}
@@ -122,7 +148,7 @@ public class AvatarService {
 		if (bitmap != null || cachedOnly) {
 			return bitmap;
 		}
-		final List<MucOptions.User> users = new ArrayList<>(mucOptions.getUsers());
+		final List<MucOptions.User> users = mucOptions.getUsers();
 		int count = users.size();
 		bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
 		Canvas canvas = new Canvas(bitmap);
@@ -198,15 +224,20 @@ public class AvatarService {
 	}
 
 	public Bitmap get(Message message, int size, boolean cachedOnly) {
+		final Conversation conversation = message.getConversation();
 		if (message.getStatus() == Message.STATUS_RECEIVED) {
-			Contact contact = message.getContact();
-			if (contact != null) {
-				return get(contact, size, cachedOnly);
-			} else {
-				return get(UIHelper.getMessageDisplayName(message), size, cachedOnly);
+			Contact c = message.getContact();
+			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());
+				if (user != null) {
+					return get(user,size,cachedOnly);
+				}
 			}
+			return get(UIHelper.getMessageDisplayName(message), size, cachedOnly);
 		} else  {
-			return get(message.getConversation().getAccount(), size, cachedOnly);
+			return get(conversation.getAccount(), size, cachedOnly);
 		}
 	}
 
@@ -290,6 +321,11 @@ public class AvatarService {
 			if (drawTile(canvas, uri, left, top, right, bottom)) {
 				return true;
 			}
+		} else if (user.getAvatar() != null) {
+			Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar());
+			if (drawTile(canvas, uri, left, top, right, bottom)) {
+				return true;
+			}
 		}
 		String name = contact != null ? contact.getDisplayName() : user.getName();
 		drawTile(canvas, name, left, top, right, bottom);

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

@@ -2358,12 +2358,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 						if (getFileBackend().save(avatar)) {
 							Log.d(Config.LOGTAG, account.getJid().toBareJid()
 									+ ": successfully fetched vCard avatar for " + avatar.owner);
-							Contact contact = account.getRoster()
-									.getContact(avatar.owner);
-							contact.setAvatar(avatar);
-							getAvatarService().clear(contact);
-							updateConversationUi();
-							updateRosterUi();
+							if (avatar.owner.isBareJid()) {
+								Contact contact = account.getRoster()
+										.getContact(avatar.owner);
+								contact.setAvatar(avatar);
+								getAvatarService().clear(contact);
+								updateConversationUi();
+								updateRosterUi();
+							} 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());
+									if (user != null) {
+										user.setAvatar(avatar);
+									}
+								}
+							}
 						}
 					}
 				}

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

@@ -494,8 +494,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 		}
 		LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 		membersView.removeAllViews();
-		final ArrayList<User> users = new ArrayList<>();
-		users.addAll(mConversation.getMucOptions().getUsers());
+		final ArrayList<User> users = mucOptions.getUsers();
 		Collections.sort(users,new Comparator<User>() {
 			@Override
 			public int compare(User lhs, User rhs) {
@@ -527,20 +526,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
 				});
 				tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
 			}
-			Bitmap bm;
 			Contact contact = user.getContact();
 			if (contact != null) {
-				bm = avatarService().get(contact, getPixel(48));
 				tvDisplayName.setText(contact.getDisplayName());
 				tvStatus.setText(user.getName() + " \u2022 " + getStatus(user));
 			} else {
-				bm = avatarService().get(user.getName(), getPixel(48));
 				tvDisplayName.setText(user.getName());
 				tvStatus.setText(getStatus(user));
 
 			}
 			ImageView iv = (ImageView) view.findViewById(R.id.contact_photo);
-			iv.setImageBitmap(bm);
+			iv.setImageBitmap(avatarService().get(user, getPixel(48), false));
 			membersView.addView(view);
 			if (mConversation.getMucOptions().canInvite()) {
 				mInviteButton.setVisibility(View.VISIBLE);