From 743cc9a1e7f135a75ee498e258309dba8da5af51 Mon Sep 17 00:00:00 2001 From: Ashoat Tevosyan Date: Thu, 23 Apr 2020 23:05:47 -0400 Subject: [PATCH 1/3] [expo-media-library] [Android] Flip dimensions based on media rotation data --- .../expo/modules/medialibrary/GetAssets.java | 6 +- .../medialibrary/MediaLibraryConstants.java | 8 +- .../medialibrary/MediaLibraryUtils.java | 99 ++++++++++++++----- 3 files changed, 83 insertions(+), 30 deletions(-) diff --git a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/GetAssets.java b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/GetAssets.java index fa7b518dd15e1..6ff3f08ba6c83 100644 --- a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/GetAssets.java +++ b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/GetAssets.java @@ -1,6 +1,7 @@ package expo.modules.medialibrary; import android.content.Context; +import android.content.ContentResolver; import android.database.Cursor; import android.os.AsyncTask; import android.os.Bundle; @@ -37,7 +38,8 @@ protected Void doInBackground(Void... params) { final String order = getQueryInfo.getOrder(); final int limit = getQueryInfo.getLimit(); final int offset = getQueryInfo.getOffset(); - try (Cursor assets = mContext.getContentResolver().query( + ContentResolver contentResolver = mContext.getContentResolver(); + try (Cursor assets = contentResolver.query( EXTERNAL_CONTENT, ASSET_PROJECTION, selection, @@ -47,7 +49,7 @@ protected Void doInBackground(Void... params) { mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get assets. Query returns null."); } else { ArrayList assetsInfo = new ArrayList<>(); - putAssetsInfo(assets, assetsInfo, limit, offset, false); + putAssetsInfo(contentResolver, assets, assetsInfo, limit, offset, false); response.putParcelableArrayList("assets", assetsInfo); response.putBoolean("hasNextPage", !assets.isAfterLast()); response.putString("endCursor", Integer.toString(assets.getPosition())); diff --git a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryConstants.java b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryConstants.java index 5e3eeaf40b1ca..35d459caeaa67 100644 --- a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryConstants.java +++ b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryConstants.java @@ -56,8 +56,8 @@ final class MediaLibraryConstants { put(SORT_BY_CREATION_TIME, MediaStore.Images.Media.DATE_TAKEN); put(SORT_BY_MODIFICATION_TIME, MediaStore.Images.Media.DATE_MODIFIED); put(SORT_BY_MEDIA_TYPE, MediaStore.Files.FileColumns.MEDIA_TYPE); - put(SORT_BY_WIDTH, MediaStore.Images.Media.WIDTH); - put(SORT_BY_HEIGHT, MediaStore.Images.Media.HEIGHT); + put(SORT_BY_WIDTH, MediaStore.MediaColumns.WIDTH); + put(SORT_BY_HEIGHT, MediaStore.MediaColumns.HEIGHT); put(SORT_BY_DURATION, MediaStore.Video.VideoColumns.DURATION); } }; @@ -70,8 +70,8 @@ final class MediaLibraryConstants { MediaStore.Files.FileColumns.DISPLAY_NAME, MediaStore.Images.Media.DATA, MediaStore.Files.FileColumns.MEDIA_TYPE, - MediaStore.Images.Media.WIDTH, - MediaStore.Images.Media.HEIGHT, + MediaStore.MediaColumns.WIDTH, + MediaStore.MediaColumns.HEIGHT, MediaStore.Images.Media.DATE_TAKEN, MediaStore.Images.Media.DATE_MODIFIED, MediaStore.Images.Media.LATITUDE, diff --git a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java index da5bd28c63ff8..7d70a64ca9e1a 100644 --- a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java +++ b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java @@ -1,14 +1,18 @@ package expo.modules.medialibrary; import android.content.Context; +import android.content.ContentResolver; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.graphics.BitmapFactory; import android.os.Bundle; +import android.media.MediaMetadataRetriever; import android.provider.MediaStore; import android.provider.MediaStore.Files; import android.provider.MediaStore.Images.Media; import androidx.exifinterface.media.ExifInterface; import android.text.TextUtils; +import android.net.Uri; import java.io.File; import java.io.FileInputStream; @@ -96,7 +100,8 @@ static File safeCopyFile(final File src, final File dir) throws IOException { } static void queryAssetInfo(Context context, final String selection, final String[] selectionArgs, boolean fullInfo, Promise promise) { - try (Cursor asset = context.getContentResolver().query( + ContentResolver contentResolver = context.getContentResolver(); + try (Cursor asset = contentResolver.query( EXTERNAL_CONTENT, ASSET_PROJECTION, selection, @@ -109,7 +114,7 @@ static void queryAssetInfo(Context context, final String selection, final String if (asset.getCount() == 1) { asset.moveToFirst(); ArrayList array = new ArrayList<>(); - putAssetsInfo(asset, array, 1, 0, fullInfo); + putAssetsInfo(contentResolver, asset, array, 1, 0, fullInfo); // actually we want to return just the first item, but array.getMap returns ReadableMap // which is not compatible with promise.resolve and there is no simple solution to convert // ReadableMap to WritableMap so it's easier to return an array and pick the first item on JS side @@ -126,7 +131,7 @@ static void queryAssetInfo(Context context, final String selection, final String } } - static void putAssetsInfo(Cursor cursor, ArrayList response, int limit, int offset, boolean fullInfo) throws IOException { + static void putAssetsInfo(ContentResolver contentResolver, Cursor cursor, ArrayList response, int limit, int offset, boolean fullInfo) throws IOException { final int idIndex = cursor.getColumnIndex(Media._ID); final int filenameIndex = cursor.getColumnIndex(Media.DISPLAY_NAME); final int mediaTypeIndex = cursor.getColumnIndex(Files.FileColumns.MEDIA_TYPE); @@ -142,9 +147,16 @@ static void putAssetsInfo(Cursor cursor, ArrayList response, int limit, return; } for (int i = 0; i < limit && !cursor.isAfterLast(); i++) { - String localUri = "file://" + cursor.getString(localUriIndex); + String path = cursor.getString(localUriIndex); + String localUri = "file://" + path; int mediaType = cursor.getInt(mediaTypeIndex); - int[] size = getSizeFromCursor(cursor, mediaType, localUriIndex); + + ExifInterface exifInterface = null; + if (mediaType == Files.FileColumns.MEDIA_TYPE_IMAGE) { + exifInterface = new ExifInterface(path); + } + + int[] size = getSizeFromCursor(contentResolver, exifInterface, cursor, mediaType, localUriIndex); Bundle asset = new Bundle(); asset.putString("id", cursor.getString(idIndex)); @@ -159,8 +171,8 @@ static void putAssetsInfo(Cursor cursor, ArrayList response, int limit, asset.putString("albumId", cursor.getString(albumIdIndex)); if (fullInfo) { - if (mediaType == Files.FileColumns.MEDIA_TYPE_IMAGE) { - getExifFullInfo(cursor, asset); + if (exifInterface != null) { + getExifFullInfo(exifInterface, asset); } asset.putString("localUri", localUri); @@ -213,25 +225,66 @@ static Integer convertMediaType(String mediaType) throws IllegalArgumentExceptio return MEDIA_TYPES.get(mediaType); } - static int[] getSizeFromCursor(Cursor cursor, int mediaType, int localUriIndex){ + static int[] getSizeFromCursor(ContentResolver contentResolver, ExifInterface exifInterface, Cursor cursor, int mediaType, int localUriIndex) { + final String uri = cursor.getString(localUriIndex); + + if (mediaType == Files.FileColumns.MEDIA_TYPE_VIDEO) { + Uri photoUri = Uri.parse("file://" + uri); + try { + AssetFileDescriptor photoDescriptor = contentResolver.openAssetFileDescriptor(photoUri, "r"); + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + retriever.setDataSource(photoDescriptor.getFileDescriptor()); + int videoWidth = Integer.parseInt( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) + ); + int videoHeight = Integer.parseInt( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) + ); + int videoOrientation = Integer.parseInt( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) + ); + return maybeRotateAssetSize(videoWidth, videoHeight, videoOrientation); + } catch (NumberFormatException e) { + } finally { + retriever.release(); + photoDescriptor.close(); + } + } catch (Exception e) { + } + } + + final int widthIndex = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH); + final int heightIndex = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT); final int orientationIndex = cursor.getColumnIndex(Media.ORIENTATION); - final int widthIndex = cursor.getColumnIndex(Media.WIDTH); - final int heightIndex = cursor.getColumnIndex(Media.HEIGHT); - - int[] size; - // If image doesn't have the required information, we can get them from Bitmap.Options - if ((cursor.getType(widthIndex) == Cursor.FIELD_TYPE_NULL || - cursor.getType(heightIndex) == Cursor.FIELD_TYPE_NULL) && - mediaType == Files.FileColumns.MEDIA_TYPE_IMAGE) { + int width = cursor.getInt(widthIndex); + int height = cursor.getInt(heightIndex); + int orientation = cursor.getInt(orientationIndex); + + if (width <= 0 || height <= 0) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(uri, options); + width = options.outWidth; + height = options.outHeight; + } - BitmapFactory.decodeFile(cursor.getString(localUriIndex), options); - size = maybeRotateAssetSize(options.outWidth, options.outHeight, cursor.getInt(orientationIndex)); - } else { - size = maybeRotateAssetSize(cursor.getInt(widthIndex), cursor.getInt(heightIndex), cursor.getInt(orientationIndex)); + if (exifInterface != null) { + int exifOrientation = exifInterface.getAttributeInt( + ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL + ); + if ( + exifOrientation == ExifInterface.ORIENTATION_ROTATE_90 || + exifOrientation == ExifInterface.ORIENTATION_ROTATE_270 || + exifOrientation == ExifInterface.ORIENTATION_TRANSPOSE || + exifOrientation == ExifInterface.ORIENTATION_TRANSVERSE + ) { + orientation = 90; + } } - return size; + + return maybeRotateAssetSize(width, height, orientation); } static int[] maybeRotateAssetSize(int width, int height, int orientation) { @@ -265,9 +318,7 @@ static String mapOrderDescriptor(List orderDescriptor) throws IllegalArgumentExc return TextUtils.join(",", result); } - static void getExifFullInfo(Cursor cursor, Bundle response) throws IOException { - File input = new File(cursor.getString(cursor.getColumnIndex(Media.DATA))); - ExifInterface exifInterface = new ExifInterface(input.getPath()); + static void getExifFullInfo(ExifInterface exifInterface, Bundle response) { Bundle exifMap = new Bundle(); for (String[] tagInfo : exifTags) { String name = tagInfo[1]; From 92d1579b83156bbbb01caedb4dc242f110c639ff Mon Sep 17 00:00:00 2001 From: Ashoat Tevosyan Date: Mon, 27 Apr 2020 12:42:08 -0400 Subject: [PATCH 2/3] [expo-media-library] [Android] Cleanup for media rotation dimension flip --- .../medialibrary/MediaLibraryUtils.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java index 7d70a64ca9e1a..6861e02bb7c43 100644 --- a/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java +++ b/packages/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryUtils.java @@ -13,9 +13,11 @@ import androidx.exifinterface.media.ExifInterface; import android.text.TextUtils; import android.net.Uri; +import android.util.Log; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; @@ -225,32 +227,33 @@ static Integer convertMediaType(String mediaType) throws IllegalArgumentExceptio return MEDIA_TYPES.get(mediaType); } - static int[] getSizeFromCursor(ContentResolver contentResolver, ExifInterface exifInterface, Cursor cursor, int mediaType, int localUriIndex) { + static int[] getSizeFromCursor(ContentResolver contentResolver, ExifInterface exifInterface, Cursor cursor, int mediaType, int localUriIndex) throws IOException { final String uri = cursor.getString(localUriIndex); if (mediaType == Files.FileColumns.MEDIA_TYPE_VIDEO) { - Uri photoUri = Uri.parse("file://" + uri); - try { - AssetFileDescriptor photoDescriptor = contentResolver.openAssetFileDescriptor(photoUri, "r"); - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - try { - retriever.setDataSource(photoDescriptor.getFileDescriptor()); - int videoWidth = Integer.parseInt( - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) - ); - int videoHeight = Integer.parseInt( - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) - ); - int videoOrientation = Integer.parseInt( - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) - ); - return maybeRotateAssetSize(videoWidth, videoHeight, videoOrientation); - } catch (NumberFormatException e) { - } finally { + Uri videoUri = Uri.parse("file://" + uri); + MediaMetadataRetriever retriever = null; + try (AssetFileDescriptor photoDescriptor = contentResolver.openAssetFileDescriptor(videoUri, "r")) { + retriever = new MediaMetadataRetriever(); + retriever.setDataSource(photoDescriptor.getFileDescriptor()); + int videoWidth = Integer.parseInt( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) + ); + int videoHeight = Integer.parseInt( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) + ); + int videoOrientation = Integer.parseInt( + retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) + ); + return maybeRotateAssetSize(videoWidth, videoHeight, videoOrientation); + } catch (NumberFormatException e) { + Log.e("expo-media-library", "MediaMetadataRetriever unexpectedly returned non-integer: " + e.getMessage()); + } catch (FileNotFoundException e) { + Log.e("expo-media-library", String.format("ContentResolver failed to read %s: %s", uri, e.getMessage())); + } finally { + if (retriever != null) { retriever.release(); - photoDescriptor.close(); } - } catch (Exception e) { } } @@ -261,7 +264,8 @@ static int[] getSizeFromCursor(ContentResolver contentResolver, ExifInterface ex int height = cursor.getInt(heightIndex); int orientation = cursor.getInt(orientationIndex); - if (width <= 0 || height <= 0) { + // If the image doesn't have the required information, we can get them from Bitmap.Options + if (mediaType == Files.FileColumns.MEDIA_TYPE_IMAGE && (width <= 0 || height <= 0)) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(uri, options); From 49922430f203876dcad48b8dbc0d691823336247 Mon Sep 17 00:00:00 2001 From: Ashoat Tevosyan Date: Thu, 7 May 2020 11:56:00 -0400 Subject: [PATCH 3/3] [expo-media-library] [Android] CHANGELOG entry for media rotation dimension flip --- packages/expo-media-library/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/expo-media-library/CHANGELOG.md b/packages/expo-media-library/CHANGELOG.md index 836a0fc2435a8..bc2ee3c3c95d4 100644 --- a/packages/expo-media-library/CHANGELOG.md +++ b/packages/expo-media-library/CHANGELOG.md @@ -9,3 +9,4 @@ ### 🐛 Bug fixes - Fixed `MediaLibrary` not compiling with the `use_frameworks!` option in the bare React Native application. ([#7861](https://github.com/expo/expo/pull/7861) by [@Ashoat](https://github.com/Ashoat)) +- Flip dimensions based on media rotation data on Android to match `` and `