Select an avatar to publish using the camera or the gallery.
--- a/res/values-fr/strings.xml Sun Feb 20 03:46:43 2011 +0100
+++ b/res/values-fr/strings.xml Wed Mar 02 00:58:11 2011 +0100
@@ -56,6 +56,10 @@
<string name="MenuConnection">Modifier un compte</string>
<string name="ChangeStatusOk">Mise à jour du statut</string>
<string name="ChangeStatusNoChange">Rien à changer</string>
+ <string name="my_avatar">Mon avatar</string>
+ <string name="take_photo">Prendre une photo</string>
+ <string name="pick_photo">Choisir une image</string>
+ <string name="photoPickerNotFoundText">Sélecteur d'image non disponible</string>
<!-- Settings class -->
<string name="SettingsText">Saisissez votre identifiant de connexion</string>
--- a/res/values/strings.xml Sun Feb 20 03:46:43 2011 +0100
+++ b/res/values/strings.xml Wed Mar 02 00:58:11 2011 +0100
@@ -56,6 +56,10 @@
<string name="ChangeStatusOk">Updating status</string>
<string name="ChangeStatusNoChange">Nothing to change</string>
<string name="my_avatar">My avatar</string>
+ <string name="take_photo">Take a photo</string>
+ <string name="pick_photo">Select a picture</string>
+ <string name="photoPickerNotFoundText">Photo picker not found</string>
+
<!-- Settings class -->
<string name="SettingsText">Edit your username</string>
--- a/src/com/beem/project/beem/service/BeemAvatarManager.java Sun Feb 20 03:46:43 2011 +0100
+++ b/src/com/beem/project/beem/service/BeemAvatarManager.java Wed Mar 02 00:58:11 2011 +0100
@@ -73,6 +73,7 @@
/**
* Create a BeemAvatarManager.
*
+ * @param ctx the Android context
* @param con the connection
* @param pepMgr the PepSubManager of the connection
* @param cache the cache which will store the avatars
@@ -84,6 +85,12 @@
mContext = ctx;
}
+ /**
+ * Publish an avatar.
+ *
+ * @param avatarUri the uri of the avatar
+ * @return true if the avatar was successfully published
+ */
public boolean publishAvatar(Uri avatarUri) {
try {
Bitmap bmp = MediaStore.Images.Media.getBitmap(mContext.getContentResolver(), avatarUri);
@@ -102,6 +109,7 @@
* @return true on success false otherwise
*/
private boolean publishAvatar(Bitmap bitmap) {
+ //TODO use the metadata available in the mediastore
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
--- a/src/com/beem/project/beem/ui/ChangeStatus.java Sun Feb 20 03:46:43 2011 +0100
+++ b/src/com/beem/project/beem/ui/ChangeStatus.java Wed Mar 02 00:58:11 2011 +0100
@@ -45,23 +45,33 @@
package com.beem.project.beem.ui;
import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ActivityNotFoundException;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
+import android.provider.MediaStore;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
+import android.widget.ListAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
@@ -76,6 +86,9 @@
import com.beem.project.beem.utils.BeemConnectivity;
import com.beem.project.beem.utils.Status;
+import java.io.File;
+import java.util.Date;
+import java.text.SimpleDateFormat;
/**
* This Activity is used to change the status.
@@ -96,6 +109,20 @@
private static final int UNAVAILABLE_IDX = 4;
private static final int DISCONNECTED_IDX = 5;
+ private static final int ICON_SIZE = 80;
+
+ private static final int SELECT_PHOTO_DLG = 0;
+
+ private static final int CAMERA_WITH_DATA = 0;
+ private static final int PHOTO_PICKED_WITH_DATA = 1;
+
+ private static final File PHOTO_DIR = new File(
+ Environment.getExternalStorageDirectory() + "/DCIM/Camera");
+
+ private static final String KEY_CURRENT_PHOTO_FILE = "currentphotofile";
+
+ private static final Uri MY_AVATAR_URI = Uri.parse(AvatarProvider.CONTENT_URI + "my_avatar");
+
private EditText mStatusMessageEditText;
private Toast mToast;
private Button mOk;
@@ -112,6 +139,10 @@
private final OnClickListener mOnClickOk = new MyOnClickListener();
private final BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver();
private boolean mShowCurrentAvatar = true;
+ private File mCurrentPhotoFile;
+
+ // BAD HACK
+ private MediaScannerConnection mMsConnection;
/**
* Constructor.
@@ -126,6 +157,7 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ Log.d(TAG, "oncreate");
setContentView(R.layout.changestatus);
mOk = (Button) findViewById(R.id.ChangeStatusOk);
@@ -187,12 +219,54 @@
this.unregisterReceiver(mReceiver);
}
- protected void onActivityResult (int requestCode, int resultCode, Intent data) {
- if (data != null) {
- mAvatarUri = data.getData();
- mAvatar.setImageURI(mAvatarUri);
- }
+ /*
+ * The activity is often reclaimed by the system memory.
+ */
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ if (mCurrentPhotoFile != null) {
+ outState.putString(KEY_CURRENT_PHOTO_FILE, mCurrentPhotoFile.toString());
+ }
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ String fileName = savedInstanceState.getString(KEY_CURRENT_PHOTO_FILE);
+ if (fileName != null) {
+ mCurrentPhotoFile = new File(fileName);
+ }
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ if (id == SELECT_PHOTO_DLG)
+ return createPickPhotoDialog();
+ return null;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Ignore failed requests
+ if (resultCode != RESULT_OK) return;
+
+ switch (requestCode) {
+ case PHOTO_PICKED_WITH_DATA:
+ mAvatarUri = Uri.parse(data.getAction());
+ if (mAvatarUri != null)
+ mAvatar.setImageURI(mAvatarUri);
+ break;
+
+ case CAMERA_WITH_DATA:
+ doCropPhoto(mCurrentPhotoFile);
+ break;
+ default:
+ Log.w(TAG, "onActivityResult : invalid request code");
+
+ }
}
/**
@@ -245,15 +319,19 @@
return result;
}
+ /**
+ * ClickListener for the avatarButton.
+ *
+ * @param button the avatar button
+ */
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);
+ showDialog(SELECT_PHOTO_DLG);
}
- private void changeAvatar() {
+ /**
+ * Publish the selected avatar.
+ */
+ private void publishAvatar() {
if (mAvatarUri == null)
return;
try {
@@ -263,6 +341,9 @@
}
}
+ /**
+ * Display the current avatar in the button.
+ */
private void displayCurrentAvatar() {
try {
UserInfo ui = mXmppFacade.getUserInfo();
@@ -275,6 +356,160 @@
mShowCurrentAvatar = false;
}
+ /*
+ * Some codes from AOSP (platform/packages/apps/Contacts)
+ * to select and crop an image.
+ */
+
+ /**
+ * Creates a dialog offering two options: take a photo or pick a photo from the gallery.
+ * @return the dialog
+ */
+ private Dialog createPickPhotoDialog() {
+ // Wrap our context to inflate list items using correct theme
+ final Context dialogContext = new ContextThemeWrapper(this,
+ android.R.style.Theme_Light);
+
+
+ String[] choices;
+ choices = new String[2];
+ choices[0] = getString(R.string.take_photo);
+ choices[1] = getString(R.string.pick_photo);
+ final ListAdapter adapter = new ArrayAdapter<String>(dialogContext,
+ android.R.layout.simple_list_item_1, choices);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(dialogContext);
+ builder.setTitle("Select a picture");
+ builder.setSingleChoiceItems(adapter, -1, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ switch(which) {
+ case 0:
+ doTakePhoto();
+ break;
+ case 1:
+ doPickPhotoFromGallery();
+ break;
+ default:
+ Log.w(TAG, "DialogInterface onClick : invalid which code");
+ }
+ }
+ });
+ return builder.create();
+ }
+
+ /**
+ * Create a file name for the icon photo using current time.
+ * @return the filename
+ */
+ private String getPhotoFileName() {
+ Date date = new Date(System.currentTimeMillis());
+ SimpleDateFormat dateFormat = new SimpleDateFormat("'IMG'_yyyyMMdd_HHmmss");
+ return dateFormat.format(date) + ".jpg";
+ }
+
+ /**
+ * Launches Camera to take a picture and store it in a file.
+ */
+ protected void doTakePhoto() {
+ try {
+ // Launch camera to take photo for selected contact
+ PHOTO_DIR.mkdirs();
+ mCurrentPhotoFile = new File(PHOTO_DIR, getPhotoFileName());
+ final Intent intent = getTakePickIntent(mCurrentPhotoFile);
+ startActivityForResult(intent, CAMERA_WITH_DATA);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Constructs an intent for capturing a photo and storing it in a temporary file.
+ * @param f the temporary file to use to store the picture
+ * @return the intent
+ */
+ public static Intent getTakePickIntent(File f) {
+ Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE, null);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(f));
+ return intent;
+ }
+
+ /**
+ * Sends a newly acquired photo to Gallery for cropping.
+ * @param f the image file to crop
+ */
+ protected void doCropPhoto(final File f) {
+ try {
+
+ // Add the image to the media store
+ // level 8
+ /*
+ MediaScannerConnection.scanFile(
+ this,
+ new String[] { f.getAbsolutePath() },
+ new String[] { null },
+ null);
+ */
+
+ // Launch gallery to crop the photo
+ final Intent intent = getCropImageIntent(Uri.fromFile(f));
+ startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Cannot crop image", e);
+ Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Constructs an intent for image cropping.
+ * @param photoUri the uri of the photo to crop
+ * @return the intent
+ */
+ public static Intent getCropImageIntent(Uri photoUri) {
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(photoUri, "image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", ICON_SIZE);
+ intent.putExtra("outputY", ICON_SIZE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, MY_AVATAR_URI);
+ return intent;
+ }
+
+ /**
+ * Launches Gallery to pick a photo.
+ */
+ protected void doPickPhotoFromGallery() {
+ try {
+ // Launch picker to choose photo for selected contact
+ final Intent intent = getPhotoPickIntent();
+ startActivityForResult(intent, PHOTO_PICKED_WITH_DATA);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.photoPickerNotFoundText, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /**
+ * Constructs an intent for picking a photo from Gallery, cropping it and returning the bitmap.
+ * @return the intent
+ */
+ public static Intent getPhotoPickIntent() {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
+ intent.setType("image/*");
+ intent.putExtra("crop", "true");
+ intent.putExtra("aspectX", 1);
+ intent.putExtra("aspectY", 1);
+ intent.putExtra("outputX", ICON_SIZE);
+ intent.putExtra("outputY", ICON_SIZE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, MY_AVATAR_URI);
+ // use this to get the bitmap in the intent
+// intent.putExtra("return-data", true);
+ return intent;
+ }
+
+
+
/**
* connection to service.
* @author nikita
@@ -331,7 +566,7 @@
try {
mXmppFacade.changeStatus(status, msg.toString());
edit.putInt(BeemApplication.STATUS_KEY, mSpinner.getSelectedItemPosition());
- changeAvatar();
+ publishAvatar();
} catch (RemoteException e) {
e.printStackTrace();
}