store avatars received over muc presence in contact

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Contact.java               |    6 
src/main/java/eu/siacs/conversations/entities/MucOptions.java            | 1621 
src/main/java/eu/siacs/conversations/parser/PresenceParser.java          |    8 
src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java    |    2 
src/main/java/eu/siacs/conversations/services/AvatarService.java         |   20 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |    8 
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java        |   10 
7 files changed, 845 insertions(+), 830 deletions(-)

Detailed changes

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

@@ -431,10 +431,14 @@ public class Contact implements ListItem, Blockable {
 		}
 	}
 
-	public String getAvatar() {
+	public String getAvatarFilename() {
 		return avatar == null ? null : avatar.getFilename();
 	}
 
+	public Avatar getAvatar() {
+		return avatar;
+	}
+
 	public boolean mutualPresenceSubscription() {
 		return getOption(Options.FROM) && getOption(Options.TO);
 	}

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

@@ -23,819 +23,810 @@ import rocks.xmpp.addr.Jid;
 
 public class MucOptions {
 
-	private boolean mAutoPushConfiguration = true;
-
-	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 mamSupport() {
-		return MessageArchiveService.Version.has(getFeatures());
-	}
-
-	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);
-
-		Affiliation(int rank, int resId) {
-			this.resId = resId;
-			this.rank = rank;
-		}
-
-		private int resId;
-		private int rank;
-
-		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 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 enum Role {
-		MODERATOR(R.string.moderator, 3),
-		VISITOR(R.string.visitor, 1),
-		PARTICIPANT(R.string.participant, 2),
-		NONE(R.string.no_role, 0);
-
-		Role(int resId, int rank) {
-			this.resId = resId;
-			this.rank = rank;
-		}
-
-		private int resId;
-		private int rank;
-
-		public int getResId() {
-			return resId;
-		}
-
-		@Override
-		public String toString() {
-			return name().toLowerCase(Locale.US);
-		}
-
-		public boolean ranks(Role role) {
-			return rank >= role.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 enum Error {
-		NO_RESPONSE,
-		SERVER_NOT_FOUND,
-		NONE,
-		NICK_IN_USE,
-		PASSWORD_REQUIRED,
-		BANNED,
-		MEMBERS_ONLY,
-		RESOURCE_CONSTRAINT,
-		KICKED,
-		SHUTDOWN,
-		DESTROYED,
-		INVALID_NICK,
-		UNKNOWN
-	}
-
-	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 interface OnEventListener {
-		void onSuccess();
-
-		void onFailure();
-	}
-
-	public interface OnRenameListener extends OnEventListener {
-
-	}
-
-	public static class User implements Comparable<User> {
-		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 from) {
-			this.options = options;
-			this.fullJid = from;
-		}
-
-		public String getName() {
-			return fullJid == null ? null : fullJid.getResource();
-		}
-
-		public void setRealJid(Jid jid) {
-			this.realJid = jid != null ? jid.asBareJid() : null;
-		}
-
-		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 void setPgpKeyId(long id) {
-			this.pgpKeyId = id;
-		}
-
-		public long getPgpKeyId() {
-			if (this.pgpKeyId != 0) {
-				return this.pgpKeyId;
-			} else if (realJid != null) {
-				return getAccount().getRoster().getContact(realJid).getPgpKeyId();
-			} else {
-				return 0;
-			}
-		}
-
-		public Contact getContact() {
-			if (fullJid != null) {
-				return getAccount().getRoster().getContactFromRoster(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() {
-			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());
-			}
-		}
-
-
-		private 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 boolean setChatState(ChatState chatState) {
-			if (this.chatState == chatState) {
-				return false;
-			}
-			this.chatState = chatState;
-			return true;
-		}
-	}
-
-	private Account account;
-	private final Set<User> users = new HashSet<>();
-	private ServiceDiscoveryResult serviceDiscoveryResult;
-	private final Conversation conversation;
-	private boolean isOnline = false;
-	private Error error = Error.NONE;
-	public OnRenameListener onRenameListener = null;
-	private User self;
-	private String password = null;
-
-	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 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()).getAvatar();
-	}
-
-	public boolean hasFeature(String feature) {
-		return this.serviceDiscoveryResult != null && this.serviceDiscoveryResult.features.contains(feature);
-	}
-
-	public boolean hasVCards() {
-	    return hasFeature("vcard-temp");
-    }
-
-	public boolean canInvite() {
-		Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
-		return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
-	}
-
-	public boolean canChangeSubject() {
-		Field field = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject");
-		return self.getRole().ranks(Role.MODERATOR) || (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) {
-					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) {
-		User user = findUserByRealJid(jid);
-		if (user == null) {
-			user = new User(this, null);
-			user.setRealJid(jid);
-		}
-		return user;
-	}
-
-	public User findUser(ReadByMarker readByMarker) {
-		if (readByMarker.getRealJid() != null) {
-			User user = findUserByRealJid(readByMarker.getRealJid().asBareJid());
-			if (user == null) {
-				user = new User(this, readByMarker.getFullJid());
-				user.setRealJid(readByMarker.getRealJid());
-			}
-			return user;
-		} 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 void setError(Error error) {
-		this.isOnline = isOnline && error == Error.NONE;
-		this.error = error;
-	}
-
-	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 int getUserCount() {
-		synchronized (users) {
-			return users.size();
-		}
-	}
-
-	private String getProposedNick() {
-		if (conversation.getBookmark() != null
-				&& conversation.getBookmark().getNick() != null
-				&& !conversation.getBookmark().getNick().trim().isEmpty()) {
-			return conversation.getBookmark().getNick().trim();
-		} else if (!conversation.getJid().isBareJid()) {
-			return conversation.getJid().getResource();
-		} else {
-			return JidHelper.localPartOrFallback(account.getJid());
-		}
-	}
-
-	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 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;
-	}
-
-	public 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 Jid.of(this.conversation.getJid().asBareJid().toString() + "/" + 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.isDomain() || includeDomains)) {
-					members.add(user.realJid);
-				}
-			}
-		}
-		return members;
-	}
+    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;
+    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 mamSupport() {
+        return MessageArchiveService.Version.has(getFeatures());
+    }
+
+    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() {
+        Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
+        return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
+    }
+
+    public boolean canChangeSubject() {
+        Field field = getRoomInfoForm().getFieldByName("muc#roominfo_changesubject");
+        return self.getRole().ranks(Role.MODERATOR) || (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) {
+                    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 int getUserCount() {
+        synchronized (users) {
+            return users.size();
+        }
+    }
+
+    private String getProposedNick() {
+        if (conversation.getBookmark() != null
+                && conversation.getBookmark().getNick() != null
+                && !conversation.getBookmark().getNick().trim().isEmpty()) {
+            return conversation.getBookmark().getNick().trim();
+        } else if (!conversation.getJid().isBareJid()) {
+            return conversation.getJid().getResource();
+        } else {
+            return JidHelper.localPartOrFallback(account.getJid());
+        }
+    }
+
+    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;
+    }
+
+    public 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 Jid.of(this.conversation.getJid().asBareJid().toString() + "/" + 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.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,
+        NONE,
+        NICK_IN_USE,
+        PASSWORD_REQUIRED,
+        BANNED,
+        MEMBERS_ONLY,
+        RESOURCE_CONSTRAINT,
+        KICKED,
+        SHUTDOWN,
+        DESTROYED,
+        INVALID_NICK,
+        UNKNOWN
+    }
+
+    private interface OnEventListener {
+        void onSuccess();
+
+        void onFailure();
+    }
+
+    public interface OnRenameListener extends OnEventListener {
+
+    }
+
+    public static class User implements Comparable<User> {
+        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().getContactFromRoster(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());
+            }
+        }
+
+        private 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;
+        }
+    }
 }

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

@@ -119,6 +119,14 @@ public class PresenceParser extends AbstractParser implements
 								if (user.setAvatar(avatar)) {
 									mXmppConnectionService.getAvatarService().clear(user);
 								}
+								if (user.getRealJid() != null) {
+									Contact c = conversation.getAccount().getRoster().getContact(user.getRealJid());
+									if (c.setAvatar(avatar)) {
+										mXmppConnectionService.syncRoster(conversation.getAccount());
+										mXmppConnectionService.getAvatarService().clear(c);
+										mXmppConnectionService.updateRosterUi();
+									}
+								}
 							} else if (mXmppConnectionService.isDataSaverDisabled()) {
 								mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
 							}

src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java 🔗

@@ -911,7 +911,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
 		final SQLiteDatabase db = this.getWritableDatabase();
 		db.beginTransaction();
 		for (Contact contact : roster.getContacts()) {
-			if (contact.getOption(Contact.Options.IN_ROSTER) || contact.getAvatar() != null) {
+			if (contact.getOption(Contact.Options.IN_ROSTER) || contact.getAvatarFilename() != null) {
 				db.insert(Contact.TABLENAME, null, contact.getContentValues());
 			} else {
 				String where = Contact.ACCOUNT + "=? AND " + Contact.JID + "=?";

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

@@ -69,8 +69,8 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 		if (contact.getProfilePhoto() != null) {
 			avatar = mXmppConnectionService.getFileBackend().cropCenterSquare(Uri.parse(contact.getProfilePhoto()), size);
 		}
-		if (avatar == null && contact.getAvatar() != null) {
-			avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatar(), size);
+		if (avatar == null && contact.getAvatarFilename() != null) {
+			avatar = mXmppConnectionService.getFileBackend().getAvatar(contact.getAvatarFilename(), size);
 		}
 		if (avatar == null) {
 			avatar = get(contact.getDisplayName(), contact.getJid().asBareJid().toString(), size, cachedOnly);
@@ -128,7 +128,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) {
 		Contact c = user.getContact();
-		if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null || user.getAvatar() == null)) {
+		if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null || user.getAvatar() == null)) {
 			return get(c, size, cachedOnly);
 		} else {
 			return getImpl(user, size, cachedOnly);
@@ -165,6 +165,10 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 			}
 		}
 		for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) {
+			MucOptions.User user = conversation.getMucOptions().findUserByRealJid(contact.getJid().asBareJid());
+			if (user != null) {
+				clear(user);
+			}
 			clear(conversation);
 		}
 	}
@@ -216,7 +220,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 				Jid jid = bookmark.getJid();
 				Account account = bookmark.getAccount();
 				Contact contact = jid == null ? null : account.getRoster().getContact(jid);
-				if (contact != null && contact.getAvatar() != null) {
+				if (contact != null && contact.getAvatarFilename() != null) {
 					return get(contact, size, cachedOnly);
 				}
 				String seed = jid != null ? jid.asBareJid().toString() : null;
@@ -400,14 +404,14 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 			return get(message.getCounterparts(), size, cachedOnly);
 		} else if (message.getStatus() == Message.STATUS_RECEIVED) {
 			Contact c = message.getContact();
-			if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) {
+			if (c != null && (c.getProfilePhoto() != null || c.getAvatarFilename() != null)) {
 				return get(c, size, cachedOnly);
 			} else if (conversation instanceof Conversation && message.getConversation().getMode() == Conversation.MODE_MULTI) {
 				final Jid trueCounterpart = message.getTrueCounterpart();
 				final MucOptions mucOptions = ((Conversation) conversation).getMucOptions();
 				MucOptions.User user;
 				if (trueCounterpart != null) {
-					user = mucOptions.findUserByRealJid(trueCounterpart);
+					user = mucOptions.findOrCreateUserByRealJid(trueCounterpart, message.getCounterpart());
 				} else {
 					user = mucOptions.findUserByFullJid(message.getCounterpart());
 				}
@@ -508,9 +512,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 			Uri uri = null;
 			if (contact.getProfilePhoto() != null) {
 				uri = Uri.parse(contact.getProfilePhoto());
-			} else if (contact.getAvatar() != null) {
+			} else if (contact.getAvatarFilename() != null) {
 				uri = mXmppConnectionService.getFileBackend().getAvatarUri(
-						contact.getAvatar());
+						contact.getAvatarFilename());
 			}
 			if (drawTile(canvas, uri, left, top, right, bottom)) {
 				return true;

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

@@ -3114,6 +3114,14 @@ public class XmppConnectionService extends Service {
 											updateConversationUi();
 											updateMucRosterUi();
 										}
+										if (user.getRealJid() != null) {
+										    Contact contact = account.getRoster().getContact(user.getRealJid());
+										    if (contact.setAvatar(avatar)) {
+                                                syncRoster(account);
+                                                getAvatarService().clear(contact);
+                                                updateRosterUi();
+                                            }
+                                        }
 									}
 								}
 							}

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

@@ -874,7 +874,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
                     attachLocationToConversation(conversation, attachment.getUri());
                 } else if (attachment.getType() == Attachment.Type.IMAGE) {
                     Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching image to conversations. CHOOSE_IMAGE");
-					attachImageToConversation(conversation, attachment.getUri());
+                    attachImageToConversation(conversation, attachment.getUri());
                 } else {
                     Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO");
                     attachFileToConversation(conversation, attachment.getUri(), attachment.getMime());
@@ -901,7 +901,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
         switch (requestCode) {
             case ATTACHMENT_CHOICE_TAKE_PHOTO:
                 if (pendingTakePhotoUri.clear()) {
-                    Log.d(Config.LOGTAG,"cleared pending photo uri after negative activity result");
+                    Log.d(Config.LOGTAG, "cleared pending photo uri after negative activity result");
                 }
                 break;
         }
@@ -1033,7 +1033,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
                     Jid tcp = message.getTrueCounterpart();
                     Jid cp = message.getCounterpart();
                     if (cp != null && !cp.isBareJid()) {
-                        User userByRealJid = tcp != null ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp) : null;
+                        User userByRealJid = tcp != null ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp, cp) : null;
                         final User user = userByRealJid != null ? userByRealJid : conversation.getMucOptions().findUserByFullJid(cp);
                         final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
                         popupMenu.inflate(R.menu.muc_details_context);
@@ -2670,7 +2670,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
             setScrollPosition(scrollState, lastMessageUuid);
         }
         if (attachments != null && attachments.size() > 0) {
-            Log.d(Config.LOGTAG,"had attachments on restore");
+            Log.d(Config.LOGTAG, "had attachments on restore");
             mediaPreviewAdapter.addMediaPreviews(attachments);
             toggleInputMethod();
         }
@@ -2682,7 +2682,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
             Log.e(Config.LOGTAG, "cleared pending intent with unhandled result left");
         }
         if (pendingScrollState.clear()) {
-            Log.e(Config.LOGTAG,"cleared scroll state");
+            Log.e(Config.LOGTAG, "cleared scroll state");
         }
         if (pendingTakePhotoUri.clear()) {
             Log.e(Config.LOGTAG, "cleared pending photo uri");