Select an avatar to publish using the camera or the gallery.
authorDa Risk <darisk972@gmail.com>
Wed, 02 Mar 2011 00:58:11 +0100
changeset 882 0b9c08ea320d
parent 881 0ceee1f2b829
child 883 c0a2d2743f38
Select an avatar to publish using the camera or the gallery.
res/values-fr/strings.xml
res/values/strings.xml
src/com/beem/project/beem/service/BeemAvatarManager.java
src/com/beem/project/beem/ui/ChangeStatus.java
--- 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();
 		    }