diff options
Diffstat (limited to 'src')
6 files changed, 295 insertions, 181 deletions
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 13a5bb9f3..412f984fa 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -4,20 +4,25 @@ import android.annotation.SuppressLint; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.PgpEngine; -import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; -import eu.siacs.conversations.xmpp.stanzas.PresencePacket; +import eu.siacs.conversations.xmpp.pep.Avatar; @SuppressLint("DefaultLocale") public class MucOptions { + public Account getAccount() { + return this.conversation.getAccount(); + } + + public void setSelf(User user) { + this.self = user; + } + public enum Affiliation { OWNER("owner", 4, R.string.owner), ADMIN("admin", 3, R.string.admin), @@ -100,28 +105,31 @@ public class MucOptions { public static final String STATUS_CODE_LOST_MEMBERSHIP = "321"; private interface OnEventListener { - public void onSuccess(); + void onSuccess(); - public void onFailure(); + void onFailure(); } public interface OnRenameListener extends OnEventListener { } - public class User { + public static class User { private Role role = Role.NONE; private Affiliation affiliation = Affiliation.NONE; - private String name; private Jid jid; + private Jid fullJid; private long pgpKeyId = 0; + private Avatar avatar; + private MucOptions options; - public String getName() { - return name; + public User(MucOptions options, Jid from) { + this.options = options; + this.fullJid = from; } - public void setName(String user) { - this.name = user; + public String getName() { + return this.fullJid.getResourcepart(); } public void setJid(Jid jid) { @@ -162,7 +170,7 @@ public class MucOptions { return false; } else { User o = (User) other; - return name != null && name.equals(o.name) + return getName() != null && getName().equals(o.getName()) && jid != null && jid.equals(o.jid) && affiliation == o.affiliation && role == o.role; @@ -202,26 +210,43 @@ public class MucOptions { } public Contact getContact() { - return account.getRoster().getContactFromRoster(getJid()); + return getAccount().getRoster().getContactFromRoster(getJid()); + } + + public void setAvatar(Avatar avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar == null ? null : avatar.getFilename(); + } + + public Account getAccount() { + return options.getAccount(); + } + + public Jid getFullJid() { + return fullJid; } } private Account account; - private List<User> users = new CopyOnWriteArrayList<>(); + private final List<User> users = new ArrayList<>(); private List<String> features = new ArrayList<>(); private Data form = new Data(); private Conversation conversation; private boolean isOnline = false; private int error = ERROR_UNKNOWN; - private OnRenameListener onRenameListener = null; - private User self = new User(); + public OnRenameListener onRenameListener = null; + private User self; private String subject = null; private String password = null; - private boolean mNickChangingInProgress = false; + public boolean mNickChangingInProgress = false; public MucOptions(Conversation conversation) { this.account = conversation.getAccount(); this.conversation = conversation; + this.self = new User(this,conversation.getJid()); } public void updateFeatures(ArrayList<String> features) { @@ -273,135 +298,67 @@ public class MucOptions { } public void deleteUser(String name) { - for (int i = 0; i < users.size(); ++i) { - if (users.get(i).getName().equals(name)) { - users.remove(i); - return; + synchronized (this.users) { + for (int i = 0; i < users.size(); ++i) { + if (users.get(i).getName().equals(name)) { + users.remove(i); + return; + } } } } public void addUser(User user) { - for (int i = 0; i < users.size(); ++i) { - if (users.get(i).getName().equals(user.getName())) { - users.set(i, user); - return; + synchronized (this.users) { + for (int i = 0; i < users.size(); ++i) { + if (users.get(i).getName().equals(user.getName())) { + users.set(i, user); + return; + } } + users.add(user); } - users.add(user); } - public boolean isUserInRoom(String name) { - for (int i = 0; i < users.size(); ++i) { - if (users.get(i).getName().equals(name)) { - return true; - } + public User findUser(String name) { + if (name == null) { + return null; } - return false; - } - - public void processPacket(PresencePacket packet, PgpEngine pgp) { - final Jid from = packet.getFrom(); - if (!from.isBareJid()) { - final String name = from.getResourcepart(); - final String type = packet.getAttribute("type"); - final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); - final List<String> codes = getStatusCodes(x); - if (type == null) { - User user = new User(); - if (x != null) { - Element item = x.findChild("item"); - if (item != null && name != null) { - user.setName(name); - user.setAffiliation(item.getAttribute("affiliation")); - user.setRole(item.getAttribute("role")); - user.setJid(item.getAttributeAsJid("jid")); - if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) { - this.isOnline = true; - this.error = ERROR_NO_ERROR; - self = user; - if (mNickChangingInProgress) { - if (onRenameListener != null) { - onRenameListener.onSuccess(); - } - mNickChangingInProgress = false; - } - } else { - addUser(user); - } - if (pgp != null) { - Element signed = packet.findChild("x", "jabber:x:signed"); - if (signed != null) { - Element status = packet.findChild("status"); - String msg = status == null ? "" : status.getContent(); - long keyId = pgp.fetchKeyId(account, msg, signed.getContent()); - if (keyId != 0) { - user.setPgpKeyId(keyId); - } - } - } - } - } - } else if (type.equals("unavailable")) { - if (codes.contains(STATUS_CODE_SELF_PRESENCE) || - packet.getFrom().equals(this.conversation.getJid())) { - if (codes.contains(STATUS_CODE_CHANGED_NICK)) { - this.mNickChangingInProgress = true; - } else if (codes.contains(STATUS_CODE_KICKED)) { - setError(KICKED_FROM_ROOM); - } else if (codes.contains(STATUS_CODE_BANNED)) { - setError(ERROR_BANNED); - } else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) { - setError(ERROR_MEMBERS_ONLY); - } else { - setError(ERROR_UNKNOWN); - } - } else { - deleteUser(name); - } - } else if (type.equals("error")) { - Element error = packet.findChild("error"); - if (error != null && error.hasChild("conflict")) { - if (isOnline) { - if (onRenameListener != null) { - onRenameListener.onFailure(); - } - } else { - setError(ERROR_NICK_IN_USE); - } - } else if (error != null && error.hasChild("not-authorized")) { - setError(ERROR_PASSWORD_REQUIRED); - } else if (error != null && error.hasChild("forbidden")) { - setError(ERROR_BANNED); - } else if (error != null && error.hasChild("registration-required")) { - setError(ERROR_MEMBERS_ONLY); + synchronized (this.users) { + for (User user : users) { + if (user.getName().equals(name)) { + return user; } } } + return null; } - private void setError(int error) { - this.isOnline = false; + public boolean isUserInRoom(String name) { + return findUser(name) != null; + } + + public void setError(int error) { + this.isOnline = error == ERROR_NO_ERROR; this.error = error; } - private List<String> getStatusCodes(Element x) { - List<String> codes = new ArrayList<>(); - if (x != null) { - for (Element child : x.getChildren()) { - if (child.getName().equals("status")) { - String code = child.getAttribute("code"); - if (code != null) { - codes.add(code); - } - } - } + public ArrayList<User> getUsers() { + synchronized (this.users) { + return new ArrayList(this.users); } - return codes; } - public List<User> getUsers() { - return this.users; + public List<User> getUsers(int max) { + synchronized (this.users) { + return new ArrayList<>(users.subList(0,Math.min(users.size(),5))); + } + } + + public int getUserCount() { + synchronized (this.users) { + return this.users.size(); + } } public String getProposedNick() { @@ -455,34 +412,38 @@ public class MucOptions { } public String createNameFromParticipants() { - if (users.size() >= 2) { - List<String> names = new ArrayList<String>(); - for (User user : users) { - Contact contact = user.getContact(); - if (contact != null && !contact.getDisplayName().isEmpty()) { - names.add(contact.getDisplayName().split("\\s+")[0]); - } else { - names.add(user.getName()); + synchronized (this.users) { + if (users.size() >= 2) { + List<String> names = new ArrayList<String>(); + for (User user : users) { + Contact contact = user.getContact(); + if (contact != null && !contact.getDisplayName().isEmpty()) { + names.add(contact.getDisplayName().split("\\s+")[0]); + } else { + names.add(user.getName()); + } } - } - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < names.size(); ++i) { - builder.append(names.get(i)); - if (i != names.size() - 1) { - builder.append(", "); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < names.size(); ++i) { + builder.append(names.get(i)); + if (i != names.size() - 1) { + builder.append(", "); + } } + return builder.toString(); + } else { + return null; } - return builder.toString(); - } else { - return null; } } public long[] getPgpKeyIds() { List<Long> ids = new ArrayList<>(); - for (User user : getUsers()) { - if (user.getPgpKeyId() != 0) { - ids.add(user.getPgpKeyId()); + synchronized (this.users) { + for (User user : this.users) { + if (user.getPgpKeyId() != 0) { + ids.add(user.getPgpKeyId()); + } } } ids.add(account.getPgpId()); @@ -494,18 +455,22 @@ public class MucOptions { } public boolean pgpKeysInUse() { - for (User user : getUsers()) { - if (user.getPgpKeyId() != 0) { - return true; + synchronized (this.users) { + for (User user : this.users) { + if (user.getPgpKeyId() != 0) { + return true; + } } } return false; } public boolean everybodyHasKeys() { - for (User user : getUsers()) { - if (user.getPgpKeyId() == 0) { - return false; + synchronized (this.users) { + for (User user : this.users) { + if (user.getPgpKeyId() == 0) { + return false; + } } } return true; @@ -520,9 +485,11 @@ public class MucOptions { } public Jid getTrueCounterpart(String counterpart) { - for (User user : this.getUsers()) { - if (user.getName().equals(counterpart)) { - return user.getJid(); + synchronized (this.users) { + for (User user : this.users) { + if (user.getName().equals(counterpart)) { + return user.getJid(); + } } } return null; diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index b4c648fe9..3e923af1c 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -169,7 +169,7 @@ public class MessageParser extends AbstractParser implements if ("urn:xmpp:avatar:metadata".equals(node)) { Avatar avatar = Avatar.parseMetadata(items); if (avatar != null) { - avatar.owner = from; + avatar.owner = from.toBareJid(); if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { if (account.getJid().toBareJid().equals(from)) { if (account.setAvatar(avatar.getFilename())) { diff --git a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java index 59b94bc0c..f94485280 100644 --- a/src/main/java/eu/siacs/conversations/parser/PresenceParser.java +++ b/src/main/java/eu/siacs/conversations/parser/PresenceParser.java @@ -1,7 +1,11 @@ package eu.siacs.conversations.parser; +import android.util.Log; + import java.util.ArrayList; +import java.util.List; +import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.PgpEngine; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; @@ -25,19 +29,18 @@ public class PresenceParser extends AbstractParser implements } public void parseConferencePresence(PresencePacket packet, Account account) { - PgpEngine mPgpEngine = mXmppConnectionService.getPgpEngine(); final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().toBareJid()); if (conversation != null) { final MucOptions mucOptions = conversation.getMucOptions(); boolean before = mucOptions.online(); - int count = mucOptions.getUsers().size(); - final ArrayList<MucOptions.User> tileUserBefore = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5))); - mucOptions.processPacket(packet, mPgpEngine); - final ArrayList<MucOptions.User> tileUserAfter = new ArrayList<>(mucOptions.getUsers().subList(0,Math.min(mucOptions.getUsers().size(),5))); + int count = mucOptions.getUserCount(); + final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5); + processConferencePresence(packet, mucOptions); + final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5); if (!tileUserAfter.equals(tileUserBefore)) { mXmppConnectionService.getAvatarService().clear(conversation); } - if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUsers().size())) { + if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) { mXmppConnectionService.updateConversationUi(); } else if (mucOptions.online()) { mXmppConnectionService.updateMucRosterUi(); @@ -45,6 +48,108 @@ public class PresenceParser extends AbstractParser implements } } + private void processConferencePresence(PresencePacket packet, MucOptions mucOptions) { + final Jid from = packet.getFrom(); + if (!from.isBareJid()) { + final String type = packet.getAttribute("type"); + final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user"); + Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update")); + final List<String> codes = getStatusCodes(x); + if (type == null) { + if (x != null) { + Element item = x.findChild("item"); + if (item != null && !from.isBareJid()) { + MucOptions.User user = new MucOptions.User(mucOptions,from); + user.setAffiliation(item.getAttribute("affiliation")); + user.setRole(item.getAttribute("role")); + user.setJid(item.getAttributeAsJid("jid")); + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(mucOptions.getConversation().getJid())) { + mucOptions.setError(MucOptions.ERROR_NO_ERROR); + mucOptions.setSelf(user); + if (mucOptions.mNickChangingInProgress) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onSuccess(); + } + mucOptions.mNickChangingInProgress = false; + } + } else { + mucOptions.addUser(user); + } + if (mXmppConnectionService.getPgpEngine() != null) { + Element signed = packet.findChild("x", "jabber:x:signed"); + if (signed != null) { + Element status = packet.findChild("status"); + String msg = status == null ? "" : status.getContent(); + long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent()); + if (keyId != 0) { + user.setPgpKeyId(keyId); + } + } + } + if (avatar != null) { + avatar.owner = from; + if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) { + user.setAvatar(avatar); + } else { + mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar); + } + Log.d(Config.LOGTAG, "user " + avatar.owner + " has avatar"); + } + } + } + } else if (type.equals("unavailable")) { + if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || + packet.getFrom().equals(mucOptions.getConversation().getJid())) { + if (codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) { + mucOptions.mNickChangingInProgress = true; + } else if (codes.contains(MucOptions.STATUS_CODE_KICKED)) { + mucOptions.setError(MucOptions.KICKED_FROM_ROOM); + } else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) { + mucOptions.setError(MucOptions.ERROR_BANNED); + } else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) { + mucOptions.setError(MucOptions.ERROR_MEMBERS_ONLY); + } else { + mucOptions.setError(MucOptions.ERROR_UNKNOWN); + } + } else if (!from.isBareJid()){ + mucOptions.deleteUser(from.getResourcepart()); + } + } else if (type.equals("error")) { + Element error = packet.findChild("error"); + if (error != null && error.hasChild("conflict")) { + if (mucOptions.online()) { + if (mucOptions.onRenameListener != null) { + mucOptions.onRenameListener.onFailure(); + } + } else { + mucOptions.setError(MucOptions.ERROR_NICK_IN_USE); + } + } else if (error != null && error.hasChild("not-authorized")) { + mucOptions.setError(MucOptions.ERROR_PASSWORD_REQUIRED); + } else if (error != null && error.hasChild("forbidden")) { + mucOptions.setError(MucOptions.ERROR_BANNED); + } else if (error != null && error.hasChild("registration-required")) { + mucOptions.setError(MucOptions.ERROR_MEMBERS_ONLY); + } + } + } + } + + private static List<String> getStatusCodes(Element x) { + List<String> codes = new ArrayList<>(); + if (x != null) { + for (Element child : x.getChildren()) { + if (child.getName().equals("status")) { + String code = child.getAttribute("code"); + if (code != null) { + codes.add(code); + } + } + } + } + return codes; + } + public void parseContactPresence(final PresencePacket packet, final Account account) { final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); final Jid from = packet.getFrom(); diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index b658b9207..528d0259b 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -58,6 +58,22 @@ public class AvatarService { return avatar; } + public Bitmap get(final MucOptions.User user, final int size, boolean cachedOnly) { + final String KEY = key(user, size); + Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY); + if (avatar != null || cachedOnly) { + return avatar; + } + if (user.getAvatar() != null) { + avatar = mXmppConnectionService.getFileBackend().getAvatar(user.getAvatar(), size); + } + if (avatar == null) { + avatar = get(user.getName(), size, cachedOnly); + } + this.mXmppConnectionService.getBitmapCache().put(KEY, avatar); + return avatar; + } + public void clear(Contact contact) { synchronized (this.sizes) { for (Integer size : sizes) { @@ -77,6 +93,16 @@ public class AvatarService { + contact.getJid() + "_" + String.valueOf(size); } + private String key(MucOptions.User user, int size) { + synchronized (this.sizes) { + if (!this.sizes.contains(size)) { + this.sizes.add(size); + } + } + return PREFIX_CONTACT + "_" + user.getAccount().getJid().toBareJid() + "_" + + user.getFullJid() + "_" + String.valueOf(size); + } + public Bitmap get(ListItem item, int size) { return get(item,size,false); } @@ -122,7 +148,7 @@ public class AvatarService { if (bitmap != null || cachedOnly) { return bitmap; } - final List<MucOptions.User> users = new ArrayList<>(mucOptions.getUsers()); + final List<MucOptions.User> users = mucOptions.getUsers(); int count = users.size(); bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); @@ -198,15 +224,20 @@ public class AvatarService { } public Bitmap get(Message message, int size, boolean cachedOnly) { + final Conversation conversation = message.getConversation(); if (message.getStatus() == Message.STATUS_RECEIVED) { - Contact contact = message.getContact(); - if (contact != null) { - return get(contact, size, cachedOnly); - } else { - return get(UIHelper.getMessageDisplayName(message), size, cachedOnly); + Contact c = message.getContact(); + if (c != null && (c.getProfilePhoto() != null || c.getAvatar() != null)) { + return get(c, size, cachedOnly); + } else if (message.getConversation().getMode() == Conversation.MODE_MULTI){ + MucOptions.User user = conversation.getMucOptions().findUser(message.getCounterpart().getResourcepart()); + if (user != null) { + return get(user,size,cachedOnly); + } } + return get(UIHelper.getMessageDisplayName(message), size, cachedOnly); } else { - return get(message.getConversation().getAccount(), size, cachedOnly); + return get(conversation.getAccount(), size, cachedOnly); } } @@ -290,6 +321,11 @@ public class AvatarService { if (drawTile(canvas, uri, left, top, right, bottom)) { return true; } + } else if (user.getAvatar() != null) { + Uri uri = mXmppConnectionService.getFileBackend().getAvatarUri(user.getAvatar()); + if (drawTile(canvas, uri, left, top, right, bottom)) { + return true; + } } String name = contact != null ? contact.getDisplayName() : user.getName(); drawTile(canvas, name, left, top, right, bottom); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 7e6bf9921..2c140a83f 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2359,12 +2359,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (getFileBackend().save(avatar)) { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": successfully fetched vCard avatar for " + avatar.owner); - Contact contact = account.getRoster() - .getContact(avatar.owner); - contact.setAvatar(avatar); - getAvatarService().clear(contact); - updateConversationUi(); - updateRosterUi(); + if (avatar.owner.isBareJid()) { + Contact contact = account.getRoster() + .getContact(avatar.owner); + contact.setAvatar(avatar); + getAvatarService().clear(contact); + updateConversationUi(); + updateRosterUi(); + } else { + Conversation conversation = find(account,avatar.owner.toBareJid()); + if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) { + MucOptions.User user = conversation.getMucOptions().findUser(avatar.owner.getResourcepart()); + if (user != null) { + user.setAvatar(avatar); + } + } + } } } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index e78a72778..adae79168 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -494,8 +494,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); - final ArrayList<User> users = new ArrayList<>(); - users.addAll(mConversation.getMucOptions().getUsers()); + final ArrayList<User> users = mucOptions.getUsers(); Collections.sort(users,new Comparator<User>() { @Override public int compare(User lhs, User rhs) { @@ -527,20 +526,17 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers }); tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } - Bitmap bm; Contact contact = user.getContact(); if (contact != null) { - bm = avatarService().get(contact, getPixel(56)); tvDisplayName.setText(contact.getDisplayName()); tvStatus.setText(user.getName() + " \u2022 " + getStatus(user)); } else { - bm = avatarService().get(user.getName(), getPixel(56)); tvDisplayName.setText(user.getName()); tvStatus.setText(getStatus(user)); } ImageView iv = (ImageView) view.findViewById(R.id.contact_photo); - iv.setImageBitmap(bm); + iv.setImageBitmap(avatarService().get(user, getPixel(48), false)); membersView.addView(view); if (mConversation.getMucOptions().canInvite()) { mInviteButton.setVisibility(View.VISIBLE); |