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.
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;
+ }
+
+}