Merge "Improve thumbnail generation utilities."
diff --git a/api/current.txt b/api/current.txt
index 11f7137..de4e8241 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24988,8 +24988,9 @@
field public static final int RATING_KEY_BY_USER = 268435457; // 0x10000001
}
- public class MediaMetadataRetriever {
+ public class MediaMetadataRetriever implements java.lang.AutoCloseable {
ctor public MediaMetadataRetriever();
+ method public void close();
method public java.lang.String extractMetadata(int);
method public byte[] getEmbeddedPicture();
method public android.graphics.Bitmap getFrameAtIndex(int, android.media.MediaMetadataRetriever.BitmapParams);
@@ -26076,7 +26077,12 @@
public class ThumbnailUtils {
ctor public ThumbnailUtils();
- method public static android.graphics.Bitmap createVideoThumbnail(java.lang.String, int);
+ method public static deprecated android.graphics.Bitmap createAudioThumbnail(java.lang.String, int);
+ method public static android.graphics.Bitmap createAudioThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException;
+ method public static deprecated android.graphics.Bitmap createImageThumbnail(java.lang.String, int);
+ method public static android.graphics.Bitmap createImageThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException;
+ method public static deprecated android.graphics.Bitmap createVideoThumbnail(java.lang.String, int);
+ method public static android.graphics.Bitmap createVideoThumbnail(java.io.File, android.util.Size, android.os.CancellationSignal) throws java.io.IOException;
method public static android.graphics.Bitmap extractThumbnail(android.graphics.Bitmap, int, int);
method public static android.graphics.Bitmap extractThumbnail(android.graphics.Bitmap, int, int, int);
field public static final int OPTIONS_RECYCLE_INPUT = 2; // 0x2
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f38f740..457453f 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -1002,6 +1002,7 @@
public static final int MICRO_KIND = 3;
public static final Point MINI_SIZE = new Point(512, 384);
+ public static final Point FULL_SCREEN_SIZE = new Point(1024, 786);
public static final Point MICRO_SIZE = new Point(96, 96);
}
@@ -1127,6 +1128,8 @@
final Point size;
if (kind == ThumbnailConstants.MICRO_KIND) {
size = ThumbnailConstants.MICRO_SIZE;
+ } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
+ size = ThumbnailConstants.FULL_SCREEN_SIZE;
} else if (kind == ThumbnailConstants.MINI_KIND) {
size = ThumbnailConstants.MINI_SIZE;
} else {
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 00a393a..d656fa3 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -40,8 +40,7 @@
* MediaMetadataRetriever class provides a unified interface for retrieving
* frame and meta data from an input media file.
*/
-public class MediaMetadataRetriever
-{
+public class MediaMetadataRetriever implements AutoCloseable {
static {
System.loadLibrary("media_jni");
native_init();
@@ -672,6 +671,11 @@
@UnsupportedAppUsage
private native byte[] getEmbeddedPicture(int pictureType);
+ @Override
+ public void close() {
+ release();
+ }
+
/**
* Call it when one is done with the object. This method releases the memory
* allocated internally.
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index fd14060..f07076a 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -16,33 +16,53 @@
package android.media;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_DURATION;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH;
+import static android.media.MediaMetadataRetriever.OPTION_CLOSEST_SYNC;
+import static android.os.Environment.MEDIA_UNKNOWN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
+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.Rect;
import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.Environment;
import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore.Images;
+import android.provider.MediaStore.ThumbnailConstants;
import android.util.Log;
+import android.util.Size;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
+import com.android.internal.util.ArrayUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.function.ToIntFunction;
/**
- * Thumbnail generation routines for media provider.
+ * Utilities for generating visual thumbnails from files.
*/
-
public class ThumbnailUtils {
private static final String TAG = "ThumbnailUtils";
- /* Maximum pixels size for created bitmap. */
- private static final int MAX_NUM_PIXELS_THUMBNAIL = 512 * 384;
- private static final int MAX_NUM_PIXELS_MICRO_THUMBNAIL = 160 * 120;
- private static final int UNCONSTRAINED = -1;
+ /** @hide */
+ @Deprecated
+ @UnsupportedAppUsage
+ public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;
/* Options used internally. */
private static final int OPTIONS_NONE = 0x0;
@@ -54,153 +74,252 @@
*/
public static final int OPTIONS_RECYCLE_INPUT = 0x2;
- /**
- * Constant used to indicate the dimension of mini thumbnail.
- * @hide Only used by media framework and media provider internally.
- */
- public static final int TARGET_SIZE_MINI_THUMBNAIL = 320;
+ private static Size convertKind(int kind) {
+ if (kind == ThumbnailConstants.MICRO_KIND) {
+ return Point.convert(ThumbnailConstants.MICRO_SIZE);
+ } else if (kind == ThumbnailConstants.FULL_SCREEN_KIND) {
+ return Point.convert(ThumbnailConstants.FULL_SCREEN_SIZE);
+ } else if (kind == ThumbnailConstants.MINI_KIND) {
+ return Point.convert(ThumbnailConstants.MINI_SIZE);
+ } else {
+ throw new IllegalArgumentException("Unsupported kind: " + kind);
+ }
+ }
+
+ private static class Resizer implements ImageDecoder.OnHeaderDecodedListener {
+ private final Size size;
+ private final CancellationSignal signal;
+
+ public Resizer(Size size, CancellationSignal signal) {
+ this.size = size;
+ this.signal = signal;
+ }
+
+ @Override
+ public void onHeaderDecoded(ImageDecoder decoder, ImageInfo info, Source source) {
+ // One last-ditch check to see if we've been canceled.
+ if (signal != null) signal.throwIfCanceled();
+
+ // We don't know how clients will use the decoded data, so we have
+ // to default to the more flexible "software" option.
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+
+ // We requested a rough thumbnail size, but the remote size may have
+ // returned something giant, so defensively scale down as needed.
+ final int widthSample = info.getSize().getWidth() / size.getWidth();
+ final int heightSample = info.getSize().getHeight() / size.getHeight();
+ final int sample = Math.max(widthSample, heightSample);
+ if (sample > 1) {
+ decoder.setTargetSampleSize(sample);
+ }
+ }
+ }
/**
- * Constant used to indicate the dimension of micro thumbnail.
- * @hide Only used by media framework and media provider internally.
+ * Create a thumbnail for given audio file.
+ *
+ * @param filePath The audio file.
+ * @param kind The desired thumbnail kind, such as
+ * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}.
*/
- @UnsupportedAppUsage
- public static final int TARGET_SIZE_MICRO_THUMBNAIL = 96;
+ @Deprecated
+ public static @Nullable Bitmap createAudioThumbnail(@NonNull String filePath, int kind) {
+ try {
+ return createAudioThumbnail(new File(filePath), convertKind(kind), null);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ }
/**
- * This method first examines if the thumbnail embedded in EXIF is bigger than our target
- * size. If not, then it'll create a thumbnail from original image. Due to efficiency
- * consideration, we want to let MediaThumbRequest avoid calling this method twice for
- * both kinds, so it only requests for MICRO_KIND and set saveImage to true.
+ * Create a thumbnail for given audio file.
*
- * This method always returns a "square thumbnail" for MICRO_KIND thumbnail.
- *
- * @param filePath the path of image file
- * @param kind could be MINI_KIND or MICRO_KIND
- * @return Bitmap, or null on failures
- *
- * @hide This method is only used by media framework and media provider internally.
+ * @param file The audio file.
+ * @param size The desired thumbnail size.
+ * @throws IOException If any trouble was encountered while generating or
+ * loading the thumbnail, or if
+ * {@link CancellationSignal#cancel()} was invoked.
*/
- @UnsupportedAppUsage
- public static Bitmap createImageThumbnail(String filePath, int kind) {
- boolean wantMini = (kind == Images.Thumbnails.MINI_KIND);
- int targetSize = wantMini
- ? TARGET_SIZE_MINI_THUMBNAIL
- : TARGET_SIZE_MICRO_THUMBNAIL;
- int maxPixels = wantMini
- ? MAX_NUM_PIXELS_THUMBNAIL
- : MAX_NUM_PIXELS_MICRO_THUMBNAIL;
- SizedThumbnailBitmap sizedThumbnailBitmap = new SizedThumbnailBitmap();
- Bitmap bitmap = null;
- String mimeType = MediaFile.getMimeTypeForFile(filePath);
+ public static @NonNull Bitmap createAudioThumbnail(@NonNull File file, @NonNull Size size,
+ @Nullable CancellationSignal signal) throws IOException {
+ // Checkpoint before going deeper
+ if (signal != null) signal.throwIfCanceled();
+
+ final Resizer resizer = new Resizer(size, signal);
+ try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) {
+ retriever.setDataSource(file.getAbsolutePath());
+ final byte[] raw = retriever.getEmbeddedPicture();
+ if (raw != null) {
+ return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer);
+ }
+ } catch (RuntimeException e) {
+ throw new IOException("Failed to create thumbnail", e);
+ }
+
+ // Only poke around for files on external storage
+ if (MEDIA_UNKNOWN.equals(Environment.getExternalStorageState(file))) {
+ throw new IOException("No embedded album art found");
+ }
+
+ // Ignore "Downloads" or top-level directories
+ final File parent = file.getParentFile();
+ final File grandParent = parent != null ? parent.getParentFile() : null;
+ if (parent != null
+ && parent.getName().equals(Environment.DIRECTORY_DOWNLOADS)) {
+ throw new IOException("No thumbnails in Downloads directories");
+ }
+ if (grandParent != null
+ && MEDIA_UNKNOWN.equals(Environment.getExternalStorageState(grandParent))) {
+ throw new IOException("No thumbnails in top-level directories");
+ }
+
+ // If no embedded image found, look around for best standalone file
+ final File[] found = ArrayUtils
+ .defeatNullable(file.getParentFile().listFiles((dir, name) -> {
+ final String lower = name.toLowerCase();
+ return (lower.endsWith(".jpg") || lower.endsWith(".png"));
+ }));
+
+ final ToIntFunction<File> score = (f) -> {
+ final String lower = f.getName().toLowerCase();
+ if (lower.equals("albumart.jpg")) return 4;
+ if (lower.startsWith("albumart") && lower.endsWith(".jpg")) return 3;
+ if (lower.contains("albumart") && lower.endsWith(".jpg")) return 2;
+ if (lower.endsWith(".jpg")) return 1;
+ return 0;
+ };
+ final Comparator<File> bestScore = (a, b) -> {
+ return score.applyAsInt(a) - score.applyAsInt(b);
+ };
+
+ final File bestFile = Arrays.asList(found).stream().max(bestScore).orElse(null);
+ if (bestFile == null) {
+ throw new IOException("No album art found");
+ }
+
+ // Checkpoint before going deeper
+ if (signal != null) signal.throwIfCanceled();
+
+ return ImageDecoder.decodeBitmap(ImageDecoder.createSource(bestFile), resizer);
+ }
+
+ /**
+ * Create a thumbnail for given image file.
+ *
+ * @param filePath The image file.
+ * @param kind The desired thumbnail kind, such as
+ * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}.
+ */
+ @Deprecated
+ public static @Nullable Bitmap createImageThumbnail(@NonNull String filePath, int kind) {
+ try {
+ return createImageThumbnail(new File(filePath), convertKind(kind), null);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ }
+
+ /**
+ * Create a thumbnail for given image file.
+ *
+ * @param file The audio file.
+ * @param size The desired thumbnail size.
+ * @throws IOException If any trouble was encountered while generating or
+ * loading the thumbnail, or if
+ * {@link CancellationSignal#cancel()} was invoked.
+ */
+ public static @NonNull Bitmap createImageThumbnail(@NonNull File file, @NonNull Size size,
+ @Nullable CancellationSignal signal) throws IOException {
+ // Checkpoint before going deeper
+ if (signal != null) signal.throwIfCanceled();
+
+ final Resizer resizer = new Resizer(size, signal);
+ 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")) {
- bitmap = createThumbnailFromMetadataRetriever(filePath, targetSize, maxPixels);
- } else if (MediaFile.isExifMimeType(mimeType)) {
- createThumbnailFromEXIF(filePath, targetSize, maxPixels, sizedThumbnailBitmap);
- bitmap = sizedThumbnailBitmap.mBitmap;
- }
-
- if (bitmap == null) {
- FileInputStream stream = null;
- try {
- stream = new FileInputStream(filePath);
- FileDescriptor fd = stream.getFD();
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = 1;
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFileDescriptor(fd, null, options);
- if (options.mCancel || options.outWidth == -1
- || options.outHeight == -1) {
- return null;
- }
- options.inSampleSize = computeSampleSize(
- options, targetSize, maxPixels);
- options.inJustDecodeBounds = false;
-
- options.inDither = false;
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- bitmap = BitmapFactory.decodeFileDescriptor(fd, null, options);
- } catch (IOException ex) {
- Log.e(TAG, "", ex);
- } catch (OutOfMemoryError oom) {
- Log.e(TAG, "Unable to decode file " + filePath + ". OutOfMemoryError.", oom);
- } finally {
- try {
- if (stream != null) {
- stream.close();
- }
- } catch (IOException ex) {
- Log.e(TAG, "", ex);
- }
+ try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) {
+ retriever.setDataSource(file.getAbsolutePath());
+ return 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);
+ final byte[] raw = exif.getThumbnailBytes();
+ if (raw != null) {
+ return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer);
+ }
}
- if (kind == Images.Thumbnails.MICRO_KIND) {
- // now we make it a "square thumbnail" for MICRO_KIND thumbnail
- bitmap = extractThumbnail(bitmap,
- TARGET_SIZE_MICRO_THUMBNAIL,
- TARGET_SIZE_MICRO_THUMBNAIL, OPTIONS_RECYCLE_INPUT);
- }
- return bitmap;
+ // Checkpoint before going deeper
+ if (signal != null) signal.throwIfCanceled();
+
+ return ImageDecoder.decodeBitmap(ImageDecoder.createSource(file), resizer);
}
/**
- * Create a video thumbnail for a video. May return null if the video is
- * corrupt or the format is not supported.
+ * Create a thumbnail for given video file.
*
- * @param filePath the path of video file
- * @param kind could be MINI_KIND or MICRO_KIND
+ * @param filePath The video file.
+ * @param kind The desired thumbnail kind, such as
+ * {@link android.provider.MediaStore.Images.Thumbnails#MINI_KIND}.
*/
- public static Bitmap createVideoThumbnail(String filePath, int kind) {
- Bitmap bitmap = null;
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ @Deprecated
+ public static @Nullable Bitmap createVideoThumbnail(@NonNull String filePath, int kind) {
try {
- retriever.setDataSource(filePath);
- // First retrieve album art in metadata if set.
- byte[] embeddedPicture = retriever.getEmbeddedPicture();
- if (embeddedPicture != null && embeddedPicture.length > 0) {
- bitmap = BitmapFactory.decodeByteArray(embeddedPicture, 0, embeddedPicture.length);
- }
- // Fall back to first frame of the video.
- if (bitmap == null) {
- bitmap = retriever.getFrameAtTime(-1);
- }
- } catch (IllegalArgumentException ex) {
- // Assume this is a corrupt video file
- } catch (RuntimeException ex) {
- // Assume this is a corrupt video file.
- } finally {
- try {
- retriever.release();
- } catch (RuntimeException ex) {
- // Ignore failures while cleaning up.
- }
+ return createVideoThumbnail(new File(filePath), convertKind(kind), null);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return null;
}
+ }
- if (bitmap == null) return null;
+ /**
+ * Create a thumbnail for given video file.
+ *
+ * @param file The video file.
+ * @param size The desired thumbnail size.
+ * @throws IOException If any trouble was encountered while generating or
+ * loading the thumbnail, or if
+ * {@link CancellationSignal#cancel()} was invoked.
+ */
+ public static @NonNull Bitmap createVideoThumbnail(@NonNull File file, @NonNull Size size,
+ @Nullable CancellationSignal signal) throws IOException {
+ // Checkpoint before going deeper
+ if (signal != null) signal.throwIfCanceled();
- if (kind == Images.Thumbnails.MINI_KIND) {
- // Scale down the bitmap if it's too large.
- int width = bitmap.getWidth();
- int height = bitmap.getHeight();
- int max = Math.max(width, height);
- if (max > 512) {
- float scale = 512f / max;
- int w = Math.round(scale * width);
- int h = Math.round(scale * height);
- bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
+ final Resizer resizer = new Resizer(size, signal);
+ try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+ mmr.setDataSource(file.getAbsolutePath());
+
+ // Try to retrieve thumbnail from metadata
+ final byte[] raw = mmr.getEmbeddedPicture();
+ if (raw != null) {
+ return ImageDecoder.decodeBitmap(ImageDecoder.createSource(raw), resizer);
}
- } else if (kind == Images.Thumbnails.MICRO_KIND) {
- bitmap = extractThumbnail(bitmap,
- TARGET_SIZE_MICRO_THUMBNAIL,
- TARGET_SIZE_MICRO_THUMBNAIL,
- OPTIONS_RECYCLE_INPUT);
+
+ // Fall back to middle of video
+ final int width = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH));
+ final int height = Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT));
+ final long duration = Long.parseLong(mmr.extractMetadata(METADATA_KEY_DURATION));
+
+ // If we're okay with something larger than native format, just
+ // return a frame without up-scaling it
+ if (size.getWidth() > width && size.getHeight() > height) {
+ return mmr.getFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC);
+ } else {
+ return mmr.getScaledFrameAtTime(duration / 2, OPTION_CLOSEST_SYNC,
+ size.getWidth(), size.getHeight());
+ }
+ } catch (RuntimeException e) {
+ throw new IOException("Failed to create thumbnail", e);
}
- return bitmap;
}
/**
@@ -242,122 +361,27 @@
return thumbnail;
}
- /*
- * Compute the sample size as a function of minSideLength
- * and maxNumOfPixels.
- * minSideLength is used to specify that minimal width or height of a
- * bitmap.
- * maxNumOfPixels is used to specify the maximal size in pixels that is
- * tolerable in terms of memory usage.
- *
- * The function returns a sample size based on the constraints.
- * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
- * which indicates no care of the corresponding constraint.
- * The functions prefers returning a sample size that
- * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
- *
- * Also, the function rounds up the sample size to a power of 2 or multiple
- * of 8 because BitmapFactory only honors sample size this way.
- * For example, BitmapFactory downsamples an image by 2 even though the
- * request is 3. So we round up the sample size to avoid OOM.
- */
+ @Deprecated
@UnsupportedAppUsage
private static int computeSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
- int initialSize = computeInitialSampleSize(options, minSideLength,
- maxNumOfPixels);
-
- int roundedSize;
- if (initialSize <= 8 ) {
- roundedSize = 1;
- while (roundedSize < initialSize) {
- roundedSize <<= 1;
- }
- } else {
- roundedSize = (initialSize + 7) / 8 * 8;
- }
-
- return roundedSize;
+ return 1;
}
+ @Deprecated
@UnsupportedAppUsage
private static int computeInitialSampleSize(BitmapFactory.Options options,
int minSideLength, int maxNumOfPixels) {
- double w = options.outWidth;
- double h = options.outHeight;
-
- int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
- (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
- int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
- (int) Math.min(Math.floor(w / minSideLength),
- Math.floor(h / minSideLength));
-
- if (upperBound < lowerBound) {
- // return the larger one when there is no overlapping zone.
- return lowerBound;
- }
-
- if ((maxNumOfPixels == UNCONSTRAINED) &&
- (minSideLength == UNCONSTRAINED)) {
- return 1;
- } else if (minSideLength == UNCONSTRAINED) {
- return lowerBound;
- } else {
- return upperBound;
- }
+ return 1;
}
- /**
- * Make a bitmap from a given Uri, minimal side length, and maximum number of pixels.
- * The image data will be read from specified pfd if it's not null, otherwise
- * a new input stream will be created using specified ContentResolver.
- *
- * Clients are allowed to pass their own BitmapFactory.Options used for bitmap decoding. A
- * new BitmapFactory.Options will be created if options is null.
- */
- private static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
- Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
- BitmapFactory.Options options) {
- Bitmap b = null;
- try {
- if (pfd == null) pfd = makeInputStream(uri, cr);
- if (pfd == null) return null;
- if (options == null) options = new BitmapFactory.Options();
-
- FileDescriptor fd = pfd.getFileDescriptor();
- options.inSampleSize = 1;
- options.inJustDecodeBounds = true;
- BitmapFactory.decodeFileDescriptor(fd, null, options);
- if (options.mCancel || options.outWidth == -1
- || options.outHeight == -1) {
- return null;
- }
- options.inSampleSize = computeSampleSize(
- options, minSideLength, maxNumOfPixels);
- options.inJustDecodeBounds = false;
-
- options.inDither = false;
- options.inPreferredConfig = Bitmap.Config.ARGB_8888;
- b = BitmapFactory.decodeFileDescriptor(fd, null, options);
- } catch (OutOfMemoryError ex) {
- Log.e(TAG, "Got oom exception ", ex);
- return null;
- } finally {
- closeSilently(pfd);
- }
- return b;
- }
-
+ @Deprecated
@UnsupportedAppUsage
private static void closeSilently(ParcelFileDescriptor c) {
- if (c == null) return;
- try {
- c.close();
- } catch (Throwable t) {
- // do nothing
- }
+ IoUtils.closeQuietly(c);
}
+ @Deprecated
@UnsupportedAppUsage
private static ParcelFileDescriptor makeInputStream(
Uri uri, ContentResolver cr) {
@@ -371,6 +395,7 @@
/**
* Transform source Bitmap to targeted width and height.
*/
+ @Deprecated
@UnsupportedAppUsage
private static Bitmap transform(Matrix scaler,
Bitmap source,
@@ -468,14 +493,7 @@
return b2;
}
- /**
- * SizedThumbnailBitmap contains the bitmap, which is downsampled either from
- * the thumbnail in exif or the full image.
- * mThumbnailData, mThumbnailWidth and mThumbnailHeight are set together only if mThumbnail
- * is not null.
- *
- * The width/height of the sized bitmap may be different from mThumbnailWidth/mThumbnailHeight.
- */
+ @Deprecated
private static class SizedThumbnailBitmap {
public byte[] mThumbnailData;
public Bitmap mBitmap;
@@ -483,81 +501,9 @@
public int mThumbnailHeight;
}
- /**
- * Creates a bitmap by either downsampling from the thumbnail in EXIF or the full image.
- * The functions returns a SizedThumbnailBitmap,
- * which contains a downsampled bitmap and the thumbnail data in EXIF if exists.
- */
+ @Deprecated
@UnsupportedAppUsage
private static void createThumbnailFromEXIF(String filePath, int targetSize,
int maxPixels, SizedThumbnailBitmap sizedThumbBitmap) {
- if (filePath == null) return;
-
- ExifInterface exif = null;
- byte [] thumbData = null;
- try {
- exif = new ExifInterface(filePath);
- thumbData = exif.getThumbnail();
- } catch (IOException ex) {
- Log.w(TAG, ex);
- }
-
- BitmapFactory.Options fullOptions = new BitmapFactory.Options();
- BitmapFactory.Options exifOptions = new BitmapFactory.Options();
- int exifThumbWidth = 0;
- int fullThumbWidth = 0;
-
- // Compute exifThumbWidth.
- if (thumbData != null) {
- exifOptions.inJustDecodeBounds = true;
- BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, exifOptions);
- exifOptions.inSampleSize = computeSampleSize(exifOptions, targetSize, maxPixels);
- exifThumbWidth = exifOptions.outWidth / exifOptions.inSampleSize;
- }
-
- // Compute fullThumbWidth.
- fullOptions.inJustDecodeBounds = true;
- BitmapFactory.decodeFile(filePath, fullOptions);
- fullOptions.inSampleSize = computeSampleSize(fullOptions, targetSize, maxPixels);
- fullThumbWidth = fullOptions.outWidth / fullOptions.inSampleSize;
-
- // Choose the larger thumbnail as the returning sizedThumbBitmap.
- if (thumbData != null && exifThumbWidth >= fullThumbWidth) {
- int width = exifOptions.outWidth;
- int height = exifOptions.outHeight;
- exifOptions.inJustDecodeBounds = false;
- sizedThumbBitmap.mBitmap = BitmapFactory.decodeByteArray(thumbData, 0,
- thumbData.length, exifOptions);
- if (sizedThumbBitmap.mBitmap != null) {
- sizedThumbBitmap.mThumbnailData = thumbData;
- sizedThumbBitmap.mThumbnailWidth = width;
- sizedThumbBitmap.mThumbnailHeight = height;
- }
- } else {
- fullOptions.inJustDecodeBounds = false;
- sizedThumbBitmap.mBitmap = BitmapFactory.decodeFile(filePath, fullOptions);
- }
- }
-
- private static Bitmap createThumbnailFromMetadataRetriever(
- String filePath, int targetSize, int maxPixels) {
- if (filePath == null) {
- return null;
- }
- Bitmap thumbnail = null;
- MediaMetadataRetriever retriever = new MediaMetadataRetriever();
- try {
- retriever.setDataSource(filePath);
- MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
- params.setPreferredConfig(Bitmap.Config.ARGB_8888);
- thumbnail = retriever.getThumbnailImageAtIndex(-1, params, targetSize, maxPixels);
- } catch (RuntimeException ex) {
- // Assume this is a corrupt video file.
- } finally {
- if (retriever != null) {
- retriever.release();
- }
- }
- return thumbnail;
}
}