# HG changeset patch # User Da Risk # Date 1299023891 -3600 # Node ID 0b9c08ea320dde0566f9924bc1043798ba1c59a1 # Parent 0ceee1f2b82973bf563d25062ad30ae323ef1700 Select an avatar to publish using the camera or the gallery. diff -r 0ceee1f2b829 -r 0b9c08ea320d res/values-fr/strings.xml --- 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 @@ Modifier un compte Mise à jour du statut Rien à changer + Mon avatar + Prendre une photo + Choisir une image + Sélecteur d'image non disponible Saisissez votre identifiant de connexion diff -r 0ceee1f2b829 -r 0b9c08ea320d res/values/strings.xml --- 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 @@ Updating status Nothing to change My avatar + Take a photo + Select a picture + Photo picker not found + Edit your username diff -r 0ceee1f2b829 -r 0b9c08ea320d src/com/beem/project/beem/service/BeemAvatarManager.java --- 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 diff -r 0ceee1f2b829 -r 0b9c08ea320d src/com/beem/project/beem/ui/ChangeStatus.java --- 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(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(); }