diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 5f34f1b2bc0db..804677648d095 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -16,6 +16,8 @@ package android.content; +import static android.provider.DocumentsContract.EXTRA_ORIENTATION; + import android.accounts.Account; import android.annotation.IntDef; import android.annotation.NonNull; @@ -40,6 +42,7 @@ import android.graphics.Bitmap; import android.graphics.ImageDecoder; import android.graphics.ImageDecoder.ImageInfo; import android.graphics.ImageDecoder.Source; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -56,6 +59,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; import android.os.storage.StorageManager; +import android.system.Int32Ref; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; @@ -3563,9 +3567,14 @@ public abstract class ContentResolver implements ContentInterface { // Convert to Point, since that's what the API is defined as final Bundle opts = new Bundle(); opts.putParcelable(EXTRA_SIZE, Point.convert(size)); + final Int32Ref orientation = new Int32Ref(0); - return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { - return content.openTypedAssetFile(uri, "image/*", opts, signal); + Bitmap bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { + final AssetFileDescriptor afd = content.openTypedAssetFile(uri, "image/*", opts, + signal); + final Bundle extras = afd.getExtras(); + orientation.value = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0; + return afd; }), (ImageDecoder decoder, ImageInfo info, Source source) -> { decoder.setAllocator(allocator); @@ -3581,6 +3590,20 @@ public abstract class ContentResolver implements ContentInterface { decoder.setTargetSampleSize(sample); } }); + + // Transform the bitmap if requested. We use a side-channel to + // communicate the orientation, since EXIF thumbnails don't contain + // the rotation flags of the original image. + if (orientation.value != 0) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + + final Matrix m = new Matrix(); + m.setRotate(orientation.value, width / 2, height / 2); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); + } + + return bitmap; } /** {@hide} */ diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 4632b7553ca0d..4ac485099da82 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -36,6 +36,7 @@ import android.graphics.Bitmap; import android.graphics.ImageDecoder; import android.graphics.Point; import android.media.ExifInterface; +import android.media.MediaFile; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -1698,6 +1699,18 @@ public final class DocumentsContract { } catch (IOException e) { } + // Use ImageDecoder to do full image decode of heif format file + // will have right orientation. So, we don't need to add orientation + // information into extras. + final String mimeType = MediaFile.getMimeTypeForFile(file.getName()); + if (mimeType.equals("image/heif") + || mimeType.equals("image/heif-sequence") + || mimeType.equals("image/heic") + || mimeType.equals("image/heic-sequence")) { + return new AssetFileDescriptor(pfd, 0 /* startOffset */, + AssetFileDescriptor.UNKNOWN_LENGTH, null /* extras */); + } + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); } diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java index 21b194d73ff98..5de56c718570f 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -244,30 +244,77 @@ public class ThumbnailUtils { final Resizer resizer = new Resizer(size, signal); final String mimeType = MediaFile.getMimeTypeForFile(file.getName()); + Bitmap bitmap = null; + ExifInterface exif = null; + int orientation = 0; + + // get orientation + if (MediaFile.isExifMimeType(mimeType)) { + exif = new ExifInterface(file); + switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) { + case ExifInterface.ORIENTATION_ROTATE_90: + orientation = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + orientation = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + orientation = 270; + break; + } + } + + boolean isHeifFile = false; + if (mimeType.equals("image/heif") || mimeType.equals("image/heif-sequence") || mimeType.equals("image/heic") || mimeType.equals("image/heic-sequence")) { + isHeifFile = true; try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { retriever.setDataSource(file.getAbsolutePath()); - return retriever.getThumbnailImageAtIndex(-1, + bitmap = retriever.getThumbnailImageAtIndex(-1, new MediaMetadataRetriever.BitmapParams(), size.getWidth(), size.getWidth() * size.getHeight()); } catch (RuntimeException e) { throw new IOException("Failed to create thumbnail", e); } - } else if (MediaFile.isExifMimeType(mimeType)) { - final ExifInterface exif = new ExifInterface(file); + } + + if (bitmap == null && exif != null) { final byte[] raw = exif.getThumbnailBytes(); if (raw != null) { - return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + try { + bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer); + } catch (ImageDecoder.DecodeException e) { + Log.w(TAG, e); + } } } // Checkpoint before going deeper if (signal != null) signal.throwIfCanceled(); - return ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), resizer); + if (bitmap == null) { + bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), resizer); + // Use ImageDecoder to do full image decode of heif format file + // will have right orientation. Don't rotate the bitmap again. + if (isHeifFile) { + return bitmap; + } + } + + // Transform the bitmap if the orientation of the image is not 0. + if (orientation != 0 && bitmap != null) { + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + + final Matrix m = new Matrix(); + m.setRotate(orientation, width / 2, height / 2); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); + } + + return bitmap; } /**