Skip to content

Commit

Permalink
[expo-media-library] [Android] Flip dimensions based on media rotatio…
Browse files Browse the repository at this point in the history
…n data (#7980)

* [expo-media-library] [Android] Flip dimensions based on media rotation data

* [expo-media-library] [Android] Cleanup for media rotation dimension flip

* [expo-media-library] [Android] CHANGELOG entry for media rotation dimension flip
  • Loading branch information
Ashoat committed May 8, 2020
1 parent 1074be6 commit f5b1d08
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 30 deletions.
1 change: 1 addition & 0 deletions packages/expo-media-library/CHANGELOG.md
Expand Up @@ -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 `<Image>` and `<Video>` as well as iOS behavior. ([#7980](https://github.com/expo/expo/pull/7980) by [@Ashoat](https://github.com/Ashoat))
@@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -47,7 +49,7 @@ protected Void doInBackground(Void... params) {
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get assets. Query returns null.");
} else {
ArrayList<Bundle> 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()));
Expand Down
Expand Up @@ -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);
}
};
Expand All @@ -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,
Expand Down
@@ -1,17 +1,23 @@
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 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;
Expand Down Expand Up @@ -96,7 +102,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,
Expand All @@ -109,7 +116,7 @@ static void queryAssetInfo(Context context, final String selection, final String
if (asset.getCount() == 1) {
asset.moveToFirst();
ArrayList<Bundle> 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
Expand All @@ -126,7 +133,7 @@ static void queryAssetInfo(Context context, final String selection, final String
}
}

static void putAssetsInfo(Cursor cursor, ArrayList<Bundle> response, int limit, int offset, boolean fullInfo) throws IOException {
static void putAssetsInfo(ContentResolver contentResolver, Cursor cursor, ArrayList<Bundle> 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);
Expand All @@ -142,9 +149,16 @@ static void putAssetsInfo(Cursor cursor, ArrayList<Bundle> 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));
Expand All @@ -159,8 +173,8 @@ static void putAssetsInfo(Cursor cursor, ArrayList<Bundle> 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);
Expand Down Expand Up @@ -213,25 +227,68 @@ 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) throws IOException {
final String uri = cursor.getString(localUriIndex);

if (mediaType == Files.FileColumns.MEDIA_TYPE_VIDEO) {
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();
}
}
}

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 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);
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) {
Expand Down Expand Up @@ -265,9 +322,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];
Expand Down

0 comments on commit f5b1d08

Please sign in to comment.