diff --git a/android/src/main/java/com/imagepicker/ImageMetadata.java b/android/src/main/java/com/imagepicker/ImageMetadata.java index 0642e49b1..e98074d19 100644 --- a/android/src/main/java/com/imagepicker/ImageMetadata.java +++ b/android/src/main/java/com/imagepicker/ImageMetadata.java @@ -3,30 +3,40 @@ import android.content.Context; import android.net.Uri; import android.util.Log; + import androidx.exifinterface.media.ExifInterface; + import java.io.InputStream; public class ImageMetadata extends Metadata { - public ImageMetadata(Uri uri, Context context) { - try(InputStream inputStream = context.getContentResolver().openInputStream(uri)) { - ExifInterface exif = new ExifInterface(inputStream); - String datetimeTag = exif.getAttribute(ExifInterface.TAG_DATETIME); - - // Extract anymore metadata here... - if(datetimeTag != null) this.datetime = getDateTimeInUTC(datetimeTag, "yyyy:MM:dd HH:mm:ss"); - } catch (Exception e) { - // This error does not bubble up to RN as we don't want failed datetime retrieval to prevent selection - Log.e("RNIP", "Could not load image metadata: " + e.getMessage()); + public ImageMetadata(Uri uri, Context context) { + try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) { + ExifInterface exif = new ExifInterface(inputStream); + String datetimeTag = exif.getAttribute(ExifInterface.TAG_DATETIME); + + // Extract anymore metadata here... + if (datetimeTag != null) + this.datetime = getDateTimeInUTC(datetimeTag, "yyyy:MM:dd HH:mm:ss"); + } catch (Exception e) { + // This error does not bubble up to RN as we don't want failed datetime retrieval to prevent selection + Log.e("RNIP", "Could not load image metadata: " + e.getMessage()); + } } - } - @Override - public String getDateTime() { return datetime; } + @Override + public String getDateTime() { + return datetime; + } - // At the moment we are not using the ImageMetadata class to get width/height - // TODO: to use this class for extracting image width and height in the future - @Override - public int getWidth() { return 0; } - @Override - public int getHeight() { return 0; } + // At the moment we are not using the ImageMetadata class to get width/height + // TODO: to use this class for extracting image width and height in the future + @Override + public int getWidth() { + return 0; + } + + @Override + public int getHeight() { + return 0; + } } diff --git a/android/src/main/java/com/imagepicker/ImagePickerModuleImpl.java b/android/src/main/java/com/imagepicker/ImagePickerModuleImpl.java index 8442491db..afe4b3e71 100644 --- a/android/src/main/java/com/imagepicker/ImagePickerModuleImpl.java +++ b/android/src/main/java/com/imagepicker/ImagePickerModuleImpl.java @@ -192,12 +192,12 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, deleteFile(fileUri); } try { - callback.invoke(getCancelMap()); - return; + callback.invoke(getCancelMap()); + return; } catch (RuntimeException exception) { - callback.invoke(getErrorMap(errOthers, exception.getMessage())); + callback.invoke(getErrorMap(errOthers, exception.getMessage())); } finally { - callback = null; + callback = null; } } @@ -225,5 +225,6 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, } @Override - public void onNewIntent(Intent intent) { } + public void onNewIntent(Intent intent) { + } } diff --git a/android/src/main/java/com/imagepicker/ImagePickerPackage.java b/android/src/main/java/com/imagepicker/ImagePickerPackage.java index 4347445e8..e9cb7e6a3 100644 --- a/android/src/main/java/com/imagepicker/ImagePickerPackage.java +++ b/android/src/main/java/com/imagepicker/ImagePickerPackage.java @@ -1,6 +1,7 @@ package com.imagepicker; import androidx.annotation.Nullable; + import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.module.model.ReactModuleInfo; @@ -37,7 +38,7 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { true, // hasConstants false, // isCxxModule isTurboModule // isTurboModule - )); + )); return moduleInfos; }; } diff --git a/android/src/main/java/com/imagepicker/Metadata.java b/android/src/main/java/com/imagepicker/Metadata.java index 65f58958d..d0f6ef40e 100644 --- a/android/src/main/java/com/imagepicker/Metadata.java +++ b/android/src/main/java/com/imagepicker/Metadata.java @@ -9,36 +9,38 @@ import java.util.Locale; abstract class Metadata { - protected String datetime; - protected int height; - protected int width; - - abstract public String getDateTime(); - abstract public int getWidth(); - abstract public int getHeight(); - - /** - * Converts a timestamp to a UTC timestamp - * - * @param value - timestamp - * @param format - input format - * @return formatted timestamp - */ - protected @Nullable - String getDateTimeInUTC(String value, String format) { - try { - Date datetime = new SimpleDateFormat(format, Locale.US).parse(value); - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); - - if (datetime != null) { - return formatter.format(datetime); - } - - return null; - } catch (Exception e) { - // This error does not bubble up to RN as we don't want failed datetime parsing to prevent selection - Log.e("RNIP", "Could not parse image datetime to UTC: " + e.getMessage()); - return null; + protected String datetime; + protected int height; + protected int width; + + abstract public String getDateTime(); + + abstract public int getWidth(); + + abstract public int getHeight(); + + /** + * Converts a timestamp to a UTC timestamp + * + * @param value - timestamp + * @param format - input format + * @return formatted timestamp + */ + protected @Nullable + String getDateTimeInUTC(String value, String format) { + try { + Date datetime = new SimpleDateFormat(format, Locale.US).parse(value); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.US); + + if (datetime != null) { + return formatter.format(datetime); + } + + return null; + } catch (Exception e) { + // This error does not bubble up to RN as we don't want failed datetime parsing to prevent selection + Log.e("RNIP", "Could not parse image datetime to UTC: " + e.getMessage()); + return null; + } } - } } diff --git a/android/src/main/java/com/imagepicker/Options.java b/android/src/main/java/com/imagepicker/Options.java index 40de8b2ab..55c441af6 100644 --- a/android/src/main/java/com/imagepicker/Options.java +++ b/android/src/main/java/com/imagepicker/Options.java @@ -1,6 +1,7 @@ package com.imagepicker; import com.facebook.react.bridge.ReadableMap; + import android.text.TextUtils; public class Options { @@ -24,7 +25,7 @@ public class Options { includeExtra = options.getBoolean("includeExtra"); String videoQualityString = options.getString("videoQuality"); - if(!TextUtils.isEmpty(videoQualityString) && !videoQualityString.toLowerCase().equals("high")) { + if (!TextUtils.isEmpty(videoQualityString) && !videoQualityString.toLowerCase().equals("high")) { videoQuality = 0; } diff --git a/android/src/main/java/com/imagepicker/Utils.java b/android/src/main/java/com/imagepicker/Utils.java index 9030c9c54..ab530f924 100644 --- a/android/src/main/java/com/imagepicker/Utils.java +++ b/android/src/main/java/com/imagepicker/Utils.java @@ -58,7 +58,7 @@ public class Utils { public static File createFile(Context reactContext, String fileType) { try { - String filename = fileNamePrefix + UUID.randomUUID() + "." + fileType; + String filename = fileNamePrefix + UUID.randomUUID() + "." + fileType; // getCacheDir will auto-clean according to android docs File fileDir = reactContext.getCacheDir(); @@ -97,8 +97,8 @@ public static void saveToPublicDirectory(Uri uri, Context context, String mediaT } public static void copyUri(Uri fromUri, Uri toUri, ContentResolver resolver) { - try(OutputStream os = resolver.openOutputStream(toUri); - InputStream is = resolver.openInputStream(fromUri)) { + try (OutputStream os = resolver.openOutputStream(toUri); + InputStream is = resolver.openInputStream(fromUri)) { byte[] buffer = new byte[8192]; int bytesRead; @@ -133,7 +133,7 @@ public static Uri getAppSpecificStorageUri(Uri sharedStorageUri, Context context } } - Uri toUri = Uri.fromFile(createFile(context, fileType)); + Uri toUri = Uri.fromFile(createFile(context, fileType)); copyUri(sharedStorageUri, toUri, contentResolver); return toUri; } @@ -156,10 +156,10 @@ public static void setFrontCamera(Intent intent) { } public static int[] getImageDimensions(Uri uri, Context reactContext) { - try(InputStream inputStream = reactContext.getContentResolver().openInputStream(uri)) { + try (InputStream inputStream = reactContext.getContentResolver().openInputStream(uri)) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(inputStream,null, options); + BitmapFactory.decodeStream(inputStream, null, options); return new int[]{options.outWidth, options.outHeight}; } catch (IOException e) { e.printStackTrace(); @@ -173,8 +173,8 @@ static boolean hasPermission(final Activity activity) { } static String getBase64String(Uri uri, Context reactContext) { - try(InputStream inputStream = reactContext.getContentResolver().openInputStream(uri); - ByteArrayOutputStream output = new ByteArrayOutputStream()) { + try (InputStream inputStream = reactContext.getContentResolver().openInputStream(uri); + ByteArrayOutputStream output = new ByteArrayOutputStream()) { byte[] bytes; byte[] buffer = new byte[8192]; int bytesRead; @@ -202,8 +202,8 @@ public static Uri resizeImage(Uri uri, Context context, Options options) { int[] newDimens = getImageDimensBasedOnConstraints(origDimens[0], origDimens[1], options); - try(InputStream imageStream = context.getContentResolver().openInputStream(uri)) { - String mimeType = getMimeType(uri, context); + try (InputStream imageStream = context.getContentResolver().openInputStream(uri)) { + String mimeType = getMimeType(uri, context); Bitmap b = BitmapFactory.decodeStream(imageStream); b = Bitmap.createScaledBitmap(b, newDimens[0], newDimens[1], true); @@ -211,7 +211,7 @@ public static Uri resizeImage(Uri uri, Context context, Options options) { File file = createFile(context, getFileTypeFromMime(mimeType)); - try(OutputStream os = context.getContentResolver().openOutputStream(Uri.fromFile(file))) { + try (OutputStream os = context.getContentResolver().openOutputStream(Uri.fromFile(file))) { b.compress(getBitmapCompressFormat(mimeType), options.quality, os); } @@ -265,7 +265,7 @@ static int[] getImageDimensBasedOnConstraints(int origWidth, int origHeight, Opt } static double getFileSize(Uri uri, Context context) { - try(ParcelFileDescriptor f = context.getContentResolver().openFileDescriptor(uri, "r")) { + try (ParcelFileDescriptor f = context.getContentResolver().openFileDescriptor(uri, "r")) { return f.getStatSize(); } catch (Exception e) { e.printStackTrace(); @@ -287,8 +287,10 @@ static boolean shouldResizeImage(int origWidth, int origHeight, Options options) static Bitmap.CompressFormat getBitmapCompressFormat(String mimeType) { switch (mimeType) { - case "image/jpeg": return Bitmap.CompressFormat.JPEG; - case "image/png": return Bitmap.CompressFormat.PNG; + case "image/jpeg": + return Bitmap.CompressFormat.JPEG; + case "image/png": + return Bitmap.CompressFormat.PNG; } return Bitmap.CompressFormat.JPEG; } @@ -298,9 +300,12 @@ static String getFileTypeFromMime(String mimeType) { return "jpg"; } switch (mimeType) { - case "image/jpeg": return "jpg"; - case "image/png": return "png"; - case "image/gif": return "gif"; + case "image/jpeg": + return "jpg"; + case "image/png": + return "png"; + case "image/gif": + return "gif"; } return MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType); } @@ -315,8 +320,10 @@ static boolean isValidRequestCode(int requestCode) { switch (requestCode) { case REQUEST_LAUNCH_IMAGE_CAPTURE: case REQUEST_LAUNCH_VIDEO_CAPTURE: - case REQUEST_LAUNCH_LIBRARY: return true; - default: return false; + case REQUEST_LAUNCH_LIBRARY: + return true; + default: + return false; } } @@ -324,13 +331,13 @@ static boolean isValidRequestCode(int requestCode) { // https://issuetracker.google.com/issues/37063818 public static boolean isCameraPermissionFulfilled(Context context, Activity activity) { try { - String[] declaredPermissions = context.getPackageManager() - .getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS) - .requestedPermissions; + String[] declaredPermissions = context.getPackageManager() + .getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS) + .requestedPermissions; - if (declaredPermissions == null) { - return true; - } + if (declaredPermissions == null) { + return true; + } if (Arrays.asList(declaredPermissions).contains(Manifest.permission.CAMERA) && ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { @@ -346,30 +353,30 @@ public static boolean isCameraPermissionFulfilled(Context context, Activity acti } static boolean isImageType(Uri uri, Context context) { - return Utils.isContentType("image/", uri, context); + return Utils.isContentType("image/", uri, context); } static boolean isVideoType(Uri uri, Context context) { return Utils.isContentType("video/", uri, context); } - /** - * Verifies the content typs of a file URI. A helper function - * for isVideoType and isImageType - * - * @param contentMimeType - "video/" or "image/" - * @param uri - file uri - * @param context - react context - * @return a boolean to determine if file is of specified content type i.e. image or video - */ + /** + * Verifies the content typs of a file URI. A helper function + * for isVideoType and isImageType + * + * @param contentMimeType - "video/" or "image/" + * @param uri - file uri + * @param context - react context + * @return a boolean to determine if file is of specified content type i.e. image or video + */ static boolean isContentType(String contentMimeType, Uri uri, Context context) { - final String mimeType = getMimeType(uri, context); + final String mimeType = getMimeType(uri, context); - if(mimeType != null) { - return mimeType.contains(contentMimeType); - } + if (mimeType != null) { + return mimeType.contains(contentMimeType); + } - return false; + return false; } static String getMimeType(Uri uri, Context context) { @@ -441,10 +448,10 @@ static ReadableMap getImageResponseMap(Uri uri, Options options, Context context map.putString("base64", getBase64String(uri, context)); } - if(options.includeExtra) { - // Add more extra data here ... - map.putString("timestamp", imageMetadata.getDateTime()); - map.putString("id", fileName); + if (options.includeExtra) { + // Add more extra data here ... + map.putString("timestamp", imageMetadata.getDateTime()); + map.putString("id", fileName); } return map; @@ -464,10 +471,10 @@ static ReadableMap getVideoResponseMap(Uri uri, Options options, Context context map.putInt("width", videoMetadata.getWidth()); map.putInt("height", videoMetadata.getHeight()); - if(options.includeExtra) { - // Add more extra data here ... - map.putString("timestamp", videoMetadata.getDateTime()); - map.putString("id", fileName); + if (options.includeExtra) { + // Add more extra data here ... + map.putString("timestamp", videoMetadata.getDateTime()); + map.putString("id", fileName); } return map; @@ -476,7 +483,7 @@ static ReadableMap getVideoResponseMap(Uri uri, Options options, Context context static ReadableMap getResponseMap(List fileUris, Options options, Context context) throws RuntimeException { WritableArray assets = Arguments.createArray(); - for(int i = 0; i < fileUris.size(); ++i) { + for (int i = 0; i < fileUris.size(); ++i) { Uri uri = fileUris.get(i); // Call getAppSpecificStorageUri in the if block to avoid copying unsupported files diff --git a/android/src/main/java/com/imagepicker/VideoMetadata.java b/android/src/main/java/com/imagepicker/VideoMetadata.java index 1e8f85d7f..9ef64308c 100644 --- a/android/src/main/java/com/imagepicker/VideoMetadata.java +++ b/android/src/main/java/com/imagepicker/VideoMetadata.java @@ -13,69 +13,79 @@ // So let's use our own wrapper for it // See https://stackoverflow.com/a/74808462/1377358 class CustomMediaMetadataRetriever extends MediaMetadataRetriever implements AutoCloseable { - public CustomMediaMetadataRetriever() { - super(); - } - - @Override - public void close() throws IOException { - release(); - } + public CustomMediaMetadataRetriever() { + super(); + } + + @Override + public void close() throws IOException { + release(); + } } public class VideoMetadata extends Metadata { - private int duration; - private int bitrate; - - public VideoMetadata(Uri uri, Context context) { - try(CustomMediaMetadataRetriever metadataRetriever = new CustomMediaMetadataRetriever()) { - metadataRetriever.setDataSource(context, uri); - - String duration = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - String bitrate = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); - String datetime = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE); - - // Extract anymore metadata here... - if(duration != null) this.duration = Math.round(Float.parseFloat(duration)) / 1000; - if(bitrate != null) this.bitrate = parseInt(bitrate); - - if(datetime != null) { - // METADATA_KEY_DATE gives us the following format: "20211214T102646.000Z" - // This format is very hard to parse, so we convert it to "20211214 102646" ("yyyyMMdd HHmmss") - String datetimeToFormat = datetime.substring(0, datetime.indexOf(".")).replace("T", " "); - this.datetime = getDateTimeInUTC(datetimeToFormat, "yyyyMMdd HHmmss"); - } - - String width = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); - String height = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); - - if(height != null && width != null) { - String rotation = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); - int rotationI = rotation == null ? 0 : Integer.parseInt(rotation); - - if(rotationI == 90 || rotationI == 270) { - this.width = Integer.parseInt(height); - this.height = Integer.parseInt(width); - } else { - this.width = Integer.parseInt(width); - this.height = Integer.parseInt(height); + private int duration; + private int bitrate; + + public VideoMetadata(Uri uri, Context context) { + try (CustomMediaMetadataRetriever metadataRetriever = new CustomMediaMetadataRetriever()) { + metadataRetriever.setDataSource(context, uri); + + String duration = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + String bitrate = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE); + String datetime = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE); + + // Extract anymore metadata here... + if (duration != null) this.duration = Math.round(Float.parseFloat(duration)) / 1000; + if (bitrate != null) this.bitrate = parseInt(bitrate); + + if (datetime != null) { + // METADATA_KEY_DATE gives us the following format: "20211214T102646.000Z" + // This format is very hard to parse, so we convert it to "20211214 102646" ("yyyyMMdd HHmmss") + String datetimeToFormat = datetime.substring(0, datetime.indexOf(".")).replace("T", " "); + this.datetime = getDateTimeInUTC(datetimeToFormat, "yyyyMMdd HHmmss"); + } + + String width = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + String height = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + + if (height != null && width != null) { + String rotation = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + int rotationI = rotation == null ? 0 : Integer.parseInt(rotation); + + if (rotationI == 90 || rotationI == 270) { + this.width = Integer.parseInt(height); + this.height = Integer.parseInt(width); + } else { + this.width = Integer.parseInt(width); + this.height = Integer.parseInt(height); + } + } + } catch (IOException e) { + e.printStackTrace(); } - } - } catch (IOException e) { - e.printStackTrace(); } - } - - public int getBitrate() { - return bitrate; - } - public int getDuration() { - return duration; - } - @Override - public String getDateTime() { return datetime; } - @Override - public int getWidth() { return width; } - @Override - public int getHeight() { return height; } + + public int getBitrate() { + return bitrate; + } + + public int getDuration() { + return duration; + } + + @Override + public String getDateTime() { + return datetime; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } }