# HG changeset patch # User Da Risk # Date 1298168195 -3600 # Node ID d23d8ad3b9ba1ee6c261250113210b229db99c49 # Parent 2236fe5b2db178d4a8e7e4fde96043ba19efb45c Add a basic way to publish avatar. diff -r 2236fe5b2db1 -r d23d8ad3b9ba res/layout/changestatus.xml --- a/res/layout/changestatus.xml Sat Mar 05 17:44:41 2011 +0100 +++ b/res/layout/changestatus.xml Sun Feb 20 03:16:35 2011 +0100 @@ -5,8 +5,21 @@ + + + + + Edit account Updating status Nothing to change + My avatar Edit your username diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/BeemService.java --- a/src/com/beem/project/beem/BeemService.java Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/BeemService.java Sun Feb 20 03:16:35 2011 +0100 @@ -45,6 +45,7 @@ import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.Roster.SubscriptionMode; @@ -132,6 +133,7 @@ * Initialize the connection. */ private void initConnectionConfig() { + SmackConfiguration.setPacketReplyTimeout(30000); mUseProxy = mSettings.getBoolean(BeemApplication.PROXY_USE_KEY, false); if (mUseProxy) { String stype = mSettings.getString(BeemApplication.PROXY_TYPE_KEY, "HTTP"); diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/service/BeemAvatarCache.java --- a/src/com/beem/project/beem/service/BeemAvatarCache.java Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/service/BeemAvatarCache.java Sun Feb 20 03:16:35 2011 +0100 @@ -125,7 +125,6 @@ @Override public boolean contains(String key) { Uri uri = AvatarProvider.CONTENT_URI.buildUpon().appendPath(key).build(); - uri = Uri.parse("content://com.beem.project.beem.providers.avatarprovider"); Cursor c = mContentResolver.query(uri, null, null, null, null); boolean res = c.getCount() > 0; c.close(); diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/service/BeemAvatarManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/beem/project/beem/service/BeemAvatarManager.java Sun Feb 20 03:16:35 2011 +0100 @@ -0,0 +1,162 @@ +/* + BEEM is a videoconference application on the Android Platform. + + Copyright (C) 2009 by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + This file is part of BEEM. + + BEEM is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + BEEM is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with BEEM. If not, see . + + Please send bug reports with examples or suggestions to + contact@beem-project.com or http://dev.beem-project.com/ + + Epitech, hereby disclaims all copyright interest in the program "Beem" + written by Frederic-Charles Barthelery, + Jean-Manuel Da Silva, + Nikita Kozlov, + Philippe Lago, + Jean Baptiste Vergely, + Vincent Veronis. + + Nicolas Sadirac, November 26, 2009 + President of Epitech. + + Flavien Astraud, November 26, 2009 + Head of the EIP Laboratory. + +*/ + +package com.beem.project.beem.service; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; + +import com.beem.project.beem.smack.avatar.AvatarManager; +import com.beem.project.beem.smack.avatar.AvatarCache; +import com.beem.project.beem.smack.avatar.AvatarMetadataExtension; + +import java.security.NoSuchAlgorithmException; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import com.beem.project.beem.smack.pep.PepSubManager; +import org.jivesoftware.smack.Connection; + +/** + * An AvatarManager for Beem. + * It allows to publish avatar on the Android platform. + */ +public class BeemAvatarManager extends AvatarManager { + private static final String TAG = BeemAvatarManager.class.getSimpleName(); + private static final int JPEG_QUALITY = 100; + + private Context mContext; + + /** + * Create a BeemAvatarManager. + * + * @param con the connection + * @param pepMgr the PepSubManager of the connection + * @param cache the cache which will store the avatars + * @param autoDownload tre to enable auto download of avatars + */ + public BeemAvatarManager(final Context ctx, final Connection con, final PepSubManager pepMgr, + final AvatarCache cache, final boolean autoDownload) { + super(con, pepMgr, cache, autoDownload); + mContext = ctx; + } + + public boolean publishAvatar(Uri avatarUri) { + try { + Bitmap bmp = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), avatarUri); + return publishAvatar(bmp); + } catch (IOException e) { + Log.d(TAG, "Error while publishing avatar " + avatarUri, e); + } + return false; + } + + /** + * Publish an avatar. + * This will send the XMPP stanza to enable the publication of an avatar. + * + * @param bitmap the avatar to publish + * @return true on success false otherwise + */ + private boolean publishAvatar(Bitmap bitmap) { + AvatarMetadataExtension meta = new AvatarMetadataExtension(); + // Probably a bug on prosody but only the last data sent is kept + // and in beem we retrieve the first info + AvatarMetadataExtension.Info jpeg = publishBitmap(bitmap, Bitmap.CompressFormat.JPEG, JPEG_QUALITY); + // The png format is mandatory for interoperability + AvatarMetadataExtension.Info png = publishBitmap(bitmap, Bitmap.CompressFormat.PNG, JPEG_QUALITY); + if (png == null) + return false; + meta.addInfo(png); + if (jpeg != null) + meta.addInfo(jpeg); + publishAvatarMetaData(png.getId(), meta); + return true; + } + + /** + * Send this bitmap to the avatar data node of the pep server. + * + * @param bmp the avatar bitmap + * @param format the image format to publish this data + * @param quality the compression quality use for JPEG compression + * @return the resulting info associate with this bitmap. null if the operation failed + */ + private AvatarMetadataExtension.Info publishBitmap(Bitmap bmp, Bitmap.CompressFormat format, int quality) { + try { + byte[] data = getBitmapByte(bmp, format, quality); + String dataid = getAvatarId(data); + if (!publishAvatarData(data)) + return null; + String mimetype = "image/png"; + if (Bitmap.CompressFormat.JPEG == format) + mimetype = "image/jpeg"; + AvatarMetadataExtension.Info info = new AvatarMetadataExtension.Info(dataid, mimetype, data.length); + info.setHeight(bmp.getHeight()); + info.setWidth(bmp.getWidth()); + return info; + } catch (NoSuchAlgorithmException ex) { + return null; + } + } + + /** + * Convert the bitmap to a byte array. + * + * @param bitmap the avatar bitmap + * @param format the resulting image format + * @param quality the compression quality use for JPEG compression + * @return the bitmap data or a array of 0 element on error + */ + private byte[] getBitmapByte(Bitmap bitmap, Bitmap.CompressFormat format, int quality) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + if (bitmap.compress(format, quality, bos)) + return bos.toByteArray(); + else + return new byte[0]; + } + +} diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/service/XmppConnectionAdapter.java --- a/src/com/beem/project/beem/service/XmppConnectionAdapter.java Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/service/XmppConnectionAdapter.java Sun Feb 20 03:16:35 2011 +0100 @@ -81,7 +81,6 @@ import com.beem.project.beem.utils.Status; import com.beem.project.beem.smack.pep.PepSubManager; import com.beem.project.beem.smack.avatar.AvatarCache; -import com.beem.project.beem.smack.avatar.AvatarManager; /** * This class implements an adapter for XMPPConnection. @@ -110,7 +109,7 @@ private ChatStateManager mChatStateManager; private final BeemService mService; private BeemApplication mApplication; - private AvatarManager mAvatarManager; + private BeemAvatarManager mAvatarManager; private PepSubManager mPepManager; private SharedPreferences mPref; private final RemoteCallbackList mRemoteConnListeners = @@ -329,7 +328,7 @@ * * @return the AvatarManager or null if there is not */ - public AvatarManager getAvatarManager() { + public BeemAvatarManager getAvatarManager() { return mAvatarManager; } @@ -502,7 +501,7 @@ // mService.getExternalCacheDir() mPepManager = new PepSubManager(mAdaptee); AvatarCache avatarCache = new BeemAvatarCache(mService); - mAvatarManager = new AvatarManager(mAdaptee, mPepManager, avatarCache, true); + mAvatarManager = new BeemAvatarManager(mService, mAdaptee, mPepManager, avatarCache, true); } /** diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/service/XmppFacade.java --- a/src/com/beem/project/beem/service/XmppFacade.java Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/service/XmppFacade.java Sun Feb 20 03:16:35 2011 +0100 @@ -45,6 +45,7 @@ import org.jivesoftware.smack.packet.Presence; +import android.net.Uri; import android.os.RemoteException; import com.beem.project.beem.service.aidl.IChatManager; @@ -53,7 +54,6 @@ import com.beem.project.beem.service.aidl.IXmppConnection; import com.beem.project.beem.service.aidl.IXmppFacade; import com.beem.project.beem.utils.PresenceType; -import com.beem.project.beem.smack.avatar.AvatarManager; /** * This class is a facade for the Beem Service. @@ -150,11 +150,18 @@ } @Override - public byte[] getAvatar(String avatarId) throws RemoteException { - AvatarManager mgr = mConnexion.getAvatarManager(); + public boolean publishAvatar(Uri avatarUri) throws RemoteException { + BeemAvatarManager mgr = mConnexion.getAvatarManager(); if (mgr == null) - return null; + return false; + + return mgr.publishAvatar(avatarUri); + } - return mgr.getAvatar(avatarId); + @Override + public void disableAvatarPublishing() throws RemoteException { + BeemAvatarManager mgr = mConnexion.getAvatarManager(); + if (mgr != null) + mgr.disableAvatarPublishing(); } } diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/service/aidl/IXmppFacade.aidl --- a/src/com/beem/project/beem/service/aidl/IXmppFacade.aidl Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/service/aidl/IXmppFacade.aidl Sun Feb 20 03:16:35 2011 +0100 @@ -48,6 +48,7 @@ import com.beem.project.beem.service.aidl.IChatManager; import com.beem.project.beem.service.aidl.IPrivacyListManager; import com.beem.project.beem.service.PresenceAdapter; +import android.net.Uri; interface IXmppFacade { @@ -96,11 +97,10 @@ */ void call(in String jid); - /** - * get the an avatar - * @param id the id of the avatar - */ - byte[] getAvatar(in String id); + boolean publishAvatar(in Uri avatarUri); + + void disableAvatarPublishing(); + IPrivacyListManager getPrivacyListManager(); } diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/smack/avatar/AvatarManager.java --- a/src/com/beem/project/beem/smack/avatar/AvatarManager.java Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/smack/avatar/AvatarManager.java Sun Feb 20 03:16:35 2011 +0100 @@ -51,11 +51,15 @@ import java.util.List; import java.util.LinkedList; +import java.security.NoSuchAlgorithmException; +import java.security.MessageDigest; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.packet.PacketExtension; import org.jivesoftware.smackx.pubsub.Item; import org.jivesoftware.smackx.pubsub.PayloadItem; +import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smack.util.StringUtils; /** * This class deals with the avatar data. @@ -64,6 +68,11 @@ */ public class AvatarManager { + /** The pubsub node for avatar data. */ + public static final String AVATARDATA_NODE = "urn:xmpp:avatar:data"; + /** The pubsub node for avatar metadata. */ + public static final String AVATARMETADATA_NODE = "urn:xmpp:avatar:metadata"; + private PepSubManager mPep; private Connection mCon; private boolean mAutoDownload; @@ -78,7 +87,8 @@ * @param cache the cache which will store the avatars * @param autoDownload true to enable auto download of avatars */ - public AvatarManager(final Connection con, final PepSubManager pepMgr, final AvatarCache cache, final boolean autoDownload) { + public AvatarManager(final Connection con, final PepSubManager pepMgr, + final AvatarCache cache, final boolean autoDownload) { mCon = con; mPep = pepMgr; mAutoDownload = autoDownload; @@ -87,6 +97,21 @@ } /** + * Create an AvatarManager. + * + * @param con the connection + * @param pepMgr the PepSubManager of the Connection + * @param autoDownload true to enable auto download of avatars + */ + protected AvatarManager(final Connection con, final PepSubManager pepMgr, final boolean autoDownload) { + mCon = con; + mPep = pepMgr; + mAutoDownload = autoDownload; + mPep.addPEPListener(new Listener()); + //TODO add a memory cache + } + + /** * Get an avatar from the cache. * * @param avatarId the id of the avatar @@ -121,31 +146,6 @@ mListeners.remove(listener); } - - /** - * Select the avatar to download. - * Subclass should override this method to take control over the selection process. - * This implementation select the first element. - * - * @param available list of the avatar metadata information - * @return the metadata of the avatar to download - */ - protected Info selectAvatar(List available) { - return available.get(0); - } - - /** - * Fire the listeners for avatar change. - * - * @param from the jid of the contact - * @param avatarId the new avatar id - * @param avatarInfos the metadata infos of the avatar - */ - private void fireListeners(String from, String avatarId, List avatarInfos) { - for (AvatarListener l : mListeners) - l.onAvatarChange(from, avatarId, avatarInfos); - } - /** * Download an avatar. * @@ -169,6 +169,96 @@ } /** + * Disable the diffusion of your avatar. + */ + public void disableAvatarPublishing() { + AvatarMetadataExtension metadata = new AvatarMetadataExtension(); + publishAvatarMetaData(null, metadata); + } + + /** + * Send an avatar image to the pep server. + * + * @param data the image data. + * @return true if the image where successfully sent. false otherwise + */ + public boolean publishAvatarData(byte[] data) { + try { + String id = getAvatarId(data); + publishAvatarData(id, data); + return true; + } catch (NoSuchAlgorithmException e) { + System.err.println("Security error while publishing avatar data : " + e.getMessage()); + return false; + } + } + + /** + * Send the metadata of the avatar you want to publish. + * By sending this metadata, you publish an avatar. + * + * @param id the id of the metadata item + * @param metadata the metadata to publish + */ + public void publishAvatarMetaData(String id, AvatarMetadataExtension metadata) { + PayloadItem item = new PayloadItem(id, metadata); + LeafNode node = mPep.getPEPNode(AVATARMETADATA_NODE); + node.publish(item); + } + + /** + * Select the avatar to download. + * Subclass should override this method to take control over the selection process. + * This implementation select the first element. + * + * @param available list of the avatar metadata information + * @return the metadata of the avatar to download + */ + protected Info selectAvatar(List available) { + return available.get(0); + } + + + /** + * Get the id corresponding to this avatar data. + * + * @param data the avatar data + * @return the id + * @throws NoSuchAlgorithmException if the sha-1 algorithm is unavailable + */ + protected String getAvatarId(byte[] data) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("sha-1"); + byte[] hash = md.digest(data); + return StringUtils.encodeHex(hash); + } + + /** + * Publish an avatar data. + * + * @param id the id of the avatar data + * @param data the data of the avatar + */ + private void publishAvatarData(String id, byte[] data) { + AvatarExtension avatar = new AvatarExtension(data); + PayloadItem item = new PayloadItem(id, avatar); + LeafNode node = mPep.getPEPNode(AVATARDATA_NODE); + node.publish(item); + } + + /** + * Fire the listeners for avatar change. + * + * @param from the jid of the contact + * @param avatarId the new avatar id + * @param avatarInfos the metadata infos of the avatar + */ + private void fireListeners(String from, String avatarId, List avatarInfos) { + for (AvatarListener l : mListeners) + l.onAvatarChange(from, avatarId, avatarInfos); + } + + + /** * A listener to PEPEevent. */ private class Listener implements PEPListener { diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/ui/ChangeStatus.java --- a/src/com/beem/project/beem/ui/ChangeStatus.java Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/ui/ChangeStatus.java Sun Feb 20 03:16:35 2011 +0100 @@ -51,14 +51,17 @@ import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; +import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.preference.PreferenceManager; +import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.ImageButton; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; @@ -71,6 +74,7 @@ import com.beem.project.beem.utils.BeemConnectivity; import com.beem.project.beem.utils.Status; + /** * This Activity is used to change the status. * @author nikita @@ -82,6 +86,7 @@ SERVICE_INTENT.setComponent(new ComponentName("com.beem.project.beem", "com.beem.project.beem.BeemService")); } + private static final String TAG = ChangeStatus.class.getSimpleName(); private static final int AVAILABLE_FOR_CHAT_IDX = 0; private static final int AVAILABLE_IDX = 1; private static final int BUSY_IDX = 2; @@ -95,6 +100,8 @@ private Button mClear; private Button mContact; private Spinner mSpinner; + private ImageButton mAvatar; + private Uri mAvatarUri; private SharedPreferences mSettings; private ArrayAdapter mAdapter; @@ -127,6 +134,9 @@ mContact = (Button) findViewById(R.id.OpenContactList); mContact.setOnClickListener(mOnClickOk); + mAvatar = (ImageButton) findViewById(R.id.avatarButton); + mAvatar.setOnClickListener(mOnClickOk); + mSettings = PreferenceManager.getDefaultSharedPreferences(this); mStatusMessageEditText = (EditText) findViewById(R.id.ChangeStatusMessage); mStatusMessageEditText.setText(mSettings.getString(BeemApplication.STATUS_TEXT_KEY, "")); @@ -174,6 +184,14 @@ this.unregisterReceiver(mReceiver); } + protected void onActivityResult (int requestCode, int resultCode, Intent data) { + if (data != null) { + mAvatarUri = data.getData(); + mAvatar.setImageURI(mAvatarUri); + } + + } + /** * Return the status index from status the settings. * @return the status index from status the settings. @@ -224,6 +242,25 @@ return result; } + private void onAvatarButton(View button) { + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.setType("image/*"); + i = Intent.createChooser(i, "Select avatar"); +// Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + startActivityForResult(i, 1); + } + + private void changeAvatar() { + if (mAvatarUri == null) + return; + try { + mXmppFacade.publishAvatar(mAvatarUri); + } catch (RemoteException e) { + Log.e(TAG, "Error while publishing avatar", e); + } + + } + /** * connection to service. * @author nikita @@ -278,6 +315,7 @@ try { mXmppFacade.changeStatus(status, msg.toString()); edit.putInt(BeemApplication.STATUS_KEY, mSpinner.getSelectedItemPosition()); + changeAvatar(); } catch (RemoteException e) { e.printStackTrace(); } @@ -290,7 +328,8 @@ } else if (v == mContact) { startActivity(new Intent(ChangeStatus.this, ContactList.class)); ChangeStatus.this.finish(); - } + } else if (v == mAvatar) + onAvatarButton(v); } } } diff -r 2236fe5b2db1 -r d23d8ad3b9ba src/com/beem/project/beem/ui/ContactList.java --- a/src/com/beem/project/beem/ui/ContactList.java Sat Mar 05 17:44:41 2011 +0100 +++ b/src/com/beem/project/beem/ui/ContactList.java Sun Feb 20 03:16:35 2011 +0100 @@ -665,7 +665,7 @@ ImageView img = (ImageView) view.findViewById(R.id.avatar); String avatarId = curContact.getAvatarId(); int contactStatus = curContact.getStatus(); - Drawable avatar = getAvatarStatusDrawable(curContact.getAvatarId()); + Drawable avatar = getAvatarStatusDrawable(avatarId); img.setImageDrawable(avatar); img.setImageLevel(contactStatus); } @@ -691,7 +691,7 @@ in.close(); } } catch (IOException e) { - Log.w(TAG, "Error while setting the avatar", e); + Log.w(TAG, "Error while setting the avatar " + avatarId, e); } } if (avatarDrawable == null)