arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-camera/src/android/CameraLauncher.java
plugins/cordova-plugin-camera/src/android/CameraLauncher.javaRaw
   1/*
   2       Licensed to the Apache Software Foundation (ASF) under one
   3       or more contributor license agreements.  See the NOTICE file
   4       distributed with this work for additional information
   5       regarding copyright ownership.  The ASF licenses this file
   6       to you under the Apache License, Version 2.0 (the
   7       "License"); you may not use this file except in compliance
   8       with the License.  You may obtain a copy of the License at
   9
  10         http://www.apache.org/licenses/LICENSE-2.0
  11
  12       Unless required by applicable law or agreed to in writing,
  13       software distributed under the License is distributed on an
  14       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15       KIND, either express or implied.  See the License for the
  16       specific language governing permissions and limitations
  17       under the License.
  18*/
  19package org.apache.cordova.camera;
  20
  21import java.io.ByteArrayOutputStream;
  22import java.io.File;
  23import java.io.FileInputStream;
  24import java.io.FileNotFoundException;
  25import java.io.FileOutputStream;
  26import java.io.IOException;
  27import java.io.InputStream;
  28import java.io.OutputStream;
  29import java.text.SimpleDateFormat;
  30import java.util.Date;
  31
  32import org.apache.cordova.BuildHelper;
  33import org.apache.cordova.CallbackContext;
  34import org.apache.cordova.CordovaPlugin;
  35import org.apache.cordova.CordovaResourceApi;
  36import org.apache.cordova.LOG;
  37import org.apache.cordova.PermissionHelper;
  38import org.apache.cordova.PluginResult;
  39import org.json.JSONArray;
  40import org.json.JSONException;
  41
  42import android.Manifest;
  43import android.annotation.TargetApi;
  44import android.app.Activity;
  45import android.content.ActivityNotFoundException;
  46import android.content.ContentValues;
  47import android.content.Context;
  48import android.content.Intent;
  49import android.database.Cursor;
  50import android.graphics.Bitmap;
  51import android.graphics.Bitmap.CompressFormat;
  52import android.graphics.BitmapFactory;
  53import android.graphics.Matrix;
  54import android.media.ExifInterface;
  55import android.media.MediaScannerConnection;
  56import android.media.MediaScannerConnection.MediaScannerConnectionClient;
  57import android.net.Uri;
  58import android.os.Build;
  59import android.os.Bundle;
  60import android.os.Environment;
  61import android.provider.DocumentsContract;
  62import android.provider.MediaStore;
  63import android.provider.OpenableColumns;
  64import android.support.v4.content.FileProvider;
  65import android.util.Base64;
  66import android.content.pm.PackageManager;
  67import android.content.pm.PackageManager.NameNotFoundException;
  68
  69/**
  70 * This class launches the camera view, allows the user to take a picture, closes the camera view,
  71 * and returns the captured image.  When the camera view is closed, the screen displayed before
  72 * the camera view was shown is redisplayed.
  73 */
  74public class CameraLauncher extends CordovaPlugin implements MediaScannerConnectionClient {
  75
  76    private static final int DATA_URL = 0;              // Return base64 encoded string
  77    private static final int FILE_URI = 1;              // Return file uri (content://media/external/images/media/2 for Android)
  78    private static final int NATIVE_URI = 2;                    // On Android, this is the same as FILE_URI
  79
  80    private static final int PHOTOLIBRARY = 0;          // Choose image from picture library (same as SAVEDPHOTOALBUM for Android)
  81    private static final int CAMERA = 1;                // Take picture from camera
  82    private static final int SAVEDPHOTOALBUM = 2;       // Choose image from picture library (same as PHOTOLIBRARY for Android)
  83
  84    private static final int PICTURE = 0;               // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
  85    private static final int VIDEO = 1;                 // allow selection of video only, ONLY RETURNS URL
  86    private static final int ALLMEDIA = 2;              // allow selection from all media types
  87
  88    private static final int JPEG = 0;                  // Take a picture of type JPEG
  89    private static final int PNG = 1;                   // Take a picture of type PNG
  90    private static final String GET_PICTURE = "Get Picture";
  91    private static final String GET_VIDEO = "Get Video";
  92    private static final String GET_All = "Get All";
  93
  94    public static final int PERMISSION_DENIED_ERROR = 20;
  95    public static final int TAKE_PIC_SEC = 0;
  96    public static final int SAVE_TO_ALBUM_SEC = 1;
  97
  98    private static final String LOG_TAG = "CameraLauncher";
  99
 100    //Where did this come from?
 101    private static final int CROP_CAMERA = 100;
 102
 103    private int mQuality;                   // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
 104    private int targetWidth;                // desired width of the image
 105    private int targetHeight;               // desired height of the image
 106    private CordovaUri imageUri;            // Uri of captured image
 107    private int encodingType;               // Type of encoding to use
 108    private int mediaType;                  // What type of media to retrieve
 109    private int destType;                   // Source type (needs to be saved for the permission handling)
 110    private int srcType;                    // Destination type (needs to be saved for permission handling)
 111    private boolean saveToPhotoAlbum;       // Should the picture be saved to the device's photo album
 112    private boolean correctOrientation;     // Should the pictures orientation be corrected
 113    private boolean orientationCorrected;   // Has the picture's orientation been corrected
 114    private boolean allowEdit;              // Should we allow the user to crop the image.
 115
 116    protected final static String[] permissions = { Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE };
 117
 118    public CallbackContext callbackContext;
 119    private int numPics;
 120
 121    private MediaScannerConnection conn;    // Used to update gallery app with newly-written files
 122    private Uri scanMe;                     // Uri of image to be added to content store
 123    private Uri croppedUri;
 124    private ExifHelper exifData;            // Exif data from source
 125    private String applicationId;
 126
 127
 128    /**
 129     * Executes the request and returns PluginResult.
 130     *
 131     * @param action            The action to execute.
 132     * @param args              JSONArry of arguments for the plugin.
 133     * @param callbackContext   The callback id used when calling back into JavaScript.
 134     * @return                  A PluginResult object with a status and message.
 135     */
 136    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
 137        this.callbackContext = callbackContext;
 138        //Adding an API to CoreAndroid to get the BuildConfigValue
 139        //This allows us to not make this a breaking change to embedding
 140        this.applicationId = (String) BuildHelper.getBuildConfigValue(cordova.getActivity(), "APPLICATION_ID");
 141        this.applicationId = preferences.getString("applicationId", this.applicationId);
 142
 143
 144        if (action.equals("takePicture")) {
 145            this.srcType = CAMERA;
 146            this.destType = FILE_URI;
 147            this.saveToPhotoAlbum = false;
 148            this.targetHeight = 0;
 149            this.targetWidth = 0;
 150            this.encodingType = JPEG;
 151            this.mediaType = PICTURE;
 152            this.mQuality = 50;
 153
 154            //Take the values from the arguments if they're not already defined (this is tricky)
 155            this.destType = args.getInt(1);
 156            this.srcType = args.getInt(2);
 157            this.mQuality = args.getInt(0);
 158            this.targetWidth = args.getInt(3);
 159            this.targetHeight = args.getInt(4);
 160            this.encodingType = args.getInt(5);
 161            this.mediaType = args.getInt(6);
 162            this.allowEdit = args.getBoolean(7);
 163            this.correctOrientation = args.getBoolean(8);
 164            this.saveToPhotoAlbum = args.getBoolean(9);
 165
 166            // If the user specifies a 0 or smaller width/height
 167            // make it -1 so later comparisons succeed
 168            if (this.targetWidth < 1) {
 169                this.targetWidth = -1;
 170            }
 171            if (this.targetHeight < 1) {
 172                this.targetHeight = -1;
 173            }
 174
 175            // We don't return full-quality PNG files. The camera outputs a JPEG
 176            // so requesting it as a PNG provides no actual benefit
 177            if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
 178                    !this.correctOrientation && this.encodingType == PNG && this.srcType == CAMERA) {
 179                this.encodingType = JPEG;
 180            }
 181
 182            try {
 183                if (this.srcType == CAMERA) {
 184                    this.callTakePicture(destType, encodingType);
 185                }
 186                else if ((this.srcType == PHOTOLIBRARY) || (this.srcType == SAVEDPHOTOALBUM)) {
 187                    // FIXME: Stop always requesting the permission
 188                    if(!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
 189                        PermissionHelper.requestPermission(this, SAVE_TO_ALBUM_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
 190                    } else {
 191                        this.getImage(this.srcType, destType, encodingType);
 192                    }
 193                }
 194            }
 195            catch (IllegalArgumentException e)
 196            {
 197                callbackContext.error("Illegal Argument Exception");
 198                PluginResult r = new PluginResult(PluginResult.Status.ERROR);
 199                callbackContext.sendPluginResult(r);
 200                return true;
 201            }
 202
 203            PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT);
 204            r.setKeepCallback(true);
 205            callbackContext.sendPluginResult(r);
 206
 207            return true;
 208        }
 209        return false;
 210    }
 211
 212    //--------------------------------------------------------------------------
 213    // LOCAL METHODS
 214    //--------------------------------------------------------------------------
 215
 216    private String getTempDirectoryPath() {
 217        File cache = null;
 218
 219        // SD Card Mounted
 220        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
 221            cache = cordova.getActivity().getExternalCacheDir();
 222        }
 223        // Use internal storage
 224        else {
 225            cache = cordova.getActivity().getCacheDir();
 226        }
 227
 228        // Create the cache directory if it doesn't exist
 229        cache.mkdirs();
 230        return cache.getAbsolutePath();
 231    }
 232
 233    /**
 234     * Take a picture with the camera.
 235     * When an image is captured or the camera view is cancelled, the result is returned
 236     * in CordovaActivity.onActivityResult, which forwards the result to this.onActivityResult.
 237     *
 238     * The image can either be returned as a base64 string or a URI that points to the file.
 239     * To display base64 string in an img tag, set the source to:
 240     *      img.src="data:image/jpeg;base64,"+result;
 241     * or to display URI in an img tag
 242     *      img.src=result;
 243     *
 244     * @param returnType        Set the type of image to return.
 245     * @param encodingType           Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality)
 246     */
 247    public void callTakePicture(int returnType, int encodingType) {
 248        boolean saveAlbumPermission = PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
 249        boolean takePicturePermission = PermissionHelper.hasPermission(this, Manifest.permission.CAMERA);
 250
 251        // CB-10120: The CAMERA permission does not need to be requested unless it is declared
 252        // in AndroidManifest.xml. This plugin does not declare it, but others may and so we must
 253        // check the package info to determine if the permission is present.
 254
 255        if (!takePicturePermission) {
 256            takePicturePermission = true;
 257            try {
 258                PackageManager packageManager = this.cordova.getActivity().getPackageManager();
 259                String[] permissionsInPackage = packageManager.getPackageInfo(this.cordova.getActivity().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions;
 260                if (permissionsInPackage != null) {
 261                    for (String permission : permissionsInPackage) {
 262                        if (permission.equals(Manifest.permission.CAMERA)) {
 263                            takePicturePermission = false;
 264                            break;
 265                        }
 266                    }
 267                }
 268            } catch (NameNotFoundException e) {
 269                // We are requesting the info for our package, so this should
 270                // never be caught
 271            }
 272        }
 273
 274        if (takePicturePermission && saveAlbumPermission) {
 275            takePicture(returnType, encodingType);
 276        } else if (saveAlbumPermission && !takePicturePermission) {
 277            PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.CAMERA);
 278        } else if (!saveAlbumPermission && takePicturePermission) {
 279            PermissionHelper.requestPermission(this, TAKE_PIC_SEC, Manifest.permission.READ_EXTERNAL_STORAGE);
 280        } else {
 281            PermissionHelper.requestPermissions(this, TAKE_PIC_SEC, permissions);
 282        }
 283    }
 284
 285    public void takePicture(int returnType, int encodingType)
 286    {
 287        // Save the number of images currently on disk for later
 288        this.numPics = queryImgDB(whichContentStore()).getCount();
 289
 290        // Let's use the intent and see what happens
 291        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 292
 293        // Specify file so that large image is captured and returned
 294        File photo = createCaptureFile(encodingType);
 295        this.imageUri = new CordovaUri(FileProvider.getUriForFile(cordova.getActivity(),
 296                applicationId + ".provider",
 297                photo));
 298        intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri.getCorrectUri());
 299        //We can write to this URI, this will hopefully allow us to write files to get to the next step
 300        intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 301
 302        if (this.cordova != null) {
 303            // Let's check to make sure the camera is actually installed. (Legacy Nexus 7 code)
 304            PackageManager mPm = this.cordova.getActivity().getPackageManager();
 305            if(intent.resolveActivity(mPm) != null)
 306            {
 307
 308                this.cordova.startActivityForResult((CordovaPlugin) this, intent, (CAMERA + 1) * 16 + returnType + 1);
 309            }
 310            else
 311            {
 312                LOG.d(LOG_TAG, "Error: You don't have a default camera.  Your device may not be CTS complaint.");
 313            }
 314        }
 315//        else
 316//            LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for this to work correctly. Please implement it in your activity");
 317    }
 318
 319    /**
 320     * Create a file in the applications temporary directory based upon the supplied encoding.
 321     *
 322     * @param encodingType of the image to be taken
 323     * @return a File object pointing to the temporary picture
 324     */
 325    private File createCaptureFile(int encodingType) {
 326        return createCaptureFile(encodingType, "");
 327    }
 328
 329    /**
 330     * Create a file in the applications temporary directory based upon the supplied encoding.
 331     *
 332     * @param encodingType of the image to be taken
 333     * @param fileName or resultant File object.
 334     * @return a File object pointing to the temporary picture
 335     */
 336    private File createCaptureFile(int encodingType, String fileName) {
 337        if (fileName.isEmpty()) {
 338            fileName = ".Pic";
 339        }
 340
 341        if (encodingType == JPEG) {
 342            fileName = fileName + ".jpg";
 343        } else if (encodingType == PNG) {
 344            fileName = fileName + ".png";
 345        } else {
 346            throw new IllegalArgumentException("Invalid Encoding Type: " + encodingType);
 347        }
 348
 349        return new File(getTempDirectoryPath(), fileName);
 350    }
 351
 352
 353
 354    /**
 355     * Get image from photo library.
 356     *
 357     * @param srcType           The album to get image from.
 358     * @param returnType        Set the type of image to return.
 359     * @param encodingType
 360     */
 361    // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do!
 362    // TODO: Images from kitkat filechooser not going into crop function
 363    public void getImage(int srcType, int returnType, int encodingType) {
 364        Intent intent = new Intent();
 365        String title = GET_PICTURE;
 366        croppedUri = null;
 367        if (this.mediaType == PICTURE) {
 368            intent.setType("image/*");
 369            if (this.allowEdit) {
 370                intent.setAction(Intent.ACTION_PICK);
 371                intent.putExtra("crop", "true");
 372                if (targetWidth > 0) {
 373                    intent.putExtra("outputX", targetWidth);
 374                }
 375                if (targetHeight > 0) {
 376                    intent.putExtra("outputY", targetHeight);
 377                }
 378                if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
 379                    intent.putExtra("aspectX", 1);
 380                    intent.putExtra("aspectY", 1);
 381                }
 382                File photo = createCaptureFile(JPEG);
 383                croppedUri = Uri.fromFile(photo);
 384                intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, croppedUri);
 385            } else {
 386                intent.setAction(Intent.ACTION_GET_CONTENT);
 387                intent.addCategory(Intent.CATEGORY_OPENABLE);
 388            }
 389        } else if (this.mediaType == VIDEO) {
 390            intent.setType("video/*");
 391            title = GET_VIDEO;
 392            intent.setAction(Intent.ACTION_GET_CONTENT);
 393            intent.addCategory(Intent.CATEGORY_OPENABLE);
 394        } else if (this.mediaType == ALLMEDIA) {
 395            // I wanted to make the type 'image/*, video/*' but this does not work on all versions
 396            // of android so I had to go with the wildcard search.
 397            intent.setType("*/*");
 398            title = GET_All;
 399            intent.setAction(Intent.ACTION_GET_CONTENT);
 400            intent.addCategory(Intent.CATEGORY_OPENABLE);
 401        }
 402        if (this.cordova != null) {
 403            this.cordova.startActivityForResult((CordovaPlugin) this, Intent.createChooser(intent,
 404                    new String(title)), (srcType + 1) * 16 + returnType + 1);
 405        }
 406    }
 407
 408
 409  /**
 410   * Brings up the UI to perform crop on passed image URI
 411   *
 412   * @param picUri
 413   */
 414  private void performCrop(Uri picUri, int destType, Intent cameraIntent) {
 415    try {
 416        Intent cropIntent = new Intent("com.android.camera.action.CROP");
 417        // indicate image type and Uri
 418        cropIntent.setDataAndType(picUri, "image/*");
 419        // set crop properties
 420        cropIntent.putExtra("crop", "true");
 421
 422
 423        // indicate output X and Y
 424        if (targetWidth > 0) {
 425          cropIntent.putExtra("outputX", targetWidth);
 426        }
 427        if (targetHeight > 0) {
 428          cropIntent.putExtra("outputY", targetHeight);
 429        }
 430        if (targetHeight > 0 && targetWidth > 0 && targetWidth == targetHeight) {
 431          cropIntent.putExtra("aspectX", 1);
 432          cropIntent.putExtra("aspectY", 1);
 433        }
 434        // create new file handle to get full resolution crop
 435        croppedUri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
 436        cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 437        cropIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 438        cropIntent.putExtra("output", croppedUri);
 439
 440
 441        // start the activity - we handle returning in onActivityResult
 442
 443        if (this.cordova != null) {
 444            this.cordova.startActivityForResult((CordovaPlugin) this,
 445                cropIntent, CROP_CAMERA + destType);
 446        }
 447    } catch (ActivityNotFoundException anfe) {
 448      LOG.e(LOG_TAG, "Crop operation not supported on this device");
 449      try {
 450          processResultFromCamera(destType, cameraIntent);
 451      }
 452      catch (IOException e)
 453      {
 454          e.printStackTrace();
 455          LOG.e(LOG_TAG, "Unable to write to file");
 456      }
 457    }
 458  }
 459
 460    /**
 461     * Applies all needed transformation to the image received from the camera.
 462     *
 463     * @param destType          In which form should we return the image
 464     * @param intent            An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
 465     */
 466    private void processResultFromCamera(int destType, Intent intent) throws IOException {
 467        int rotate = 0;
 468
 469        // Create an ExifHelper to save the exif data that is lost during compression
 470        ExifHelper exif = new ExifHelper();
 471
 472        String sourcePath = (this.allowEdit && this.croppedUri != null) ?
 473                FileHelper.stripFileProtocol(this.croppedUri.toString()) :
 474                this.imageUri.getFilePath();
 475
 476
 477        if (this.encodingType == JPEG) {
 478            try {
 479                //We don't support PNG, so let's not pretend we do
 480                exif.createInFile(sourcePath);
 481                exif.readExifData();
 482                rotate = exif.getOrientation();
 483
 484            } catch (IOException e) {
 485                e.printStackTrace();
 486            }
 487        }
 488
 489        Bitmap bitmap = null;
 490        Uri galleryUri = null;
 491
 492        // CB-5479 When this option is given the unchanged image should be saved
 493        // in the gallery and the modified image is saved in the temporary
 494        // directory
 495        if (this.saveToPhotoAlbum) {
 496            galleryUri = Uri.fromFile(new File(getPicturesPath()));
 497
 498            if (this.allowEdit && this.croppedUri != null) {
 499                writeUncompressedImage(croppedUri, galleryUri);
 500            } else {
 501                Uri imageUri = this.imageUri.getFileUri();
 502                writeUncompressedImage(imageUri, galleryUri);
 503            }
 504
 505            refreshGallery(galleryUri);
 506        }
 507
 508        // If sending base64 image back
 509        if (destType == DATA_URL) {
 510            bitmap = getScaledAndRotatedBitmap(sourcePath);
 511
 512            if (bitmap == null) {
 513                // Try to get the bitmap from intent.
 514                bitmap = (Bitmap) intent.getExtras().get("data");
 515            }
 516
 517            // Double-check the bitmap.
 518            if (bitmap == null) {
 519                LOG.d(LOG_TAG, "I either have a null image path or bitmap");
 520                this.failPicture("Unable to create bitmap!");
 521                return;
 522            }
 523
 524
 525            this.processPicture(bitmap, this.encodingType);
 526
 527            if (!this.saveToPhotoAlbum) {
 528                checkForDuplicateImage(DATA_URL);
 529            }
 530        }
 531
 532        // If sending filename back
 533        else if (destType == FILE_URI || destType == NATIVE_URI) {
 534            // If all this is true we shouldn't compress the image.
 535            if (this.targetHeight == -1 && this.targetWidth == -1 && this.mQuality == 100 &&
 536                    !this.correctOrientation) {
 537
 538                // If we saved the uncompressed photo to the album, we can just
 539                // return the URI we already created
 540                if (this.saveToPhotoAlbum) {
 541                    this.callbackContext.success(galleryUri.toString());
 542                } else {
 543                    Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
 544
 545                    if (this.allowEdit && this.croppedUri != null) {
 546                        Uri croppedUri = Uri.fromFile(new File(getFileNameFromUri(this.croppedUri)));
 547                        writeUncompressedImage(croppedUri, uri);
 548                    } else {
 549                        Uri imageUri = this.imageUri.getFileUri();
 550                        writeUncompressedImage(imageUri, uri);
 551                    }
 552
 553                    this.callbackContext.success(uri.toString());
 554                }
 555            } else {
 556                Uri uri = Uri.fromFile(createCaptureFile(this.encodingType, System.currentTimeMillis() + ""));
 557                bitmap = getScaledAndRotatedBitmap(sourcePath);
 558
 559                // Double-check the bitmap.
 560                if (bitmap == null) {
 561                    LOG.d(LOG_TAG, "I either have a null image path or bitmap");
 562                    this.failPicture("Unable to create bitmap!");
 563                    return;
 564                }
 565
 566
 567                // Add compressed version of captured image to returned media store Uri
 568                OutputStream os = this.cordova.getActivity().getContentResolver().openOutputStream(uri);
 569                CompressFormat compressFormat = encodingType == JPEG ?
 570                        CompressFormat.JPEG :
 571                        CompressFormat.PNG;
 572
 573                bitmap.compress(compressFormat, this.mQuality, os);
 574                os.close();
 575
 576                // Restore exif data to file
 577                if (this.encodingType == JPEG) {
 578                    String exifPath;
 579                    exifPath = uri.getPath();
 580                    //We just finished rotating it by an arbitrary orientation, just make sure it's normal
 581                    if(rotate != ExifInterface.ORIENTATION_NORMAL)
 582                        exif.resetOrientation();
 583                    exif.createOutFile(exifPath);
 584                    exif.writeExifData();
 585                }
 586
 587                // Send Uri back to JavaScript for viewing image
 588                this.callbackContext.success(uri.toString());
 589
 590            }
 591        } else {
 592            throw new IllegalStateException();
 593        }
 594
 595        this.cleanup(FILE_URI, this.imageUri.getFileUri(), galleryUri, bitmap);
 596        bitmap = null;
 597    }
 598
 599    private String getPicturesPath() {
 600        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
 601        String imageFileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
 602        File storageDir = Environment.getExternalStoragePublicDirectory(
 603                Environment.DIRECTORY_PICTURES);
 604        String galleryPath = storageDir.getAbsolutePath() + "/" + imageFileName;
 605        return galleryPath;
 606    }
 607
 608    private void refreshGallery(Uri contentUri) {
 609        Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
 610        mediaScanIntent.setData(contentUri);
 611        this.cordova.getActivity().sendBroadcast(mediaScanIntent);
 612    }
 613
 614    /**
 615     * Converts output image format int value to string value of mime type.
 616     * @param outputFormat int Output format of camera API.
 617     *                     Must be value of either JPEG or PNG constant
 618     * @return String String value of mime type or empty string if mime type is not supported
 619     */
 620    private String getMimetypeForFormat(int outputFormat) {
 621        if (outputFormat == PNG) return "image/png";
 622        if (outputFormat == JPEG) return "image/jpeg";
 623        return "";
 624    }
 625
 626
 627    private String outputModifiedBitmap(Bitmap bitmap, Uri uri) throws IOException {
 628        // Some content: URIs do not map to file paths (e.g. picasa).
 629        String realPath = FileHelper.getRealPath(uri, this.cordova);
 630
 631        // Get filename from uri
 632        String fileName = realPath != null ?
 633                realPath.substring(realPath.lastIndexOf('/') + 1) :
 634                "modified." + (this.encodingType == JPEG ? "jpg" : "png");
 635
 636        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
 637        //String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
 638        String modifiedPath = getTempDirectoryPath() + "/" + fileName;
 639
 640        OutputStream os = new FileOutputStream(modifiedPath);
 641        CompressFormat compressFormat = this.encodingType == JPEG ?
 642                CompressFormat.JPEG :
 643                CompressFormat.PNG;
 644
 645        bitmap.compress(compressFormat, this.mQuality, os);
 646        os.close();
 647
 648        if (exifData != null && this.encodingType == JPEG) {
 649            try {
 650                if (this.correctOrientation && this.orientationCorrected) {
 651                    exifData.resetOrientation();
 652                }
 653                exifData.createOutFile(modifiedPath);
 654                exifData.writeExifData();
 655                exifData = null;
 656            } catch (IOException e) {
 657                e.printStackTrace();
 658            }
 659        }
 660        return modifiedPath;
 661    }
 662
 663
 664    /**
 665     * Applies all needed transformation to the image received from the gallery.
 666     *
 667     * @param destType In which form should we return the image
 668     * @param intent   An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
 669     */
 670    private void processResultFromGallery(int destType, Intent intent) {
 671        Uri uri = intent.getData();
 672        if (uri == null) {
 673            if (croppedUri != null) {
 674                uri = croppedUri;
 675            } else {
 676                this.failPicture("null data from photo library");
 677                return;
 678            }
 679        }
 680        int rotate = 0;
 681
 682        String fileLocation = FileHelper.getRealPath(uri, this.cordova);
 683        LOG.d(LOG_TAG, "File locaton is: " + fileLocation);
 684
 685        // If you ask for video or all media type you will automatically get back a file URI
 686        // and there will be no attempt to resize any returned data
 687        if (this.mediaType != PICTURE) {
 688            this.callbackContext.success(fileLocation);
 689        }
 690        else {
 691            String uriString = uri.toString();
 692            // Get the path to the image. Makes loading so much easier.
 693            String mimeType = FileHelper.getMimeType(uriString, this.cordova);
 694
 695            // This is a special case to just return the path as no scaling,
 696            // rotating, nor compressing needs to be done
 697            if (this.targetHeight == -1 && this.targetWidth == -1 &&
 698                    (destType == FILE_URI || destType == NATIVE_URI) && !this.correctOrientation &&
 699                    mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
 700            {
 701                this.callbackContext.success(uriString);
 702            } else {
 703                // If we don't have a valid image so quit.
 704                if (!("image/jpeg".equalsIgnoreCase(mimeType) || "image/png".equalsIgnoreCase(mimeType))) {
 705                    LOG.d(LOG_TAG, "I either have a null image path or bitmap");
 706                    this.failPicture("Unable to retrieve path to picture!");
 707                    return;
 708                }
 709                Bitmap bitmap = null;
 710                try {
 711                    bitmap = getScaledAndRotatedBitmap(uriString);
 712                } catch (IOException e) {
 713                    e.printStackTrace();
 714                }
 715                if (bitmap == null) {
 716                    LOG.d(LOG_TAG, "I either have a null image path or bitmap");
 717                    this.failPicture("Unable to create bitmap!");
 718                    return;
 719                }
 720
 721                // If sending base64 image back
 722                if (destType == DATA_URL) {
 723                    this.processPicture(bitmap, this.encodingType);
 724                }
 725
 726                // If sending filename back
 727                else if (destType == FILE_URI || destType == NATIVE_URI) {
 728                    // Did we modify the image?
 729                    if ( (this.targetHeight > 0 && this.targetWidth > 0) ||
 730                            (this.correctOrientation && this.orientationCorrected) ||
 731                            !mimeType.equalsIgnoreCase(getMimetypeForFormat(encodingType)))
 732                    {
 733                        try {
 734                            String modifiedPath = this.outputModifiedBitmap(bitmap, uri);
 735                            // The modified image is cached by the app in order to get around this and not have to delete you
 736                            // application cache I'm adding the current system time to the end of the file url.
 737                            this.callbackContext.success("file://" + modifiedPath + "?" + System.currentTimeMillis());
 738
 739                        } catch (Exception e) {
 740                            e.printStackTrace();
 741                            this.failPicture("Error retrieving image.");
 742                        }
 743                    } else {
 744                        this.callbackContext.success(fileLocation);
 745                    }
 746                }
 747                if (bitmap != null) {
 748                    bitmap.recycle();
 749                    bitmap = null;
 750                }
 751                System.gc();
 752            }
 753        }
 754    }
 755
 756    /**
 757     * Called when the camera view exits.
 758     *
 759     * @param requestCode The request code originally supplied to startActivityForResult(),
 760     *                    allowing you to identify who this result came from.
 761     * @param resultCode  The integer result code returned by the child activity through its setResult().
 762     * @param intent      An Intent, which can return result data to the caller (various data can be attached to Intent "extras").
 763     */
 764    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
 765
 766        // Get src and dest types from request code for a Camera Activity
 767        int srcType = (requestCode / 16) - 1;
 768        int destType = (requestCode % 16) - 1;
 769
 770        // If Camera Crop
 771        if (requestCode >= CROP_CAMERA) {
 772            if (resultCode == Activity.RESULT_OK) {
 773
 774                // Because of the inability to pass through multiple intents, this hack will allow us
 775                // to pass arcane codes back.
 776                destType = requestCode - CROP_CAMERA;
 777                try {
 778                    processResultFromCamera(destType, intent);
 779                } catch (IOException e) {
 780                    e.printStackTrace();
 781                    LOG.e(LOG_TAG, "Unable to write to file");
 782                }
 783
 784            }// If cancelled
 785            else if (resultCode == Activity.RESULT_CANCELED) {
 786                this.failPicture("Camera cancelled.");
 787            }
 788
 789            // If something else
 790            else {
 791                this.failPicture("Did not complete!");
 792            }
 793        }
 794        // If CAMERA
 795        else if (srcType == CAMERA) {
 796            // If image available
 797            if (resultCode == Activity.RESULT_OK) {
 798                try {
 799                    if (this.allowEdit) {
 800                        Uri tmpFile = FileProvider.getUriForFile(cordova.getActivity(),
 801                                applicationId + ".provider",
 802                                createCaptureFile(this.encodingType));
 803                        performCrop(tmpFile, destType, intent);
 804                    } else {
 805                        this.processResultFromCamera(destType, intent);
 806                    }
 807                } catch (IOException e) {
 808                    e.printStackTrace();
 809                    this.failPicture("Error capturing image.");
 810                }
 811            }
 812
 813            // If cancelled
 814            else if (resultCode == Activity.RESULT_CANCELED) {
 815                this.failPicture("Camera cancelled.");
 816            }
 817
 818            // If something else
 819            else {
 820                this.failPicture("Did not complete!");
 821            }
 822        }
 823        // If retrieving photo from library
 824        else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) {
 825            if (resultCode == Activity.RESULT_OK && intent != null) {
 826                final Intent i = intent;
 827                final int finalDestType = destType;
 828                cordova.getThreadPool().execute(new Runnable() {
 829                    public void run() {
 830                        processResultFromGallery(finalDestType, i);
 831                    }
 832                });
 833            } else if (resultCode == Activity.RESULT_CANCELED) {
 834                this.failPicture("Selection cancelled.");
 835            } else {
 836                this.failPicture("Selection did not complete!");
 837            }
 838        }
 839    }
 840
 841    private int exifToDegrees(int exifOrientation) {
 842        if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
 843            return 90;
 844        } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
 845            return 180;
 846        } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
 847            return 270;
 848        } else {
 849            return 0;
 850        }
 851    }
 852    
 853    /**
 854     * Write an inputstream to local disk
 855     *
 856     * @param fis - The InputStream to write
 857     * @param dest - Destination on disk to write to
 858     * @throws FileNotFoundException
 859     * @throws IOException
 860     */
 861    private void writeUncompressedImage(InputStream fis, Uri dest) throws FileNotFoundException,
 862            IOException {
 863        OutputStream os = null;
 864        try {
 865            os = this.cordova.getActivity().getContentResolver().openOutputStream(dest);
 866            byte[] buffer = new byte[4096];
 867            int len;
 868            while ((len = fis.read(buffer)) != -1) {
 869                os.write(buffer, 0, len);
 870            }
 871            os.flush();
 872        } finally {
 873            if (os != null) {
 874                try {
 875                    os.close();
 876                } catch (IOException e) {
 877                    LOG.d(LOG_TAG, "Exception while closing output stream.");
 878                }
 879            }
 880            if (fis != null) {
 881                try {
 882                    fis.close();
 883                } catch (IOException e) {
 884                    LOG.d(LOG_TAG, "Exception while closing file input stream.");
 885                }
 886            }
 887        }
 888    }
 889    /**
 890     * In the special case where the default width, height and quality are unchanged
 891     * we just write the file out to disk saving the expensive Bitmap.compress function.
 892     *
 893     * @param src
 894     * @throws FileNotFoundException
 895     * @throws IOException
 896     */
 897    private void writeUncompressedImage(Uri src, Uri dest) throws FileNotFoundException,
 898            IOException {
 899
 900        FileInputStream fis = new FileInputStream(FileHelper.stripFileProtocol(src.toString()));
 901        writeUncompressedImage(fis, dest);
 902
 903    }
 904
 905    /**
 906     * Create entry in media store for image
 907     *
 908     * @return uri
 909     */
 910    private Uri getUriFromMediaStore() {
 911        ContentValues values = new ContentValues();
 912        values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
 913        Uri uri;
 914        try {
 915            uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
 916        } catch (RuntimeException e) {
 917            LOG.d(LOG_TAG, "Can't write to external media storage.");
 918            try {
 919                uri = this.cordova.getActivity().getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
 920            } catch (RuntimeException ex) {
 921                LOG.d(LOG_TAG, "Can't write to internal media storage.");
 922                return null;
 923            }
 924        }
 925        return uri;
 926    }
 927
 928    /**
 929     * Return a scaled and rotated bitmap based on the target width and height
 930     *
 931     * @param imageUrl
 932     * @return
 933     * @throws IOException
 934     */
 935    private Bitmap getScaledAndRotatedBitmap(String imageUrl) throws IOException {
 936        // If no new width or height were specified, and orientation is not needed return the original bitmap
 937        if (this.targetWidth <= 0 && this.targetHeight <= 0 && !(this.correctOrientation)) {
 938            InputStream fileStream = null;
 939            Bitmap image = null;
 940            try {
 941                fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
 942                image = BitmapFactory.decodeStream(fileStream);
 943            } finally {
 944                if (fileStream != null) {
 945                    try {
 946                        fileStream.close();
 947                    } catch (IOException e) {
 948                        LOG.d(LOG_TAG, "Exception while closing file input stream.");
 949                    }
 950                }
 951            }
 952            return image;
 953        }
 954
 955
 956        /*  Copy the inputstream to a temporary file on the device.
 957            We then use this temporary file to determine the width/height/orientation.
 958            This is the only way to determine the orientation of the photo coming from 3rd party providers (Google Drive, Dropbox,etc)
 959            This also ensures we create a scaled bitmap with the correct orientation
 960
 961             We delete the temporary file once we are done
 962         */
 963        File localFile = null;
 964        Uri galleryUri = null;
 965        int rotate = 0;
 966        try {
 967            InputStream fileStream = FileHelper.getInputStreamFromUriString(imageUrl, cordova);
 968            if (fileStream != null) {
 969                // Generate a temporary file
 970                String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
 971                String fileName = "IMG_" + timeStamp + (this.encodingType == JPEG ? ".jpg" : ".png");
 972                localFile = new File(getTempDirectoryPath() + fileName);
 973                galleryUri = Uri.fromFile(localFile);
 974                writeUncompressedImage(fileStream, galleryUri);
 975                try {
 976                    String mimeType = FileHelper.getMimeType(imageUrl.toString(), cordova);
 977                    if ("image/jpeg".equalsIgnoreCase(mimeType)) {
 978                        //  ExifInterface doesn't like the file:// prefix
 979                        String filePath = galleryUri.toString().replace("file://", "");
 980                        // read exifData of source
 981                        exifData = new ExifHelper();
 982                        exifData.createInFile(filePath);
 983                        // Use ExifInterface to pull rotation information
 984                        if (this.correctOrientation) {
 985                            ExifInterface exif = new ExifInterface(filePath);
 986                            rotate = exifToDegrees(exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED));
 987                        }
 988                    }
 989                } catch (Exception oe) {
 990                    LOG.w(LOG_TAG,"Unable to read Exif data: "+ oe.toString());
 991                    rotate = 0;
 992                }
 993            }
 994        }
 995        catch (Exception e)
 996        {
 997            LOG.e(LOG_TAG,"Exception while getting input stream: "+ e.toString());
 998            return null;
 999        }
1000
1001
1002
1003        try {
1004            // figure out the original width and height of the image
1005            BitmapFactory.Options options = new BitmapFactory.Options();
1006            options.inJustDecodeBounds = true;
1007            InputStream fileStream = null;
1008            try {
1009                fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
1010                BitmapFactory.decodeStream(fileStream, null, options);
1011            } finally {
1012                if (fileStream != null) {
1013                    try {
1014                        fileStream.close();
1015                    } catch (IOException e) {
1016                        LOG.d(LOG_TAG, "Exception while closing file input stream.");
1017                    }
1018                }
1019            }
1020
1021
1022            //CB-2292: WTF? Why is the width null?
1023            if (options.outWidth == 0 || options.outHeight == 0) {
1024                return null;
1025            }
1026
1027            // User didn't specify output dimensions, but they need orientation
1028            if (this.targetWidth <= 0 && this.targetHeight <= 0) {
1029                this.targetWidth = options.outWidth;
1030                this.targetHeight = options.outHeight;
1031            }
1032
1033            // Setup target width/height based on orientation
1034            int rotatedWidth, rotatedHeight;
1035            boolean rotated= false;
1036            if (rotate == 90 || rotate == 270) {
1037                rotatedWidth = options.outHeight;
1038                rotatedHeight = options.outWidth;
1039                rotated = true;
1040            } else {
1041                rotatedWidth = options.outWidth;
1042                rotatedHeight = options.outHeight;
1043            }
1044
1045            // determine the correct aspect ratio
1046            int[] widthHeight = calculateAspectRatio(rotatedWidth, rotatedHeight);
1047
1048
1049            // Load in the smallest bitmap possible that is closest to the size we want
1050            options.inJustDecodeBounds = false;
1051            options.inSampleSize = calculateSampleSize(rotatedWidth, rotatedHeight,  widthHeight[0], widthHeight[1]);
1052            Bitmap unscaledBitmap = null;
1053            try {
1054                fileStream = FileHelper.getInputStreamFromUriString(galleryUri.toString(), cordova);
1055                unscaledBitmap = BitmapFactory.decodeStream(fileStream, null, options);
1056            } finally {
1057                if (fileStream != null) {
1058                    try {
1059                        fileStream.close();
1060                    } catch (IOException e) {
1061                        LOG.d(LOG_TAG, "Exception while closing file input stream.");
1062                    }
1063                }
1064            }
1065            if (unscaledBitmap == null) {
1066                return null;
1067            }
1068
1069            int scaledWidth = (!rotated) ? widthHeight[0] : widthHeight[1];
1070            int scaledHeight = (!rotated) ? widthHeight[1] : widthHeight[0];
1071
1072            Bitmap scaledBitmap = Bitmap.createScaledBitmap(unscaledBitmap, scaledWidth, scaledHeight, true);
1073            if (scaledBitmap != unscaledBitmap) {
1074                unscaledBitmap.recycle();
1075                unscaledBitmap = null;
1076            }
1077            if (this.correctOrientation && (rotate != 0)) {
1078                Matrix matrix = new Matrix();
1079                matrix.setRotate(rotate);
1080                try {
1081                    scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
1082                    this.orientationCorrected = true;
1083                } catch (OutOfMemoryError oom) {
1084                    this.orientationCorrected = false;
1085                }
1086            }
1087            return scaledBitmap;
1088        }
1089        finally {
1090            // delete the temporary copy
1091            if (localFile != null) {
1092                localFile.delete();
1093            }
1094        }
1095
1096    }
1097
1098    /**
1099     * Maintain the aspect ratio so the resulting image does not look smooshed
1100     *
1101     * @param origWidth
1102     * @param origHeight
1103     * @return
1104     */
1105    public int[] calculateAspectRatio(int origWidth, int origHeight) {
1106        int newWidth = this.targetWidth;
1107        int newHeight = this.targetHeight;
1108
1109        // If no new width or height were specified return the original bitmap
1110        if (newWidth <= 0 && newHeight <= 0) {
1111            newWidth = origWidth;
1112            newHeight = origHeight;
1113        }
1114        // Only the width was specified
1115        else if (newWidth > 0 && newHeight <= 0) {
1116            newHeight = (int)((double)(newWidth / (double)origWidth) * origHeight);
1117        }
1118        // only the height was specified
1119        else if (newWidth <= 0 && newHeight > 0) {
1120            newWidth = (int)((double)(newHeight / (double)origHeight) * origWidth);
1121        }
1122        // If the user specified both a positive width and height
1123        // (potentially different aspect ratio) then the width or height is
1124        // scaled so that the image fits while maintaining aspect ratio.
1125        // Alternatively, the specified width and height could have been
1126        // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
1127        // would result in whitespace in the new image.
1128        else {
1129            double newRatio = newWidth / (double) newHeight;
1130            double origRatio = origWidth / (double) origHeight;
1131
1132            if (origRatio > newRatio) {
1133                newHeight = (newWidth * origHeight) / origWidth;
1134            } else if (origRatio < newRatio) {
1135                newWidth = (newHeight * origWidth) / origHeight;
1136            }
1137        }
1138
1139        int[] retval = new int[2];
1140        retval[0] = newWidth;
1141        retval[1] = newHeight;
1142        return retval;
1143    }
1144
1145    /**
1146     * Figure out what ratio we can load our image into memory at while still being bigger than
1147     * our desired width and height
1148     *
1149     * @param srcWidth
1150     * @param srcHeight
1151     * @param dstWidth
1152     * @param dstHeight
1153     * @return
1154     */
1155    public static int calculateSampleSize(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
1156        final float srcAspect = (float) srcWidth / (float) srcHeight;
1157        final float dstAspect = (float) dstWidth / (float) dstHeight;
1158
1159        if (srcAspect > dstAspect) {
1160            return srcWidth / dstWidth;
1161        } else {
1162            return srcHeight / dstHeight;
1163        }
1164    }
1165
1166    /**
1167     * Creates a cursor that can be used to determine how many images we have.
1168     *
1169     * @return a cursor
1170     */
1171    private Cursor queryImgDB(Uri contentStore) {
1172        return this.cordova.getActivity().getContentResolver().query(
1173                contentStore,
1174                new String[]{MediaStore.Images.Media._ID},
1175                null,
1176                null,
1177                null);
1178    }
1179
1180    /**
1181     * Cleans up after picture taking. Checking for duplicates and that kind of stuff.
1182     *
1183     * @param newImage
1184     */
1185    private void cleanup(int imageType, Uri oldImage, Uri newImage, Bitmap bitmap) {
1186        if (bitmap != null) {
1187            bitmap.recycle();
1188        }
1189
1190        // Clean up initial camera-written image file.
1191        (new File(FileHelper.stripFileProtocol(oldImage.toString()))).delete();
1192
1193        checkForDuplicateImage(imageType);
1194        // Scan for the gallery to update pic refs in gallery
1195        if (this.saveToPhotoAlbum && newImage != null) {
1196            this.scanForGallery(newImage);
1197        }
1198
1199        System.gc();
1200    }
1201
1202    /**
1203     * Used to find out if we are in a situation where the Camera Intent adds to images
1204     * to the content store. If we are using a FILE_URI and the number of images in the DB
1205     * increases by 2 we have a duplicate, when using a DATA_URL the number is 1.
1206     *
1207     * @param type FILE_URI or DATA_URL
1208     */
1209    private void checkForDuplicateImage(int type) {
1210        int diff = 1;
1211        Uri contentStore = whichContentStore();
1212        Cursor cursor = queryImgDB(contentStore);
1213        int currentNumOfImages = cursor.getCount();
1214
1215        if (type == FILE_URI && this.saveToPhotoAlbum) {
1216            diff = 2;
1217        }
1218
1219        // delete the duplicate file if the difference is 2 for file URI or 1 for Data URL
1220        if ((currentNumOfImages - numPics) == diff) {
1221            cursor.moveToLast();
1222            int id = Integer.valueOf(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
1223            if (diff == 2) {
1224                id--;
1225            }
1226            Uri uri = Uri.parse(contentStore + "/" + id);
1227            this.cordova.getActivity().getContentResolver().delete(uri, null, null);
1228            cursor.close();
1229        }
1230    }
1231
1232    /**
1233     * Determine if we are storing the images in internal or external storage
1234     *
1235     * @return Uri
1236     */
1237    private Uri whichContentStore() {
1238        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1239            return android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
1240        } else {
1241            return android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI;
1242        }
1243    }
1244
1245    /**
1246     * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript.
1247     *
1248     * @param bitmap
1249     */
1250    public void processPicture(Bitmap bitmap, int encodingType) {
1251        ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream();
1252        CompressFormat compressFormat = encodingType == JPEG ?
1253                CompressFormat.JPEG :
1254                CompressFormat.PNG;
1255
1256        try {
1257            if (bitmap.compress(compressFormat, mQuality, jpeg_data)) {
1258                byte[] code = jpeg_data.toByteArray();
1259                byte[] output = Base64.encode(code, Base64.NO_WRAP);
1260                String js_out = new String(output);
1261                this.callbackContext.success(js_out);
1262                js_out = null;
1263                output = null;
1264                code = null;
1265            }
1266        } catch (Exception e) {
1267            this.failPicture("Error compressing image.");
1268        }
1269        jpeg_data = null;
1270    }
1271
1272    /**
1273     * Send error message to JavaScript.
1274     *
1275     * @param err
1276     */
1277    public void failPicture(String err) {
1278        this.callbackContext.error(err);
1279    }
1280
1281    private void scanForGallery(Uri newImage) {
1282        this.scanMe = newImage;
1283        if (this.conn != null) {
1284            this.conn.disconnect();
1285        }
1286        this.conn = new MediaScannerConnection(this.cordova.getActivity().getApplicationContext(), this);
1287        conn.connect();
1288    }
1289
1290    public void onMediaScannerConnected() {
1291        try {
1292            this.conn.scanFile(this.scanMe.toString(), "image/*");
1293        } catch (java.lang.IllegalStateException e) {
1294            LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture");
1295        }
1296
1297    }
1298
1299    public void onScanCompleted(String path, Uri uri) {
1300        this.conn.disconnect();
1301    }
1302
1303
1304    public void onRequestPermissionResult(int requestCode, String[] permissions,
1305                                          int[] grantResults) throws JSONException {
1306        for (int r : grantResults) {
1307            if (r == PackageManager.PERMISSION_DENIED) {
1308                this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
1309                return;
1310            }
1311        }
1312        switch (requestCode) {
1313            case TAKE_PIC_SEC:
1314                takePicture(this.destType, this.encodingType);
1315                break;
1316            case SAVE_TO_ALBUM_SEC:
1317                this.getImage(this.srcType, this.destType, this.encodingType);
1318                break;
1319        }
1320    }
1321
1322    /**
1323     * Taking or choosing a picture launches another Activity, so we need to implement the
1324     * save/restore APIs to handle the case where the CordovaActivity is killed by the OS
1325     * before we get the launched Activity's result.
1326     */
1327    public Bundle onSaveInstanceState() {
1328        Bundle state = new Bundle();
1329        state.putInt("destType", this.destType);
1330        state.putInt("srcType", this.srcType);
1331        state.putInt("mQuality", this.mQuality);
1332        state.putInt("targetWidth", this.targetWidth);
1333        state.putInt("targetHeight", this.targetHeight);
1334        state.putInt("encodingType", this.encodingType);
1335        state.putInt("mediaType", this.mediaType);
1336        state.putInt("numPics", this.numPics);
1337        state.putBoolean("allowEdit", this.allowEdit);
1338        state.putBoolean("correctOrientation", this.correctOrientation);
1339        state.putBoolean("saveToPhotoAlbum", this.saveToPhotoAlbum);
1340
1341        if (this.croppedUri != null) {
1342            state.putString("croppedUri", this.croppedUri.toString());
1343        }
1344
1345        if (this.imageUri != null) {
1346            state.putString("imageUri", this.imageUri.getFileUri().toString());
1347        }
1348
1349        return state;
1350    }
1351
1352    public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {
1353        this.destType = state.getInt("destType");
1354        this.srcType = state.getInt("srcType");
1355        this.mQuality = state.getInt("mQuality");
1356        this.targetWidth = state.getInt("targetWidth");
1357        this.targetHeight = state.getInt("targetHeight");
1358        this.encodingType = state.getInt("encodingType");
1359        this.mediaType = state.getInt("mediaType");
1360        this.numPics = state.getInt("numPics");
1361        this.allowEdit = state.getBoolean("allowEdit");
1362        this.correctOrientation = state.getBoolean("correctOrientation");
1363        this.saveToPhotoAlbum = state.getBoolean("saveToPhotoAlbum");
1364
1365        if (state.containsKey("croppedUri")) {
1366            this.croppedUri = Uri.parse(state.getString("croppedUri"));
1367        }
1368
1369        if (state.containsKey("imageUri")) {
1370            //I have no idea what type of URI is being passed in
1371            this.imageUri = new CordovaUri(Uri.parse(state.getString("imageUri")));
1372        }
1373
1374        this.callbackContext = callbackContext;
1375    }
1376
1377 /*
1378  * This is dirty, but it does the job.
1379  *
1380  * Since the FilesProvider doesn't really provide you a way of getting a URL from the file,
1381  * and since we actually need the Camera to create the file for us most of the time, we don't
1382  * actually write the file, just generate the location based on a timestamp, we need to get it
1383  * back from the Intent.
1384  *
1385  * However, the FilesProvider preserves the path, so we can at least write to it from here, since
1386  * we own the context in this case.
1387 */
1388
1389    private String getFileNameFromUri(Uri uri) {
1390        String fullUri = uri.toString();
1391        String partial_path = fullUri.split("external_files")[1];
1392        File external_storage = Environment.getExternalStorageDirectory();
1393        String path = external_storage.getAbsolutePath() + partial_path;
1394        return path;
1395
1396    }
1397
1398
1399}