Implementation of XEP-084 which loads avatar from http.
authorDa Risk <darisk972@gmail.com>
Sun, 12 Sep 2010 01:07:36 +0200
changeset 799 b2a796654230
parent 796 e85bccf2d817
child 800 1eefc5da624a
Implementation of XEP-084 which loads avatar from http. Big hacking on the PEP* classes of Smack. Still need to commit the patch. Maybe this should be port to use she pubsub packages.
libs/asmack-android-7-beem.jar
src/com/beem/project/beem/BeemService.java
src/com/beem/project/beem/service/XmppConnectionAdapter.java
src/com/beem/project/beem/smack/AvatarMetadataExtension.java
src/com/beem/project/beem/smack/AvatarMetadataProvider.java
src/com/beem/project/beem/smack/avatar/AvatarCache.java
src/com/beem/project/beem/smack/avatar/AvatarManager.java
src/com/beem/project/beem/smack/avatar/AvatarRetriever.java
src/com/beem/project/beem/smack/avatar/AvatarRetrieverFactory.java
src/com/beem/project/beem/smack/avatar/FileAvatarCache.java
src/com/beem/project/beem/smack/avatar/HttpAvatarRetriever.java
src/com/beem/project/beem/smack/avatar/XmppAvatarRetriever.java
Binary file libs/asmack-android-7-beem.jar has changed
--- a/src/com/beem/project/beem/BeemService.java	Tue Aug 10 20:36:16 2010 +0200
+++ b/src/com/beem/project/beem/BeemService.java	Sun Sep 12 01:07:36 2010 +0200
@@ -57,6 +57,10 @@
 import org.jivesoftware.smack.proxy.ProxyInfo;
 import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType;
 import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smackx.pubsub.provider.PubSubProvider;
+import org.jivesoftware.smackx.pubsub.provider.ItemsProvider;
+import org.jivesoftware.smackx.pubsub.provider.ItemProvider;
+import org.jivesoftware.smackx.provider.PEPProvider;
 
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -80,6 +84,7 @@
 import com.beem.project.beem.utils.BeemBroadcastReceiver;
 import com.beem.project.beem.utils.BeemConnectivity;
 import com.beem.project.beem.utils.Status;
+import com.beem.project.beem.smack.AvatarMetadataProvider;
 import com.beem.project.beem.smack.caps.CapsProvider;
 
 /**
@@ -148,7 +153,7 @@
 	    || mSettings.getBoolean("settings_key_gmail", false)) {
 	    mConnectionConfiguration.setSecurityMode(SecurityMode.required);
 	}
-	mConnectionConfiguration.setDebuggerEnabled(false);
+	mConnectionConfiguration.setDebuggerEnabled(true);
 	mConnectionConfiguration.setSendPresence(true);
 	// maybe not the universal path, but it works on most devices (Samsung Galaxy, Google Nexus One)
 	mConnectionConfiguration.setTruststoreType("BKS");
@@ -317,6 +322,12 @@
      * @param pm The ProviderManager.
      */
     private void configure(ProviderManager pm) {
+	Log.d(TAG, "configure");
+	// Service Discovery # Items
+	pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
+	// Service Discovery # Info
+	pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());
+
 	// Privacy
 	pm.addIQProvider("query", "jabber:iq:privacy", new PrivacyProvider());
 	// Delayed Delivery only the new version
@@ -335,7 +346,24 @@
 	pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", chatState);
 	pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", chatState);
 	pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", chatState);
+	// capabilities
 	pm.addExtensionProvider("c", "http://jabber.org/protocol/caps", new CapsProvider());
+	//PEP
+//         pm.addIQProvider("pubsub", "http://jabber.org/protocol/pubsub", new PubSubProvider());
+//         pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider());
+//         pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider());
+//         pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub", new ItemProvider());
+
+	PEPProvider pep  = new PEPProvider();
+	AvatarMetadataProvider avaMeta  = new AvatarMetadataProvider();
+	pep.registerPEPParserExtension("urn:xmpp:avatar:metadata", avaMeta);
+	pm.addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", pep);
+//         pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub#event", new ItemsProvider());
+//         pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub#event", new ItemProvider());
+	//PEP avatar
+//         pm.addExtensionProvider("metadata", "urn:xmpp:avatar:metadata", new AvatarMetadataProvider());
+
+
 	/*
 	// Private Data Storage
 	pm.addIQProvider("query", "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider());
--- a/src/com/beem/project/beem/service/XmppConnectionAdapter.java	Tue Aug 10 20:36:16 2010 +0200
+++ b/src/com/beem/project/beem/service/XmppConnectionAdapter.java	Sun Sep 12 01:07:36 2010 +0200
@@ -52,9 +52,14 @@
 import org.jivesoftware.smack.XMPPException;
 import org.jivesoftware.smack.filter.PacketFilter;
 import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.*;
+import org.jivesoftware.smack.filter.*;
+import org.jivesoftware.smack.packet.*;
 import org.jivesoftware.smack.packet.Presence;
 import org.jivesoftware.smackx.ChatStateManager;
 import org.jivesoftware.smackx.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.pubsub.PayloadItem;
 
 import android.app.Notification;
 import android.app.PendingIntent;
@@ -63,8 +68,12 @@
 import android.content.SharedPreferences;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.Environment;
 import android.util.Log;
 
+import java.util.Iterator;
+import java.io.File;
+
 import com.beem.project.beem.BeemService;
 import com.beem.project.beem.R;
 import com.beem.project.beem.BeemApplication;
@@ -76,6 +85,10 @@
 import com.beem.project.beem.ui.Subscription;
 import com.beem.project.beem.utils.BeemBroadcastReceiver;
 import com.beem.project.beem.utils.Status;
+import com.beem.project.beem.smack.AvatarMetadataExtension;
+import com.beem.project.beem.smack.avatar.AvatarCache;
+import com.beem.project.beem.smack.avatar.FileAvatarCache;
+import com.beem.project.beem.smack.avatar.AvatarManager;
 
 /**
  * This class implements an adapter for XMPPConnection.
@@ -229,6 +242,7 @@
 	    mPrivacyListManager = new PrivacyListManagerAdapter(PrivacyListManager.getInstanceFor(mAdaptee));
 	    mService.resetStatus();
 	    mService.initJingle(mAdaptee);
+	    discoverServerFeatures();
 
 	    mApplication.setConnected(true);
 	    changeStatus(Status.CONTACT_STATUS_AVAILABLE, mService.getServicePreference().getString("status_text", ""));
@@ -240,6 +254,36 @@
 	}
     }
 
+    private void makeNoise()
+    {
+	Notification notif = new Notification(android.R.drawable.stat_notify_more, mService.getString(
+		    R.string.AcceptContactRequest, "NOISE"), System.currentTimeMillis());
+	notif.flags = Notification.FLAG_AUTO_CANCEL;
+	Intent intent = new Intent(mService, Subscription.class);
+	intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+	notif.setLatestEventInfo(mService, "NOISE", mService
+		.getString(R.string.AcceptContactRequestFrom, "noise"), PendingIntent.getActivity(mService, 0,
+		    intent, PendingIntent.FLAG_ONE_SHOT));
+	int id = 42;
+	mService.sendNotification(id, notif);
+    }
+
+    private void testPEP() {
+	Log.d(TAG, "TEST PEP activate");
+
+	AvatarMetadataExtension meta = new AvatarMetadataExtension(null);
+	AvatarMetadataExtension.Info info = new AvatarMetadataExtension.Info();
+	info.id = "test";
+	info.setUrl("http://elyzion.net/img.png");
+	info.bytes = 5;
+	info.type="image/png";
+	meta.addInfo(info);
+	PayloadItem<AvatarMetadataExtension> item = new PayloadItem<AvatarMetadataExtension>("test", meta);
+//         pmgr.publish(item, "urn:xmpp:avatar:data");
+	
+	Log.d(TAG, "END TEST PEP ");
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -392,12 +436,39 @@
 	ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee);
 	if (sdm == null)
 	    sdm = new ServiceDiscoveryManager(mAdaptee);
+
 	sdm.addFeature("http://jabber.org/protocol/disco#info");
 	sdm.addFeature("jabber:iq:privacy");
 	sdm.addFeature("http://jabber.org/protocol/caps");
+	sdm.addFeature("urn:xmpp:avatar:metadata");
+	sdm.addFeature("urn:xmpp:avatar:metadata+notify");
+	sdm.addFeature("urn:xmpp:avatar:data");
+	sdm.addFeature("http://jabber.org/protocol/nick");
+	sdm.addFeature("http://jabber.org/protocol/nick+notify");
+
 	mChatStateManager = ChatStateManager.getInstance(mAdaptee);
 	BeemCapsManager caps = new BeemCapsManager(sdm, mAdaptee, mService);
 	caps.setNode("http://www.beem-project.com");
+
+    }
+
+    private void discoverServerFeatures() {
+	try {
+	    // jid et server
+	    ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee);
+	    DiscoverInfo info = sdm.discoverInfo("elyzion.net");
+	    Iterator<DiscoverInfo.Identity> it = info.getIdentities();
+	    while (it.hasNext()) {
+		DiscoverInfo.Identity identity = it.next();
+		if ("pubsub".equals(identity.getCategory()) && "pep".equals(identity.getType())) {
+		    initPEP();
+		    break;
+		}
+	    }
+	} catch (XMPPException e) {
+	    // No Pep
+	}
+    
     }
 
     /**
@@ -441,6 +512,19 @@
 	return mErrorMsg;
     }
 
+    private void initPEP() {
+		// Enable pep sending
+		// API 8
+		// mService.getExternalCacheDir()
+		File cacheDir = Environment.getExternalStorageDirectory();
+		cacheDir = new File(cacheDir,"/Android/data/com.beem.project.beem/cache/");
+		AvatarCache avatarCache = new FileAvatarCache(cacheDir);
+		new AvatarManager(mAdaptee, avatarCache, true);
+		testPEP();
+		System.err.println("TEST PEP");
+    
+    }
+
     /**
      * Listener for XMPP connection events. It will calls the remote listeners for connection events.
      * @author darisk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/AvatarMetadataExtension.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,76 @@
+
+package com.beem.project.beem.smack;
+
+import java.util.List;
+import java.util.LinkedList;
+import org.jivesoftware.smackx.packet.PEPItem;
+
+public class AvatarMetadataExtension extends PEPItem {
+    List<Info> infos = new LinkedList<Info>();
+
+    public AvatarMetadataExtension(String id) {
+	super(id);
+    }
+
+    public List<Info> getInfos() {
+	return infos;
+    }
+
+    public void addInfo(Info info) {
+	infos.add(info);
+    }
+
+    @Override
+    protected String getNode() {
+	return "urn:xmpp:avatar:metadata";
+    }
+
+    @Override
+    protected String getItemDetailsXML() {
+	StringBuilder builder = new StringBuilder("<metadata xmlns=\"");
+	builder.append(getNode()).append("\">");
+	for (Info info : infos) {
+	    builder.append(info.toXML());
+	}
+	builder.append("</metadata>");
+
+	return builder.toString();
+    }
+
+    public static class Info {
+	 public int bytes;
+	 public int height;
+	 public String id;
+	 public String type;
+	 private String url;
+	 public int width;
+
+	 public void setUrl(String url) {
+	    this.url = url;
+	 }
+
+	 public String getUrl() {
+	    return url;
+	 }
+
+	 public String getId() {
+	    return id;
+	 }
+
+	 public String toXML() {
+	     StringBuilder builder = new StringBuilder("<info ");
+	     builder.append("bytes=\"" + bytes + "\"");
+	     builder.append(" id=\"" + id + "\"");
+	     builder.append(" type=\"" + type + "\"");
+
+	     if (height > 0)
+		 builder.append(" height=\"" + height + "\"");
+	     if (width > 0)
+		 builder.append(" width=\"" + width + "\"");
+	     if (url != null)
+		 builder.append(" url=\"" + url + "\"");
+	     builder.append(" />");
+	     return builder.toString();
+	 }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/AvatarMetadataProvider.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,52 @@
+
+package com.beem.project.beem.smack;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.xmlpull.v1.XmlPullParser;
+import android.util.Log;
+
+public class AvatarMetadataProvider implements PacketExtensionProvider {
+
+    /**
+     * Creates a new AvatarMetadataProvider.
+     * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor
+     */
+    public AvatarMetadataProvider() {
+    }
+
+    @Override
+    public PacketExtension parseExtension(XmlPullParser parser)
+	throws Exception {
+	Log.e("PROVIDER", "begin parsing");
+	// TODO add id
+	AvatarMetadataExtension metadata = null;
+	boolean done = false;
+	StringBuilder buffer = new StringBuilder();
+	while (!done) {
+	    int eventType = parser.next();
+	    if (eventType == XmlPullParser.START_TAG) {
+		if (parser.getName().equals("item")){
+		    String id = parser.getAttributeValue(null, "id");
+		    metadata = new AvatarMetadataExtension(id);
+		} else if (parser.getName().equals("info")) {
+		    AvatarMetadataExtension.Info info = new AvatarMetadataExtension.Info();
+		    info.bytes = Integer.parseInt(parser.getAttributeValue(null, "bytes"));
+		    info.height = Integer.parseInt(parser.getAttributeValue(null, "height"));
+		    info.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
+		    info.id = parser.getAttributeValue(null, "id");
+		    info.type = parser.getAttributeValue(null, "type");
+		    info.setUrl(parser.getAttributeValue(null, "url"));
+		    Log.e("PROVIDER", "add info");
+		    metadata.addInfo(info);
+		}
+	    } else if (eventType == XmlPullParser.END_TAG) {
+		    if (parser.getName().equals(metadata.getElementName())) {
+			done = true;
+		    }
+	    }
+	}
+	Log.e("PROVIDER", "end parsing");
+	return metadata;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/avatar/AvatarCache.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,13 @@
+package com.beem.project.beem.smack.avatar;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public interface AvatarCache {
+
+    void put(String id, byte[] data) throws IOException;
+    void put(String id, InputStream data) throws IOException;
+
+    byte[] get(String id) throws IOException;
+    boolean contains(String id);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/avatar/AvatarManager.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,74 @@
+
+package com.beem.project.beem.smack.avatar;
+
+import android.util.Log;
+
+import com.beem.project.beem.smack.AvatarMetadataExtension;
+import com.beem.project.beem.smack.AvatarMetadataExtension.Info;
+
+import java.io.IOException;
+
+import java.util.List;
+
+import org.jivesoftware.smackx.packet.PEPEvent;
+import org.jivesoftware.smackx.packet.PEPItem;
+import org.jivesoftware.smackx.PEPListener;
+import org.jivesoftware.smackx.PEPManager;
+import org.jivesoftware.smack.XMPPConnection;
+
+public class AvatarManager {
+
+    private PEPManager mPep;
+    private XMPPConnection mCon;
+    private boolean mAutoDownload;
+    private AvatarCache mCache;
+
+    public AvatarManager(XMPPConnection con, AvatarCache cache, boolean autoDownload) {
+	Log.d("AvatarMgr", "creation");
+	mCon = con;
+	mPep = new PEPManager(mCon);
+	mAutoDownload = autoDownload;
+	mCache = cache;
+	if (mAutoDownload)
+	    mPep.addPEPListener(new Listener());
+    }
+
+    public byte[] getAvatar(String avatarId) {
+	try {
+	    return mCache.get(avatarId);
+	} catch (IOException e) {
+	    return null;
+	}
+    }
+
+    protected Info selectAvatar(List<Info> available) {
+	return available.get(0);
+    }
+
+    private void downloadAvatar(String from, Info info) {
+	try {
+	    AvatarRetriever retriever = AvatarRetrieverFactory.getRetriever(mCon, from, info);
+	    byte[] avatar = retriever.getAvatar();
+	// TODO verifier le hash avant de stocker ?
+	    mCache.put(info.getId(), avatar);
+	} catch (IOException e) {
+	    Log.d("AvatarMgr", "Error while downloading avatar", e);
+	}
+    }
+
+    private class Listener implements PEPListener {
+	@Override
+	public void eventReceived(String from, PEPEvent event) {
+
+	    PEPItem item = event.getPEPItem();
+	    Log.d("AvatarMgr", "Received pep event " + item.toXML());
+	    if (item instanceof AvatarMetadataExtension) {
+		AvatarMetadataExtension ext = (AvatarMetadataExtension) item;
+		Info info = selectAvatar(ext.getInfos());
+		if (!mCache.contains(info.getId()))
+		    downloadAvatar(from, info);
+	    }
+	}
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/avatar/AvatarRetriever.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,8 @@
+
+package com.beem.project.beem.smack.avatar;
+
+import java.io.IOException;
+
+public interface AvatarRetriever {
+    public byte[] getAvatar() throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/avatar/AvatarRetrieverFactory.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,16 @@
+
+package com.beem.project.beem.smack.avatar;
+
+import com.beem.project.beem.smack.AvatarMetadataExtension.Info;
+import org.jivesoftware.smack.XMPPConnection;
+
+public class AvatarRetrieverFactory {
+
+    public static AvatarRetriever getRetriever(XMPPConnection con, String from, Info info) {
+	String url = info.getUrl();
+	if (url != null) {
+	    return new HttpAvatarRetriever(url);
+	}
+	return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/avatar/FileAvatarCache.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,71 @@
+package com.beem.project.beem.smack.avatar;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class FileAvatarCache implements AvatarCache {
+
+    private File mStoreDir;
+
+    public FileAvatarCache(File storedir) {
+	if (storedir.exists() && !storedir.isDirectory())
+	    throw new IllegalArgumentException("The store directory must be a directory");
+	mStoreDir = storedir;
+	mStoreDir.mkdirs();
+    }
+
+    @Override
+    public void put(String key, byte[] data) throws IOException {
+	File f = new File(mStoreDir, key);
+	OutputStream os = new BufferedOutputStream(new FileOutputStream(f));
+	try {
+	    os.write(data);
+	} finally {
+	    os.close();
+	}
+    }
+
+    @Override
+    public void put(String key, InputStream in) throws IOException {
+	File f = new File(mStoreDir, key);
+	OutputStream os = new BufferedOutputStream(new FileOutputStream(f));
+	try {
+	    byte[] data = new byte[1024];
+	    int nbread;
+	    while ((nbread = in.read(data)) != -1)
+		    os.write(data, 0, nbread);
+	} finally {
+	    in.close();
+	    os.close();
+	}
+    }
+
+    @Override
+    public byte[] get(String key) throws IOException {
+	File f = new File(mStoreDir, key);
+	InputStream is = new BufferedInputStream(new FileInputStream(f));
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	try {
+	    byte[] data = new byte[1024];
+	    is.read(data);
+	    bos.write(data);
+	} finally {
+	    is.close();
+	}
+	return bos.toByteArray();
+    }
+
+    @Override
+    public boolean contains(String key) {
+	File f = new File(mStoreDir, key);
+	return f.exists();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/avatar/HttpAvatarRetriever.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,41 @@
+
+package com.beem.project.beem.smack.avatar;
+
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URL;
+
+public  class HttpAvatarRetriever implements AvatarRetriever {
+
+    private URL mUrl;
+    private String mUrlString;
+
+    public HttpAvatarRetriever(URL url) {
+	mUrl = url;
+    }
+
+    public HttpAvatarRetriever(String url) {
+	mUrlString = url;
+    }
+
+    @Override
+    public byte[] getAvatar() throws IOException {
+	if (mUrl == null)
+	    mUrl = new URL(mUrlString);
+	InputStream in = mUrl.openStream();
+	ByteArrayOutputStream os = new ByteArrayOutputStream();
+	try {
+	    byte[] data = new byte[1024];
+	    int nbread;
+	    while ((nbread = in.read(data)) != -1) {
+		os.write(data, 0, nbread);
+	    }
+	} finally {
+	    in.close();
+	    os.close();
+	}
+	return os.toByteArray();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/beem/project/beem/smack/avatar/XmppAvatarRetriever.java	Sun Sep 12 01:07:36 2010 +0200
@@ -0,0 +1,41 @@
+package com.beem.project.beem.smack.avatar;
+
+import java.util.List;
+import java.util.Arrays;
+import org.jivesoftware.smack.XMPPConnection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smackx.pubsub.PubSubManager;
+import org.jivesoftware.smackx.pubsub.Node;
+import org.jivesoftware.smackx.pubsub.LeafNode;
+import org.jivesoftware.smackx.pubsub.Item;
+
+public class XmppAvatarRetriever implements AvatarRetriever {
+
+    private PubSubManager mPubsub;
+    private String mFrom;
+    private String mId;
+    private static String AVATARDATANODE = "urn:xmpp:avatar:data";
+
+    public XmppAvatarRetriever(XMPPConnection con, String from, String id) {
+	mPubsub = new PubSubManager(con, from);
+	mFrom = from;
+	mId = id;
+    }
+
+    @Override
+    public byte[] getAvatar() {
+	try {
+	    Node node = mPubsub.getNode(AVATARDATANODE);
+	    if (node instanceof LeafNode) {
+		LeafNode lnode = (LeafNode) node;
+		List<Item> items = lnode.getItems(Arrays.asList(mId));
+		// TODO la suite
+	    }
+	
+	} catch (XMPPException e) {
+	    return null;
+	}
+		return null;
+    }
+
+}