com.phonegap.plugins.wsiCameraLauncher.WsiCameraLauncher.java Source code

Java tutorial

Introduction

Here is the source code for com.phonegap.plugins.wsiCameraLauncher.WsiCameraLauncher.java

Source

/*
 * Copyright (c) 2013, WeSawIt Inc.
 * Author: celwell
 * Some parts originally from Cordova Project and under Apache License
 */

package com.phonegap.plugins.wsiCameraLauncher;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.commons.codec.binary.Base64;
import org.apache.cordova.ExifHelper;
import org.apache.cordova.FileUtils;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.phonegap.plugins.wsiCapture.WsiCapture;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Bitmap.CompressFormat;
import android.media.ExifInterface;
import android.media.MediaScannerConnection;
import android.media.ThumbnailUtils;
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.KeyEvent;

/**
 * This class launches the camera view, allows the user to take a picture,
 * closes the camera view, and returns the captured image. When the camera view
 * is closed, the screen displayed before the camera view was shown is
 * redisplayed.
 */
public class WsiCameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient {

    private static final int DATA_URL = 0; // Return base64 encoded string
    private static final int FILE_URI = 1; // Return file uri
    // (content://media/external/images/media/2
    // for Android)

    private static final int PHOTOLIBRARY = 0; // Choose image from picture
    // library (same as
    // SAVEDPHOTOALBUM for Android)
    private static final int CAMERA = 1; // Take picture from camera
    private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture
    // library (same as
    // PHOTOLIBRARY for Android)

    private static final int PICTURE = 0; // allow selection of still pictures
    // only. DEFAULT. Will return format
    // specified via DestinationType
    private static final int VIDEO = 1; // allow selection of video only, ONLY
    // RETURNS URL
    private static final int ALLMEDIA = 2; // allow selection from all media
    // types

    private static final int JPEG = 0; // Take a picture of type JPEG
    private static final int PNG = 1; // Take a picture of type PNG
    private static final String GET_PICTURE = "Get Picture";
    private static final String GET_VIDEO = "Get Video";
    private static final String GET_All = "Get All";

    private static final String LOG_TAG = "WsiCameraLauncher";

    private int mQuality; // Compression quality hint (0-100: 0=low quality &
    // high compression, 100=compress of max quality)
    private int targetWidth; // desired width of the image
    private int targetHeight; // desired height of the image
    private Uri imageUri; // Uri of captured image
    private int encodingType; // Type of encoding to use
    private int mediaType; // What type of media to retrieve
    private boolean saveToPhotoAlbum; // Should the picture be saved to the
    // device's photo album
    private boolean correctOrientation; // Should the pictures orientation be
    // corrected
    // private boolean allowEdit; // Should we allow the user to crop the image.
    // UNUSED.

    public CallbackContext callbackContext;
    private int numPics;

    private MediaScannerConnection conn; // Used to update gallery app with
    // newly-written files
    private Uri scanMe; // Uri of image to be added to content store

    // This should never be null!
    // private CordovaInterface cordova;

    /**
     * Constructor.
     */
    public WsiCameraLauncher() {
    }

    // public void setContext(CordovaInterface mCtx) {
    // super.setContext(mCtx);
    // if (CordovaInterface.class.isInstance(mCtx))
    // cordova = (CordovaInterface) mCtx;
    // else
    // LOG.d(LOG_TAG,
    // "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
    // }

    /**
     * Executes the request and returns PluginResult.
     * 
     * @param action
     *            The action to execute.
     * @param args
     *            JSONArry of arguments for the plugin.
     * @param callbackContext
     *            The callback id used when calling back into JavaScript.
     * @return A PluginResult object with a status and message.
     */
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        this.callbackContext = callbackContext;

        Log.d(LOG_TAG, "wsiCameraLauncher execute called");

        if (action.equals("takePicture")) {
            int srcType = CAMERA;
            int destType = FILE_URI;
            this.saveToPhotoAlbum = false;
            this.targetHeight = 0;
            this.targetWidth = 0;
            this.encodingType = JPEG;
            this.mediaType = PICTURE;
            this.mQuality = 80;

            this.mQuality = args.getInt(0);
            destType = args.getInt(1);
            srcType = args.getInt(2);
            this.targetWidth = args.getInt(3);
            this.targetHeight = args.getInt(4);
            this.encodingType = args.getInt(5);
            this.mediaType = args.getInt(6);
            // this.allowEdit = args.getBoolean(7); // This field is unused.
            this.correctOrientation = args.getBoolean(8);
            this.saveToPhotoAlbum = args.getBoolean(9);

            // If the user specifies a 0 or smaller width/height
            // make it -1 so later comparisons succeed
            if (this.targetWidth < 1) {
                this.targetWidth = -1;
            }
            if (this.targetHeight < 1) {
                this.targetHeight = -1;
            }

            if (srcType == CAMERA) {
                this.takePicture(destType, encodingType);
            } else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
                this.getImage(srcType, destType);
            }
            PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
            r.setKeepCallback(true);
            callbackContext.sendPluginResult(r);
            return true;
        }
        return false;
    }

    // --------------------------------------------------------------------------
    // LOCAL METHODS
    // --------------------------------------------------------------------------

    /**
     * Take a picture with the camera. When an image is captured or the camera
     * view is cancelled, the result is returned in
     * CordovaActivity.onActivityResult, which forwards the result to
     * this.onActivityResult.
     * 
     * The image can either be returned as a base64 string or a URI that points
     * to the file. To display base64 string in an img tag, set the source to:
     * img.src="data:image/jpeg;base64,"+result; or to display URI in an img tag
     * img.src=result;
     * 
     * @param quality
     *            Compression quality hint (0-100: 0=low quality & high
     *            compression, 100=compress of max quality)
     * @param returnType
     *            Set the type of image to return.
     */
    public void takePicture(int returnType, int encodingType) {
        // Save the number of images currently on disk for later
        this.numPics = queryImgDB(whichContentStore()).getCount();

        // Display camera
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");

        // Specify file so that large image is captured and returned
        File photo = createCaptureFile(encodingType);
        intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo));
        this.imageUri = Uri.fromFile(photo);

        if (this.cordova != null) {
            this.cordova.startActivityForResult((CordovaPlugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
        }
        // else
        // LOG.d(LOG_TAG,
        // "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
    }

    private String getTempDirectoryPath(Context ctx) {
        File cache = null;

        // SD Card Mounted
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            cache = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/"
                    + ctx.getPackageName() + "/cache/");
        }
        // Use internal storage
        else {
            cache = ctx.getCacheDir();
        }

        // Create the cache directory if it doesn't exist
        if (!cache.exists()) {
            cache.mkdirs();
        }

        return cache.getAbsolutePath();
    }

    /**
     * Create a file in the applications temporary directory based upon the
     * supplied encoding.
     * 
     * @param encodingType
     *            of the image to be taken
     * @return a File object pointing to the temporary picture
     */
    private File createCaptureFile(int encodingType) {
        File photo = null;
        if (encodingType == JPEG) {
            photo = new File(this.getTempDirectoryPath(this.cordova.getActivity()), ".Pic.jpg");
        } else if (encodingType == PNG) {
            photo = new File(this.getTempDirectoryPath(this.cordova.getActivity()), ".Pic.png");
        } else {
            throw new IllegalArgumentException("Invalid Encoding Type: " + encodingType);
        }
        return photo;
    }

    /**
     * Get image from photo library.
     * 
     * @param quality
     *            Compression quality hint (0-100: 0=low quality & high
     *            compression, 100=compress of max quality)
     * @param srcType
     *            The album to get image from.
     * @param returnType
     *            Set the type of image to return.
     */
    // TODO: Images selected from SDCARD don't display correctly, but from
    // CAMERA ALBUM do!
    public void getImage(int srcType, int returnType) {

        final int srcTypeFinal = srcType;
        final int returnTypeFinal = returnType;

        String[] choices = { "Upload a Photo", "Upload a Video" };

        AlertDialog.Builder builder = new AlertDialog.Builder(this.cordova.getActivity());
        builder.setItems(choices, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                Log.d(LOG_TAG, "Index #" + which + " chosen.");
                Intent intent = new Intent();
                if (which == 0) {
                    // set up photo intent
                    WsiCameraLauncher.this.mediaType = PICTURE;
                    intent.setType("image/*");
                } else if (which == 1) {
                    // set up video intent
                    WsiCameraLauncher.this.mediaType = VIDEO;
                    intent.setType("video/*");
                } else {
                    WsiCameraLauncher.this.failPicture("Selection cancelled.");
                    return;
                }
                intent.setAction(Intent.ACTION_GET_CONTENT);
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                if (WsiCameraLauncher.this.cordova != null) {
                    WsiCameraLauncher.this.cordova.startActivityForResult((CordovaPlugin) WsiCameraLauncher.this,
                            Intent.createChooser(intent, new String("Pick")),
                            (srcTypeFinal + 1) * 16 + returnTypeFinal + 1);
                }
            }
        });
        builder.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP
                        && !event.isCanceled()) {
                    dialog.cancel();
                    WsiCameraLauncher.this.failPicture("Selection cancelled.");
                    return true;
                }
                return false;
            }
        });
        builder.show();
    }

    private String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) {
        final String scheme = contentUri.getScheme();

        if (scheme.compareTo("content") == 0) {
            String[] proj = { "_data" };
            Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null);
            int column_index = cursor.getColumnIndexOrThrow("_data");
            cursor.moveToFirst();
            return cursor.getString(column_index);
        } else if (scheme.compareTo("file") == 0) {
            return contentUri.getPath();
        } else {
            return contentUri.toString();
        }
    }

    private class UploadFilesToS3Task extends AsyncTask<Object, Void, PutObjectResult> {

        private Exception exception;
        private CallbackContext callbackContext;

        private String mid;
        private JSONObject mediaFile;

        protected PutObjectResult doInBackground(Object... params) {
            try {
                Log.d(LOG_TAG, "Inside doInBackground.");
                File fileToUpload = (File) params[0];
                File fileToUploadMedium = (File) params[1];
                File fileToUploadThumb = (File) params[2];
                this.callbackContext = (CallbackContext) params[3];
                this.mid = (String) params[4];
                this.mediaFile = (JSONObject) params[5];
                AmazonS3Client s3Client = new AmazonS3Client(
                        new BasicAWSCredentials("---AWS KEY REMOVED---", "---AWS SECRET KEY REMOVED---"));
                Log.d(LOG_TAG, "mid = " + mid);

                PutObjectRequest por = new PutObjectRequest("---AWS BUCKET NAME REMOVED---", "econ_" + mid + ".jpg",
                        fileToUpload);
                por.setCannedAcl(CannedAccessControlList.PublicRead);
                Log.d(LOG_TAG, "about to PUT");
                PutObjectResult result = s3Client.putObject(por);
                Log.d(LOG_TAG, "After PUT");

                PutObjectRequest porMedium = new PutObjectRequest("---AWS BUCKET NAME REMOVED---",
                        "medium_" + mid + ".jpg", fileToUploadMedium);
                porMedium.setCannedAcl(CannedAccessControlList.PublicRead);
                Log.d(LOG_TAG, "about to PUT porMedium");
                PutObjectResult resultMedium = s3Client.putObject(porMedium);
                Log.d(LOG_TAG, "After PUT porMedium");

                PutObjectRequest porThumb = new PutObjectRequest("---AWS BUCKET NAME REMOVED---",
                        "thumb_" + mid + ".jpg", fileToUploadThumb);
                porThumb.setCannedAcl(CannedAccessControlList.PublicRead);
                Log.d(LOG_TAG, "about to PUT porThumb");
                PutObjectResult resultThumb = s3Client.putObject(porThumb);
                Log.d(LOG_TAG, "After PUT porThumb");

                return result;
            } catch (Exception e) {
                this.exception = e;
                Log.d(LOG_TAG, "exception in doInBackground catch: " + this.exception.toString());
                return null;
            }
        }

        protected void onPostExecute(PutObjectResult result) {
            Log.d(LOG_TAG, "Inside onPostExecute.");
            if (result != null) {
                // was successful upload
                Log.d(LOG_TAG, "result of put: " + result.toString());
                try {
                    mediaFile.put("status", "loaded");
                    mediaFile.put("statusMedium", "loaded");
                    mediaFile.put("statusThumb", "loaded");
                    mediaFile.put("typeOfPluginResult", "success");
                    PluginResult pluginResult = new PluginResult(PluginResult.Status.OK,
                            (new JSONArray()).put(mediaFile));
                    pluginResult.setKeepCallback(false);
                    this.callbackContext.sendPluginResult(pluginResult);
                } catch (JSONException e) {
                    Log.d(LOG_TAG, "error: " + e.getStackTrace().toString());
                }
            } else {
                if (this.exception != null) {
                    Log.d(LOG_TAG, "exception in asynctask if any: " + this.exception.toString());
                }
            }
        }
    }

    private class UploadVideoToS3Task extends AsyncTask<Object, Void, PutObjectResult> {

        private Exception exception;
        private CallbackContext callbackContext;

        private String mid;
        private JSONObject mediaFile;

        protected PutObjectResult doInBackground(Object... params) {
            try {
                File fileToUpload = (File) params[0];
                this.callbackContext = (CallbackContext) params[1];
                this.mid = (String) params[2];
                this.mediaFile = (JSONObject) params[3];

                AmazonS3Client s3Client = new AmazonS3Client(
                        new BasicAWSCredentials("---AWS KEY REMOVED---", "---AWS SECRET KEY REMOVED---"));

                PutObjectRequest por = new PutObjectRequest("---AWS BUCKET NAME REMOVED---",
                        "" + mid + "." + mediaFile.getString("fileExt"), fileToUpload);
                por.setCannedAcl(CannedAccessControlList.PublicRead);
                Log.d(LOG_TAG, "about to PUT video");
                PutObjectResult result = s3Client.putObject(por);
                Log.d(LOG_TAG, "After PUT video");

                return result;
            } catch (Exception e) {
                this.exception = e;
                Log.d(LOG_TAG, "exception in doInBackground catch: " + this.exception.toString());
                return null;
            }
        }

        protected void onPostExecute(PutObjectResult result) {
            Log.d(LOG_TAG, "Inside onPostExecute.");
            if (result != null) {
                // was successful upload
                Log.d(LOG_TAG, "result of put: " + result.toString());
                try {
                    mediaFile.put("status", "loaded");
                    mediaFile.put("typeOfPluginResult", "success");
                    PluginResult pluginResult = new PluginResult(PluginResult.Status.OK,
                            (new JSONArray()).put(mediaFile));
                    pluginResult.setKeepCallback(false);
                    this.callbackContext.sendPluginResult(pluginResult);
                } catch (JSONException e) {
                    Log.d(LOG_TAG, "error: " + e.getStackTrace().toString());
                }
            } else {
                if (this.exception != null) {
                    Log.d(LOG_TAG, "exception in asynctask if any: " + this.exception.toString());
                }
            }
        }
    }

    private String generateRandomMid() {
        return "" + (100000000 + (int) (Math.random() * ((999999999 - 100000000) + 1)));
    }

    private Bitmap fitInsideSquare(Bitmap b, int sideLength) {
        int grabWidth = b.getWidth();
        int grabHeight = b.getHeight();

        float scaleX = ((float) sideLength) / grabWidth;
        float scaleY = ((float) sideLength) / grabHeight;
        float scale = Math.min(scaleX, scaleY);

        Matrix m = new Matrix();
        m.postScale(scale, scale);

        return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, true);
    }

    private Bitmap makeInsideSquare(Bitmap b, int sideLength) {
        int grabWidth = b.getWidth();
        int grabHeight = b.getHeight();
        if (grabWidth > grabHeight) {
            grabWidth = grabHeight;
        } else {
            grabHeight = grabWidth;
        }

        float scale = ((float) sideLength) / grabWidth;

        Matrix m = new Matrix();
        m.postScale(scale, scale);

        return Bitmap.createBitmap(b, 0, 0, grabWidth, grabHeight, m, true);
    }

    /**
     * Called when the camera view exits.
     * 
     * @param requestCode
     *            The request code originally supplied to
     *            startActivityForResult(), allowing you to identify who this
     *            result came from.
     * @param resultCode
     *            The integer result code returned by the child activity through
     *            its setResult().
     * @param intent
     *            An Intent, which can return result data to the caller (various
     *            data can be attached to Intent "extras").
     */
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {

        // Get src and dest types from request code
        int srcType = (requestCode / 16) - 1;
        int destType = (requestCode % 16) - 1;
        int rotate = 0;

        Log.d(LOG_TAG, "-z");

        // If retrieving photo from library
        if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
            Log.d(LOG_TAG, "-y");
            if (resultCode == Activity.RESULT_OK) {
                Log.d(LOG_TAG, "-x");
                Uri uri = intent.getData();
                Log.d(LOG_TAG, "-w");
                // If you ask for video or all media type you will automatically
                // get back a file URI
                // and there will be no attempt to resize any returned data
                if (this.mediaType != PICTURE) {
                    Log.d(LOG_TAG, "mediaType not PICTURE, so must be Video");

                    String metadataDateTime = "";
                    ExifInterface exif;
                    try {
                        exif = new ExifInterface(this.getRealPathFromURI(uri, this.cordova));
                        if (exif.getAttribute(ExifInterface.TAG_DATETIME) != null) {
                            Log.d(LOG_TAG, "z4a");
                            metadataDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME).toString();
                            metadataDateTime = metadataDateTime.replaceFirst(":", "-");
                            metadataDateTime = metadataDateTime.replaceFirst(":", "-");
                        }
                    } catch (IOException e2) {
                        // TODO Auto-generated catch block
                        e2.printStackTrace();
                    }

                    Log.d(LOG_TAG, "before create thumbnail");
                    Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(
                            (new File(this.getRealPathFromURI(uri, this.cordova))).getAbsolutePath(),
                            MediaStore.Images.Thumbnails.MINI_KIND);
                    Log.d(LOG_TAG, "after create thumbnail");
                    String mid = generateRandomMid();

                    try {
                        String filePathMedium = this.getTempDirectoryPath(this.cordova.getActivity()) + "/medium_"
                                + mid + ".jpg";
                        FileOutputStream foMedium = new FileOutputStream(filePathMedium);
                        bitmap.compress(CompressFormat.JPEG, 100, foMedium);
                        foMedium.flush();
                        foMedium.close();

                        bitmap.recycle();
                        System.gc();

                        JSONObject mediaFile = new JSONObject();
                        try {
                            mediaFile.put("mid", mid);
                            mediaFile.put("mediaType", "video");
                            mediaFile.put("filePath", filePathMedium);
                            mediaFile.put("filePathMedium", filePathMedium);
                            mediaFile.put("filePathThumb", filePathMedium);
                            mediaFile.put("typeOfPluginResult", "initialRecordInformer");
                            String absolutePath = (new File(this.getRealPathFromURI(uri, this.cordova)))
                                    .getAbsolutePath();
                            mediaFile.put("fileExt", absolutePath.substring(absolutePath.lastIndexOf(".") + 1));
                            if (metadataDateTime != "") {
                                mediaFile.put("metadataDateTime", metadataDateTime);
                            }
                        } catch (JSONException e) {
                            Log.d(LOG_TAG, "error: " + e.getStackTrace().toString());
                        }
                        Log.d(LOG_TAG, "mediafile at 638" + mediaFile.toString());
                        PluginResult pluginResult = new PluginResult(PluginResult.Status.OK,
                                (new JSONArray()).put(mediaFile));
                        pluginResult.setKeepCallback(true);
                        this.callbackContext.sendPluginResult(pluginResult);
                        new UploadVideoToS3Task().execute(new File(this.getRealPathFromURI(uri, this.cordova)),
                                this.callbackContext, mid, mediaFile);
                    } catch (FileNotFoundException e1) {
                        e1.printStackTrace();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                } else {
                    String imagePath = this.getRealPathFromURI(uri, this.cordova);
                    String mimeType = FileUtils.getMimeType(imagePath);
                    // If we don't have a valid image so quit.
                    if (imagePath == null || mimeType == null || !(mimeType.equalsIgnoreCase("image/jpeg")
                            || mimeType.equalsIgnoreCase("image/png"))) {
                        Log.d(LOG_TAG, "I either have a null image path or bitmap");
                        this.failPicture("Unable to retrieve path to picture!");
                        return;
                    }

                    String mid = generateRandomMid();

                    Log.d(LOG_TAG, "a");

                    JSONObject mediaFile = new JSONObject();

                    Log.d(LOG_TAG, "b");

                    try {
                        FileInputStream fi = new FileInputStream(imagePath);
                        Bitmap bitmap = BitmapFactory.decodeStream(fi);
                        fi.close();

                        Log.d(LOG_TAG, "z1");

                        // try to get exif data
                        ExifInterface exif = new ExifInterface(imagePath);

                        Log.d(LOG_TAG, "z2");

                        JSONObject metadataJson = new JSONObject();

                        Log.d(LOG_TAG, "z3");

                        /*
                        JSONObject latlng = new JSONObject();
                        String lat = "0";
                        String lng = "0";
                        if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) != null) {
                           lat = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
                        }
                        if (exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE) != null) {
                           lng = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
                        }
                        latlng.put("lat", lat);
                        latlng.put("lng", lng);
                        Log.d(LOG_TAG, "z4");
                        metadataJson.put("locationData", latlng);
                        */

                        String metadataDateTime = "";

                        if (exif.getAttribute(ExifInterface.TAG_DATETIME) != null) {
                            Log.d(LOG_TAG, "z4a");
                            JSONObject exifWrapper = new JSONObject();
                            exifWrapper.put("DateTimeOriginal",
                                    exif.getAttribute(ExifInterface.TAG_DATETIME).toString());
                            exifWrapper.put("DateTimeDigitized",
                                    exif.getAttribute(ExifInterface.TAG_DATETIME).toString());
                            metadataDateTime = exif.getAttribute(ExifInterface.TAG_DATETIME).toString();
                            metadataDateTime = metadataDateTime.replaceFirst(":", "-");
                            metadataDateTime = metadataDateTime.replaceFirst(":", "-");
                            Log.d(LOG_TAG, "z5");
                            metadataJson.put("Exif", exifWrapper);
                        }
                        Log.d(LOG_TAG, "z6");

                        Log.d(LOG_TAG, "metadataJson: " + metadataJson.toString());
                        Log.d(LOG_TAG, "metadataDateTime: " + metadataDateTime.toString());

                        if (exif.getAttribute(ExifInterface.TAG_ORIENTATION) != null) {
                            int o = Integer.parseInt(exif.getAttribute(ExifInterface.TAG_ORIENTATION));

                            Log.d(LOG_TAG, "z7");

                            if (o == ExifInterface.ORIENTATION_NORMAL) {
                                rotate = 0;
                            } else if (o == ExifInterface.ORIENTATION_ROTATE_90) {
                                rotate = 90;
                            } else if (o == ExifInterface.ORIENTATION_ROTATE_180) {
                                rotate = 180;
                            } else if (o == ExifInterface.ORIENTATION_ROTATE_270) {
                                rotate = 270;
                            } else {
                                rotate = 0;
                            }

                            Log.d(LOG_TAG, "z8");

                            Log.d(LOG_TAG, "rotate: " + rotate);

                            // try to correct orientation
                            if (rotate != 0) {
                                Matrix matrix = new Matrix();
                                Log.d(LOG_TAG, "z9");
                                matrix.setRotate(rotate);
                                Log.d(LOG_TAG, "z10");
                                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(),
                                        matrix, true);
                                Log.d(LOG_TAG, "z11");
                            }
                        }

                        Log.d(LOG_TAG, "c");

                        String filePath = this.getTempDirectoryPath(this.cordova.getActivity()) + "/econ_" + mid
                                + ".jpg";
                        FileOutputStream foEcon = new FileOutputStream(filePath);
                        fitInsideSquare(bitmap, 850).compress(CompressFormat.JPEG, 45, foEcon);
                        foEcon.flush();
                        foEcon.close();

                        Log.d(LOG_TAG, "d");

                        String filePathMedium = this.getTempDirectoryPath(this.cordova.getActivity()) + "/medium_"
                                + mid + ".jpg";
                        FileOutputStream foMedium = new FileOutputStream(filePathMedium);
                        makeInsideSquare(bitmap, 320).compress(CompressFormat.JPEG, 55, foMedium);
                        foMedium.flush();
                        foMedium.close();

                        Log.d(LOG_TAG, "e");

                        String filePathThumb = this.getTempDirectoryPath(this.cordova.getActivity()) + "/thumb_"
                                + mid + ".jpg";
                        FileOutputStream foThumb = new FileOutputStream(filePathThumb);
                        makeInsideSquare(bitmap, 175).compress(CompressFormat.JPEG, 55, foThumb);
                        foThumb.flush();
                        foThumb.close();

                        bitmap.recycle();
                        System.gc();

                        Log.d(LOG_TAG, "f");

                        mediaFile.put("mid", mid);
                        mediaFile.put("mediaType", "photo");
                        mediaFile.put("filePath", filePath);
                        mediaFile.put("filePathMedium", filePath);
                        mediaFile.put("filePathThumb", filePath);
                        mediaFile.put("typeOfPluginResult", "initialRecordInformer");
                        //mediaFile.put("metadataJson", metadataJson);
                        if (metadataDateTime != "") {
                            mediaFile.put("metadataDateTime", metadataDateTime);
                        }

                        PluginResult pluginResult = new PluginResult(PluginResult.Status.OK,
                                (new JSONArray()).put(mediaFile));
                        pluginResult.setKeepCallback(true);
                        this.callbackContext.sendPluginResult(pluginResult);
                        Log.d(LOG_TAG, "g");
                        Log.d(LOG_TAG, "mediaFile " + mediaFile.toString());
                        new UploadFilesToS3Task().execute(new File(filePath), new File(filePathMedium),
                                new File(filePathThumb), this.callbackContext, mid, mediaFile);
                        Log.d(LOG_TAG, "h");
                    } catch (FileNotFoundException e) {
                        Log.d(LOG_TAG, "error: " + e.getStackTrace().toString());
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        Log.d(LOG_TAG, "error: " + e1.getStackTrace().toString());
                    } catch (JSONException e2) {
                        // TODO Auto-generated catch block
                        Log.d(LOG_TAG, "error: " + e2.getStackTrace().toString());
                    }

                    /*
                    if (this.correctOrientation) {
                       String[] cols = { MediaStore.Images.Media.ORIENTATION };
                       Cursor cursor = this.cordova
                    .getActivity()
                    .getContentResolver()
                    .query(intent.getData(), cols, null, null,
                          null);
                       if (cursor != null) {
                          cursor.moveToPosition(0);
                          rotate = cursor.getInt(0);
                          cursor.close();
                       }
                       if (rotate != 0) {
                          Matrix matrix = new Matrix();
                          matrix.setRotate(rotate);
                          bitmap = Bitmap.createBitmap(bitmap, 0, 0,
                       bitmap.getWidth(), bitmap.getHeight(),
                       matrix, true);
                       }
                    }
                        
                    // Create an ExifHelper to save the exif
                    // data that is lost during compression
                    String resizePath = this
                          .getTempDirectoryPath(this.cordova
                       .getActivity())
                          + "/resize.jpg";
                    ExifHelper exif = new ExifHelper();
                    try {
                       if (this.encodingType == JPEG) {
                          exif.createInFile(resizePath);
                          exif.readExifData();
                          rotate = exif.getOrientation();
                       }
                    } catch (IOException e) {
                       e.printStackTrace();
                    }
                        
                    OutputStream os = new FileOutputStream(
                          resizePath);
                    bitmap.compress(Bitmap.CompressFormat.JPEG,
                          this.mQuality, os);
                    os.close();
                        
                    // Restore exif data to file
                    if (this.encodingType == JPEG) {
                       exif.createOutFile(this
                    .getRealPathFromURI(uri,
                          this.cordova));
                       exif.writeExifData();
                    }
                        
                    if (bitmap != null) {
                       bitmap.recycle();
                       bitmap = null;
                    }
                    System.gc();
                        
                    // The resized image is cached by the app in
                    // order to get around this and not have to
                    // delete your
                    // application cache I'm adding the current
                    // system time to the end of the file url.
                    this.callbackContext.success("file://" + resizePath + "?" + System.currentTimeMillis());
                    */
                }
            } else if (resultCode == Activity.RESULT_CANCELED) {
                this.failPicture("Selection cancelled.");
            } else {
                this.failPicture("Selection did not complete!");
            }
        }
    }

    /**
     * Figure out if the bitmap should be rotated. For instance if the picture
     * was taken in portrait mode
     * 
     * @param rotate
     * @param bitmap
     * @return rotated bitmap
     */
    private Bitmap getRotatedBitmap(int rotate, Bitmap bitmap, ExifHelper exif) {
        Matrix matrix = new Matrix();
        if (rotate == 180) {
            matrix.setRotate(rotate);
        } else {
            matrix.setRotate(rotate, (float) bitmap.getWidth() / 2, (float) bitmap.getHeight() / 2);
        }
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        exif.resetOrientation();
        return bitmap;
    }

    /**
     * In the special case where the default width, height and quality are
     * unchanged we just write the file out to disk saving the expensive
     * Bitmap.compress function.
     * 
     * @param uri
     * @throws FileNotFoundException
     * @throws IOException
     */
    private void writeUncompressedImage(Uri uri) throws FileNotFoundException, IOException {
        FileInputStream fis = new FileInputStream(FileUtils.stripFileProtocol(imageUri.toString()));
        OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
        byte[] buffer = new byte[4096];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
        os.flush();
        os.close();
        fis.close();
    }

    /**
     * Create entry in media store for image
     * 
     * @return uri
     */
    private Uri getUriFromMediaStore() {
        ContentValues values = new ContentValues();
        values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        Uri uri;
        try {
            uri = this.cordova.getActivity().getContentResolver()
                    .insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        } catch (UnsupportedOperationException e) {
            LOG.d(LOG_TAG, "Can't write to external media storage.");
            try {
                uri = this.cordova.getActivity().getContentResolver()
                        .insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
            } catch (UnsupportedOperationException ex) {
                LOG.d(LOG_TAG, "Can't write to internal media storage.");
                return null;
            }
        }
        return uri;
    }

    /**
     * Return a scaled bitmap based on the target width and height
     * 
     * @param imagePath
     * @return
     */
    private Bitmap getScaledBitmap(String imagePath) {
        // If no new width or height were specified return the original bitmap
        if (this.targetWidth <= 0 && this.targetHeight <= 0) {
            return BitmapFactory.decodeFile(imagePath);
        }

        // figure out the original width and height of the image
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(imagePath, options);

        // determine the correct aspect ratio
        int[] widthHeight = calculateAspectRatio(options.outWidth, options.outHeight);

        // Load in the smallest bitmap possible that is closest to the size we
        // want
        options.inJustDecodeBounds = false;
        options.inSampleSize = calculateSampleSize(options.outWidth, options.outHeight, this.targetWidth,
                this.targetHeight);
        Bitmap unscaledBitmap = BitmapFactory.decodeFile(imagePath, options);
        if (unscaledBitmap == null) {
            return null;
        }

        return Bitmap.createScaledBitmap(unscaledBitmap, widthHeight[0], widthHeight[1], true);
    }

    /**
     * Maintain the aspect ratio so the resulting image does not look smooshed
     * 
     * @param origWidth
     * @param origHeight
     * @return
     */
    public int[] calculateAspectRatio(int origWidth, int origHeight) {
        int newWidth = this.targetWidth;
        int newHeight = this.targetHeight;

        // If no new width or height were specified return the original bitmap
        if (newWidth <= 0 && newHeight <= 0) {
            newWidth = origWidth;
            newHeight = origHeight;
        }
        // Only the width was specified
        else if (newWidth > 0 && newHeight <= 0) {
            newHeight = (newWidth * origHeight) / origWidth;
        }
        // only the height was specified
        else if (newWidth <= 0 && newHeight > 0) {
            newWidth = (newHeight * origWidth) / origHeight;
        }
        // If the user specified both a positive width and height
        // (potentially different aspect ratio) then the width or height is
        // scaled so that the image fits while maintaining aspect ratio.
        // Alternatively, the specified width and height could have been
        // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
        // would result in whitespace in the new image.
        else {
            double newRatio = newWidth / (double) newHeight;
            double origRatio = origWidth / (double) origHeight;

            if (origRatio > newRatio) {
                newHeight = (newWidth * origHeight) / origWidth;
            } else if (origRatio < newRatio) {
                newWidth = (newHeight * origWidth) / origHeight;
            }
        }

        int[] retval = new int[2];
        retval[0] = newWidth;
        retval[1] = newHeight;
        return retval;
    }

    /**
     * Figure out what ratio we can load our image into memory at while still
     * being bigger than our desired width and height
     * 
     * @param srcWidth
     * @param srcHeight
     * @param dstWidth
     * @param dstHeight
     * @return
     */
    public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
        final float srcAspect = (float) srcWidth / (float) srcHeight;
        final float dstAspect = (float) dstWidth / (float) dstHeight;

        if (srcAspect > dstAspect) {
            return srcWidth / dstWidth;
        } else {
            return srcHeight / dstHeight;
        }
    }

    /**
     * Creates a cursor that can be used to determine how many images we have.
     * 
     * @return a cursor
     */
    private Cursor queryImgDB(Uri contentStore) {
        return this.cordova.getActivity().getContentResolver().query(contentStore,
                new String[] { MediaStore.Images.Media._ID }, null, null, null);
    }

    /**
     * Cleans up after picture taking. Checking for duplicates and that kind of
     * stuff.
     * 
     * @param newImage
     */
    private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
        if (bitmap != null) {
            bitmap.recycle();
        }

        // Clean up initial camera-written image file.
        (new File(FileUtils.stripFileProtocol(oldImage.toString()))).delete();

        checkForDuplicateImage(imageType);
        // Scan for the gallery to update pic refs in gallery
        if (this.saveToPhotoAlbum && newImage != null) {
            this.scanForGallery(newImage);
        }

        System.gc();
    }

    /**
     * Used to find out if we are in a situation where the Camera Intent adds to
     * images to the content store. If we are using a FILE_URI and the number of
     * images in the DB increases by 2 we have a duplicate, when using a
     * DATA_URL the number is 1.
     * 
     * @param type
     *            FILE_URI or DATA_URL
     */
    private void checkForDuplicateImage(int type) {
        int diff = 1;
        Uri contentStore = whichContentStore();
        Cursor cursor = queryImgDB(contentStore);
        int currentNumOfImages = cursor.getCount();

        if (type == FILE_URI && this.saveToPhotoAlbum) {
            diff = 2;
        }

        // delete the duplicate file if the difference is 2 for file URI or 1
        // for Data URL
        if ((currentNumOfImages - numPics) == diff) {
            cursor.moveToLast();
            int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
            if (diff == 2) {
                id--;
            }
            Uri uri = Uri.parse(contentStore + "/" + id);
            this.cordova.getActivity().getContentResolver().delete(uri, null, null);
        }
    }

    /**
     * Determine if we are storing the images in internal or external storage
     * 
     * @return Uri
     */
    private Uri whichContentStore() {
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        } else {
            return android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI;
        }
    }

    /**
     * Compress bitmap using jpeg, convert to Base64 encoded string, and return
     * to JavaScript.
     * 
     * @param bitmap
     */
    public void processPicture(Bitmap bitmap) {
        ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream();
        try {
            if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) {
                byte[] code = jpeg_data.toByteArray();
                byte[] output = Base64.encodeBase64(code);
                String js_out = new String(output);
                this.callbackContext.success(js_out);
                js_out = null;
                output = null;
                code = null;
            }
        } catch (Exception e) {
            this.failPicture("Error compressing image.");
        }
        jpeg_data = null;
    }

    /**
     * Send error message to JavaScript.
     * 
     * @param err
     */
    public void failPicture(String err) {
        this.callbackContext.error(err);
    }

    private void scanForGallery(Uri newImage) {
        this.scanMe = newImage;
        if (this.conn != null) {
            this.conn.disconnect();
        }
        this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this);
        conn.connect();
    }

    public void onMediaScannerConnected() {
        try {
            this.conn.scanFile(this.scanMe.toString(), "image/*");
        } catch (java.lang.IllegalStateException e) {
            LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture");
        }

    }

    public void onScanCompleted(String path, Uri uri) {
        this.conn.disconnect();
    }
}