r/o support for vcard avatars. pep avatars will be prefered

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Contact.java               | 26 
src/main/java/eu/siacs/conversations/generator/IqGenerator.java          |  9 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |  2 
src/main/java/eu/siacs/conversations/parser/PresenceParser.java          | 15 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java | 53 
src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java                | 50 
6 files changed, 127 insertions(+), 28 deletions(-)

Detailed changes

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

@@ -15,6 +15,7 @@ import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.pep.Avatar;
 
 public class Contact implements ListItem, Blockable {
 	public static final String TABLENAME = "contacts";
@@ -40,11 +41,11 @@ public class Contact implements ListItem, Blockable {
 	protected int subscription = 0;
 	protected String systemAccount;
 	protected String photoUri;
-	protected String avatar;
 	protected JSONObject keys = new JSONObject();
 	protected JSONArray groups = new JSONArray();
 	protected Presences presences = new Presences();
 	protected Account account;
+	protected Avatar avatar;
 
 	public Contact(final String account, final String systemName, final String serverName,
 			final Jid jid, final int subscription, final String photoUri,
@@ -61,7 +62,11 @@ public class Contact implements ListItem, Blockable {
 		} catch (JSONException e) {
 			this.keys = new JSONObject();
 		}
-		this.avatar = avatar;
+		if (avatar != null) {
+			this.avatar = new Avatar();
+			this.avatar.sha1sum = avatar;
+			this.avatar.origin = Avatar.Origin.VCARD; //always assume worst
+		}
 		try {
 			this.groups = (groups == null ? new JSONArray() : new JSONArray(groups));
 		} catch (JSONException e) {
@@ -187,7 +192,7 @@ public class Contact implements ListItem, Blockable {
 		values.put(SYSTEMACCOUNT, systemAccount);
 		values.put(PHOTOURI, photoUri);
 		values.put(KEYS, keys.toString());
-		values.put(AVATAR, avatar);
+		values.put(AVATAR, avatar == null ? null : avatar.getFilename());
 		values.put(LAST_PRESENCE, lastseen.presence);
 		values.put(LAST_TIME, lastseen.time);
 		values.put(GROUPS, groups.toString());
@@ -411,17 +416,20 @@ public class Contact implements ListItem, Blockable {
 		return getJid().toDomainJid();
 	}
 
-	public boolean setAvatar(String filename) {
-		if (this.avatar != null && this.avatar.equals(filename)) {
+	public boolean setAvatar(Avatar avatar) {
+		if (this.avatar != null && this.avatar.equals(avatar)) {
 			return false;
 		} else {
-			this.avatar = filename;
+			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
+				return false;
+			}
+			this.avatar = avatar;
 			return true;
 		}
 	}
 
 	public String getAvatar() {
-		return this.avatar;
+		return avatar == null ? null : avatar.getFilename();
 	}
 
 	public boolean deleteOtrFingerprint(String fingerprint) {
@@ -478,6 +486,10 @@ public class Contact implements ListItem, Blockable {
 		}
 	}
 
+	public boolean isSelf() {
+		return account.getJid().toBareJid().equals(getJid().toBareJid());
+	}
+
 	public static class Lastseen {
 		public long time;
 		public String presence;

src/main/java/eu/siacs/conversations/generator/IqGenerator.java 🔗

@@ -91,7 +91,7 @@ public class IqGenerator extends AbstractGenerator {
 		return publish("urn:xmpp:avatar:metadata", item);
 	}
 
-	public IqPacket retrieveAvatar(final Avatar avatar) {
+	public IqPacket retrievePepAvatar(final Avatar avatar) {
 		final Element item = new Element("item");
 		item.setAttribute("id", avatar.sha1sum);
 		final IqPacket packet = retrieve("urn:xmpp:avatar:data", item);
@@ -99,6 +99,13 @@ public class IqGenerator extends AbstractGenerator {
 		return packet;
 	}
 
+	public IqPacket retrieveVcardAvatar(final Avatar avatar) {
+		final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
+		packet.setTo(avatar.owner);
+		packet.addChild("vCard","vcard-temp");
+		return packet;
+	}
+
 	public IqPacket retrieveAvatarMetaData(final Jid to) {
 		final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
 		if (to != null) {

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

@@ -494,7 +494,7 @@ public class MessageParser extends AbstractParser implements
 					} else {
 						Contact contact = account.getRoster().getContact(
 								from);
-						contact.setAvatar(avatar.getFilename());
+						contact.setAvatar(avatar);
 						mXmppConnectionService.getAvatarService().clear(
 								contact);
 						mXmppConnectionService.updateConversationUi();

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

@@ -13,6 +13,7 @@ import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.xml.Element;
 import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
 import eu.siacs.conversations.xmpp.jid.Jid;
+import eu.siacs.conversations.xmpp.pep.Avatar;
 import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
 
 public class PresenceParser extends AbstractParser implements
@@ -101,6 +102,20 @@ public class PresenceParser extends AbstractParser implements
 		if (nick != null) {
 			contact.setPresenceName(nick.getContent());
 		}
+		Element x = packet.findChild("x","vcard-temp:x:update");
+		Avatar avatar = Avatar.parsePresence(x);
+		if (avatar != null && !contact.isSelf()) {
+			avatar.owner = from.toBareJid();
+			if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
+				if (contact.setAvatar(avatar)) {
+					mXmppConnectionService.getAvatarService().clear(contact);
+					mXmppConnectionService.updateConversationUi();
+					mXmppConnectionService.updateRosterUi();
+				}
+			} else {
+				mXmppConnectionService.fetchAvatar(account,avatar);
+			}
+		}
 		mXmppConnectionService.updateRosterUi();
 	}
 

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

@@ -1893,9 +1893,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		fetchAvatar(account, avatar, null);
 	}
 
-	public void fetchAvatar(Account account, final Avatar avatar,
-							final UiCallback<Avatar> callback) {
-		IqPacket packet = this.mIqGenerator.retrieveAvatar(avatar);
+	public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+		switch (avatar.origin) {
+			case PEP:
+				fetchAvatarPep(account,avatar,callback);
+				break;
+			case VCARD:
+				fetchAvatarVcard(account, avatar, callback);
+				break;
+		}
+	}
+
+	private void fetchAvatarPep(Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+		IqPacket packet = this.mIqGenerator.retrievePepAvatar(avatar);
 		sendIqPacket(account, packet, new OnIqPacketReceived() {
 
 			@Override
@@ -1916,7 +1926,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 							} else {
 								Contact contact = account.getRoster()
 										.getContact(avatar.owner);
-								contact.setAvatar(avatar.getFilename());
+								contact.setAvatar(avatar);
 								getAvatarService().clear(contact);
 								updateConversationUi();
 								updateRosterUi();
@@ -1925,8 +1935,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 								callback.success(avatar);
 							}
 							Log.d(Config.LOGTAG, account.getJid().toBareJid()
-									+ ": succesfully fetched avatar for "
-									+ avatar.owner);
+									+ ": succesfuly fetched pep avatar for " + avatar.owner);
 							return;
 						}
 					} else {
@@ -1949,8 +1958,34 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 		});
 	}
 
-	public void checkForAvatar(Account account,
-							   final UiCallback<Avatar> callback) {
+	private void fetchAvatarVcard(final Account account, final Avatar avatar, final UiCallback<Avatar> callback) {
+		IqPacket packet = this.mIqGenerator.retrieveVcardAvatar(avatar);
+		this.sendIqPacket(account,packet,new OnIqPacketReceived() {
+			@Override
+			public void onIqPacketReceived(Account account, IqPacket packet) {
+				if (packet.getType() == IqPacket.TYPE.RESULT) {
+					Element vCard = packet.findChild("vCard","vcard-temp");
+					Element photo = vCard != null ? vCard.findChild("PHOTO") : null;
+					Element binval = photo != null ? photo.findChild("BINVAL") : null;
+					if (binval != null) {
+						avatar.image = binval.getContent();
+						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();
+						}
+					}
+				}
+			}
+		});
+	}
+
+	public void checkForAvatar(Account account, final UiCallback<Avatar> callback) {
 		IqPacket packet = this.mIqGenerator.retrieveAvatarMetaData(null);
 		this.sendIqPacket(account, packet, new OnIqPacketReceived() {
 
@@ -1972,7 +2007,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
 									getAvatarService().clear(account);
 									callback.success(avatar);
 								} else {
-									fetchAvatar(account, avatar, callback);
+									fetchAvatarPep(account, avatar, callback);
 								}
 								return;
 							}

src/main/java/eu/siacs/conversations/xmpp/pep/Avatar.java 🔗

@@ -6,6 +6,9 @@ import eu.siacs.conversations.xmpp.jid.Jid;
 import android.util.Base64;
 
 public class Avatar {
+
+	public enum Origin { PEP, VCARD };
+
 	public String type;
 	public String sha1sum;
 	public String image;
@@ -13,21 +16,14 @@ public class Avatar {
 	public int width;
 	public long size;
 	public Jid owner;
+	public Origin origin = Origin.PEP; //default to maintain compat
 
 	public byte[] getImageAsBytes() {
 		return Base64.decode(image, Base64.DEFAULT);
 	}
 
 	public String getFilename() {
-		if (type == null) {
-			return sha1sum;
-		} else if (type.equalsIgnoreCase("image/webp")) {
-			return sha1sum + ".webp";
-		} else if (type.equalsIgnoreCase("image/png")) {
-			return sha1sum + ".png";
-		} else {
-			return sha1sum;
-		}
+		return sha1sum;
 	}
 
 	public static Avatar parseMetadata(Element items) {
@@ -64,10 +60,44 @@ public class Avatar {
 					return null;
 				}
 				avatar.type = child.getAttribute("type");
-				avatar.sha1sum = child.getAttribute("id");
+				String hash = child.getAttribute("id");
+				if (!isValidSHA1(hash)) {
+					return null;
+				}
+				avatar.sha1sum = hash;
+				avatar.origin = Origin.PEP;
 				return avatar;
 			}
 		}
 		return null;
 	}
+
+	@Override
+	public boolean equals(Object object) {
+		if (object != null && object instanceof Avatar) {
+			Avatar other = (Avatar) object;
+			return other.getFilename().equals(this.getFilename());
+		} else {
+			return false;
+		}
+	}
+
+	public static Avatar parsePresence(Element x) {
+		Element photo = x != null ? x.findChild("photo") : null;
+		String hash = photo != null ? photo.getContent() : null;
+		if (hash == null) {
+			return null;
+		}
+		if (!isValidSHA1(hash)) {
+			return null;
+		}
+		Avatar avatar = new Avatar();
+		avatar.sha1sum = hash;
+		avatar.origin = Origin.VCARD;
+		return avatar;
+	}
+
+	private static boolean isValidSHA1(String s) {
+		return s != null && s.matches("[a-fA-F0-9]{40}");
+	}
 }