~singpolyma/cheogram-android

1985f6bdec7852eee549ffff3d82092aac447129 — Daniel Gultsch 6 years ago b8d831f
store avatars received over muc presence in contact
M src/main/java/eu/siacs/conversations/entities/Contact.java => src/main/java/eu/siacs/conversations/entities/Contact.java +5 -1
@@ 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);
	}

M src/main/java/eu/siacs/conversations/entities/MucOptions.java => src/main/java/eu/siacs/conversations/entities/MucOptions.java +806 -815
@@ 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;
        }
    }
}

M src/main/java/eu/siacs/conversations/parser/PresenceParser.java => src/main/java/eu/siacs/conversations/parser/PresenceParser.java +8 -0
@@ 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);
							}

M src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java => src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +1 -1
@@ 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 + "=?";

M src/main/java/eu/siacs/conversations/services/AvatarService.java => src/main/java/eu/siacs/conversations/services/AvatarService.java +12 -8
@@ 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;

M src/main/java/eu/siacs/conversations/services/XmppConnectionService.java => src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +8 -0
@@ 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();
                                            }
                                        }
									}
								}
							}

M src/main/java/eu/siacs/conversations/ui/ConversationFragment.java => src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +5 -5
@@ 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");