From 77d7ab5baf4665be86d09c222cda708714959781 Mon Sep 17 00:00:00 2001 From: Marco Celotti Date: Fri, 24 Aug 2018 11:22:04 +0200 Subject: [PATCH 1/3] CB-13683: (android) cordova-plugin-media-capture rotates pictures with some devices --- plugin.xml | 2 + src/android/Capture.java | 117 ++++++++++++++++++++++++++++++--------- 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/plugin.xml b/plugin.xml index 7b4bb87b..e035ad70 100644 --- a/plugin.xml +++ b/plugin.xml @@ -91,6 +91,8 @@ xmlns:rim="http://www.blackberry.com/ns/widgets" + + diff --git a/src/android/Capture.java b/src/android/Capture.java index 40117aaf..ac963de3 100644 --- a/src/android/Capture.java +++ b/src/android/Capture.java @@ -18,44 +18,47 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.cordova.mediacapture; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.Arrays; - -import android.os.Build; -import android.os.Bundle; - -import org.apache.cordova.file.FileUtils; -import org.apache.cordova.file.LocalFilesystemURL; - -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.LOG; -import org.apache.cordova.PermissionHelper; -import org.apache.cordova.PluginManager; -import org.apache.cordova.mediacapture.PendingRequests.Request; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import android.Manifest; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Matrix; import android.media.MediaPlayer; import android.net.Uri; +import android.os.Build; +import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; +import android.support.annotation.Nullable; +import android.support.media.ExifInterface; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.LOG; +import org.apache.cordova.PermissionHelper; +import org.apache.cordova.PluginManager; +import org.apache.cordova.file.FileUtils; +import org.apache.cordova.file.LocalFilesystemURL; +import org.apache.cordova.mediacapture.PendingRequests.Request; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; public class Capture extends CordovaPlugin { @@ -183,7 +186,7 @@ else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) { /** * Get the Image specific attributes * - * @param filePath path to the file + * @param fileUrl path to the file * @param obj represents the Media File Data * @return a JSONObject that represents the Media File Data * @throws JSONException @@ -287,6 +290,12 @@ private static void createWritableFile(File file) throws IOException { file.setWritable(true, false); } + private static Bitmap rotateImage(Bitmap source, float angle) { + Matrix matrix = new Matrix(); + matrix.postRotate(angle); + return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, true); + } + /** * Sets up an intent to capture video. Result handled by onActivityResult() */ @@ -378,8 +387,16 @@ public void onAudioActivityResult(Request req, Intent intent) { } public void onImageActivityResult(Request req) { + Uri uri = imageUri; + + // Check image rotation + Bitmap rotatedBitmap = rotateAccordingToExifOrientation(uri); + if (rotatedBitmap != null) { + uri = fromBitmapToUri(this.cordova.getContext(), rotatedBitmap); + } + // Add image to results - req.results.put(createMediaFile(imageUri)); + req.results.put(createMediaFile(uri)); checkForDuplicateImage(); @@ -537,6 +554,52 @@ private Uri whichContentStore() { } } + @Nullable + private Bitmap rotateAccordingToExifOrientation(Uri uri) { + Context context = this.cordova.getContext(); + int orientation; + Bitmap bitmap; + try { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + if (inputStream == null) + throw new IOException("input stream from ContentResolver is null"); + ExifInterface ei = new ExifInterface(inputStream); + orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); + bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); + } catch (IOException e) { + LOG.e(LOG_TAG, "Failed reading bitmap", e); + return null; + } + + Bitmap rotatedBitmap; + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + rotatedBitmap = rotateImage(bitmap, 90); + break; + + case ExifInterface.ORIENTATION_ROTATE_180: + rotatedBitmap = rotateImage(bitmap, 180); + break; + + case ExifInterface.ORIENTATION_ROTATE_270: + rotatedBitmap = rotateImage(bitmap, 270); + break; + + case ExifInterface.ORIENTATION_NORMAL: + default: + // use original bitmap + rotatedBitmap = null; + } + return rotatedBitmap; + } + + private Uri fromBitmapToUri(Context context, Bitmap inImage) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + inImage.compress(Bitmap.CompressFormat.JPEG, 85, bytes); + String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), inImage, "title", null); + return Uri.parse(path); + } + private void executeRequest(Request req) { switch (req.action) { case CAPTURE_AUDIO: From abed3674d13a5f380c5c1f779ae49752f0d5c9f1 Mon Sep 17 00:00:00 2001 From: Marco Celotti Date: Fri, 31 Aug 2018 16:38:58 +0200 Subject: [PATCH 2/3] CB-13683: (android) set compression level to 100 for rotated images --- src/android/Capture.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/Capture.java b/src/android/Capture.java index ac963de3..15f39481 100644 --- a/src/android/Capture.java +++ b/src/android/Capture.java @@ -595,7 +595,7 @@ private Bitmap rotateAccordingToExifOrientation(Uri uri) { private Uri fromBitmapToUri(Context context, Bitmap inImage) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - inImage.compress(Bitmap.CompressFormat.JPEG, 85, bytes); + inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes); String path = MediaStore.Images.Media.insertImage(context.getContentResolver(), inImage, "title", null); return Uri.parse(path); } From 84fba26a6d9eac9a4a84170b8d7d394696182946 Mon Sep 17 00:00:00 2001 From: Marco Celotti Date: Mon, 24 Sep 2018 12:27:20 +0200 Subject: [PATCH 3/3] CB-13683: (android) merge upstream/master --- src/android/Capture.java | 90 ++++++++++++++++++++++------------------ types/index.d.ts | 1 + 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/src/android/Capture.java b/src/android/Capture.java index 15f39481..1df6274d 100644 --- a/src/android/Capture.java +++ b/src/android/Capture.java @@ -18,48 +18,51 @@ Licensed to the Apache Software Foundation (ASF) under one */ package org.apache.cordova.mediacapture; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Build; +import android.os.Bundle; + +import org.apache.cordova.file.FileUtils; +import org.apache.cordova.file.LocalFilesystemURL; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.LOG; +import org.apache.cordova.PermissionHelper; +import org.apache.cordova.PluginManager; +import org.apache.cordova.mediacapture.PendingRequests.Request; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import android.Manifest; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; -import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; -import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.media.MediaPlayer; import android.net.Uri; -import android.os.Build; -import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.support.annotation.Nullable; import android.support.media.ExifInterface; -import org.apache.cordova.CallbackContext; -import org.apache.cordova.CordovaPlugin; -import org.apache.cordova.LOG; -import org.apache.cordova.PermissionHelper; -import org.apache.cordova.PluginManager; -import org.apache.cordova.file.FileUtils; -import org.apache.cordova.file.LocalFilesystemURL; -import org.apache.cordova.mediacapture.PendingRequests.Request; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; - public class Capture extends CordovaPlugin { private static final String VIDEO_3GPP = "video/3gpp"; @@ -74,10 +77,11 @@ public class Capture extends CordovaPlugin { private static final String LOG_TAG = "Capture"; private static final int CAPTURE_INTERNAL_ERR = 0; -// private static final int CAPTURE_APPLICATION_BUSY = 1; + // private static final int CAPTURE_APPLICATION_BUSY = 1; // private static final int CAPTURE_INVALID_ARGUMENT = 2; private static final int CAPTURE_NO_MEDIA_FILES = 3; private static final int CAPTURE_PERMISSION_DENIED = 4; + private static final int CAPTURE_NOT_SUPPORTED = 20; private boolean cameraPermissionInManifest; // Whether or not the CAMERA permission is declared in AndroidManifest.xml @@ -229,13 +233,17 @@ private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean vi * Sets up an intent to capture audio. Result handled by onActivityResult() */ private void captureAudio(Request req) { - if (!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { - PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.READ_EXTERNAL_STORAGE); - } else { - Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); + if (!PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { + PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.READ_EXTERNAL_STORAGE); + } else { + try { + Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); - this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); - } + this.cordova.startActivityForResult((CordovaPlugin) this, intent, req.requestCode); + } catch (ActivityNotFoundException ex) { + pendingRequests.resolveWithFailure(req, createErrorObject(CAPTURE_NOT_SUPPORTED, "No Activity found to handle Audio Capture.")); + } + } } private String getTempDirectoryPath() { @@ -254,16 +262,16 @@ private String getTempDirectoryPath() { */ private void captureImage(Request req) { boolean needExternalStoragePermission = - !PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); + !PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE); boolean needCameraPermission = cameraPermissionInManifest && - !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA); + !PermissionHelper.hasPermission(this, Manifest.permission.CAMERA); if (needExternalStoragePermission || needCameraPermission) { if (needExternalStoragePermission && needCameraPermission) { - PermissionHelper.requestPermissions(this, req.requestCode, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA}); + PermissionHelper.requestPermissions(this, req.requestCode, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}); } else if (needExternalStoragePermission) { - PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.READ_EXTERNAL_STORAGE); + PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE); } else { PermissionHelper.requestPermission(this, req.requestCode, Manifest.permission.CAMERA); } @@ -517,11 +525,11 @@ private JSONObject createErrorObject(int code, String message) { */ private Cursor queryImgDB(Uri contentStore) { return this.cordova.getActivity().getContentResolver().query( - contentStore, - new String[] { MediaStore.Images.Media._ID }, - null, - null, - null); + contentStore, + new String[] { MediaStore.Images.Media._ID }, + null, + null, + null); } /** diff --git a/types/index.d.ts b/types/index.d.ts index 726c5dac..17f53858 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -123,6 +123,7 @@ declare var CaptureError: { CAPTURE_INVALID_ARGUMENT: number; CAPTURE_NO_MEDIA_FILES: number; CAPTURE_NOT_SUPPORTED: number; + CAPTURE_PERMISSION_DENIED: number; } /** Encapsulates audio capture configuration options. */