# HG changeset patch # User Da Risk # Date 1284246456 -7200 # Node ID b2a796654230d9b211972e8cbca90cf2d4efda26 # Parent e85bccf2d817c52494f17b4efaaa5db02d56ca39 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. diff -r e85bccf2d817 -r b2a796654230 libs/asmack-android-7-beem.jar Binary file libs/asmack-android-7-beem.jar has changed diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/BeemService.java --- 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()); diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/service/XmppConnectionAdapter.java --- 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 item = new PayloadItem("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 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 diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/AvatarMetadataExtension.java --- /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 infos = new LinkedList(); + + public AvatarMetadataExtension(String id) { + super(id); + } + + public List 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(""); + for (Info info : infos) { + builder.append(info.toXML()); + } + builder.append(""); + + 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(" 0) + builder.append(" height=\"" + height + "\""); + if (width > 0) + builder.append(" width=\"" + width + "\""); + if (url != null) + builder.append(" url=\"" + url + "\""); + builder.append(" />"); + return builder.toString(); + } + } +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/AvatarMetadataProvider.java --- /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; + } +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/avatar/AvatarCache.java --- /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); +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/avatar/AvatarManager.java --- /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 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); + } + } + } + +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/avatar/AvatarRetriever.java --- /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; +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/avatar/AvatarRetrieverFactory.java --- /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; + } +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/avatar/FileAvatarCache.java --- /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(); + } +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/avatar/HttpAvatarRetriever.java --- /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(); + } + +} diff -r e85bccf2d817 -r b2a796654230 src/com/beem/project/beem/smack/avatar/XmppAvatarRetriever.java --- /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 items = lnode.getItems(Arrays.asList(mId)); + // TODO la suite + } + + } catch (XMPPException e) { + return null; + } + return null; + } + +}