Add a basic way to publish avatar.
--- 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 @@
<RelativeLayout android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:padding="10dip">
+ <LinearLayout android:id="@+id/avatar_panel"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_alignParentTop="true"
+ android:layout_width="fill_parent" android:layout_height="fill_parent" >
+ <TextView android:text="@string/my_avatar" style="@style/Label"
+ android:layout_width="wrap_content" android:layout_height="wrap_content" />
+
+ <ImageButton android:id="@+id/avatarButton"
+ android:layout_width="120dip" android:layout_height="120dip"
+ android:scaleType="fitCenter" />
+ </LinearLayout>
<TextView android:id="@+id/ChangeStatusTypeLabel"
android:layout_width="fill_parent" android:layout_height="wrap_content"
+ android:layout_below="@id/avatar_panel"
android:text="@string/ChangeStatusType" style="@style/Label" />
<Spinner android:id="@+id/ChangeStatusSpinner"
android:layout_width="fill_parent" android:layout_height="wrap_content"
--- a/res/values/strings.xml Sat Mar 05 17:44:41 2011 +0100
+++ b/res/values/strings.xml Sun Feb 20 03:16:35 2011 +0100
@@ -55,6 +55,7 @@
<string name="MenuConnection">Edit account</string>
<string name="ChangeStatusOk">Updating status</string>
<string name="ChangeStatusNoChange">Nothing to change</string>
+ <string name="my_avatar">My avatar</string>
<!-- Settings class -->
<string name="SettingsText">Edit your username</string>
--- 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");
--- 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();
--- /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 <http://www.gnu.org/licenses/>.
+
+ 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];
+ }
+
+}
--- 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<IBeemConnectionListener> 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);
}
/**
--- 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();
}
}
--- 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();
}
--- 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<Info> 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<Info> 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<AvatarMetadataExtension> item = new PayloadItem<AvatarMetadataExtension>(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<Info> 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<AvatarExtension> item = new PayloadItem<AvatarExtension>(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<Info> avatarInfos) {
+ for (AvatarListener l : mListeners)
+ l.onAvatarChange(from, avatarId, avatarInfos);
+ }
+
+
+ /**
* A listener to PEPEevent.
*/
private class Listener implements PEPListener {
--- 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<CharSequence> 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);
}
}
}
--- 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)