Loading core/java/android/provider/MediaStore.java +17 −13 Original line number Diff line number Diff line Loading @@ -245,7 +245,7 @@ public final class MediaStore { * requests can cancel their own requests. * * @param cr ContentResolver * @param origId original image or video id * @param origId original image or video id. use -1 to cancel all requests. * @param baseUri the base URI of requested thumbnails */ static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) { Loading Loading @@ -340,24 +340,28 @@ public final class MediaStore { // We probably run out of space, so create the thumbnail in memory. if (bitmap == null) { Log.v(TAG, "We probably run out of space, so create the thumbnail in memory."); int targetSize = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE; int maxPixelNum = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS; Uri uri = Uri.parse( baseUri.buildUpon().appendPath(String.valueOf(origId)) .toString().replaceFirst("thumbnails", "media")); if (isVideo) { if (filePath == null) { c = cr.query(uri, PROJECTION, null, null, null); if (c != null && c.moveToFirst()) { bitmap = ThumbnailUtil.createVideoThumbnail(c.getString(1)); if (c == null || !c.moveToFirst()) { return null; } filePath = c.getString(1); } if (isVideo) { bitmap = ThumbnailUtil.createVideoThumbnail(filePath); if (kind == MICRO_KIND) { bitmap = ThumbnailUtil.extractMiniThumb(bitmap, targetSize, targetSize, ThumbnailUtil.RECYCLE_INPUT); } ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.RECYCLE_INPUT); } } else { bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixelNum, uri, cr); bitmap = ThumbnailUtil.createImageThumbnail(cr, filePath, uri, origId, kind, false); } } } catch (SQLiteException ex) { Loading media/java/android/media/MiniThumbFile.java +6 −9 Original line number Diff line number Diff line Loading @@ -61,6 +61,9 @@ public class MiniThumbFile { * we should hashcode of content://media/external/images/media remains the same. */ public static synchronized void reset() { for (MiniThumbFile file : sThumbFiles.values()) { file.deactivate(); } sThumbFiles.clear(); } Loading Loading @@ -144,7 +147,7 @@ public class MiniThumbFile { // Get the magic number for the specified id in the mini-thumb file. // Returns 0 if the magic is not available. public long getMagic(long id) { public synchronized long getMagic(long id) { // check the mini thumb file for the right data. Right is // defined as having the right magic number at the offset // reserved for this "id". Loading Loading @@ -183,13 +186,7 @@ public class MiniThumbFile { return 0; } public void saveMiniThumbToFile(Bitmap bitmap, long id, long magic) throws IOException { byte[] data = ThumbnailUtil.miniThumbData(bitmap); saveMiniThumbToFile(data, id, magic); } public void saveMiniThumbToFile(byte[] data, long id, long magic) public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); if (r == null) return; Loading Loading @@ -237,7 +234,7 @@ public class MiniThumbFile { * @param id the ID of the image (same of full size image). * @param data the buffer to store mini-thumbnail. */ public byte [] getMiniThumbFromFile(long id, byte [] data) { public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) { RandomAccessFile r = miniThumbDataFile(); if (r == null) return null; Loading media/java/android/media/ThumbnailUtil.java +169 −26 Original line number Diff line number Diff line Loading @@ -18,9 +18,15 @@ package android.media; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.BaseColumns; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.Thumbnails; import android.util.Log; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; Loading Loading @@ -249,32 +255,6 @@ public class ThumbnailUtil { return miniThumbnail; } /** * Creates a byte[] for a given bitmap of the desired size. Recycles the * input bitmap. */ public static byte[] miniThumbData(Bitmap source) { if (source == null) return null; Bitmap miniThumbnail = extractMiniThumb( source, MINI_THUMB_TARGET_SIZE, MINI_THUMB_TARGET_SIZE, RECYCLE_INPUT); ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream(); miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream); miniThumbnail.recycle(); try { miniOutStream.close(); byte [] data = miniOutStream.toByteArray(); return data; } catch (java.io.IOException ex) { Log.e(TAG, "got exception ex " + ex); } return null; } /** * Create a video thumbnail for a video. May return null if the video is * corrupt. Loading Loading @@ -302,6 +282,67 @@ public class ThumbnailUtil { return bitmap; } /** * 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. * * This method always returns a "square thumbnail" for MICRO_KIND thumbnail. * * @param cr ContentResolver * @param filePath file path needed by EXIF interface * @param uri URI of original image * @param origId image id * @param kind either MINI_KIND or MICRO_KIND * @param saveImage Whether to save MINI_KIND thumbnail obtained in this method. * @return Bitmap */ public static Bitmap createImageThumbnail(ContentResolver cr, String filePath, Uri uri, long origId, int kind, boolean saveMini) { boolean wantMini = (kind == Images.Thumbnails.MINI_KIND || saveMini); int targetSize = wantMini ? ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE; int maxPixels = wantMini ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS; byte[] thumbData = createThumbnailFromEXIF(filePath, targetSize); Bitmap bitmap = null; if (thumbData != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = computeSampleSize(options, targetSize, maxPixels); options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inJustDecodeBounds = false; bitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); } if (bitmap == null) { bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixels, uri, cr); } if (bitmap == null) { return null; } if (saveMini) { if (thumbData != null) { ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(), bitmap.getHeight()); } else { ThumbnailUtil.storeThumbnail(cr, origId, bitmap); } } if (kind == Images.Thumbnails.MICRO_KIND) { // now we make it a "square thumbnail" for MICRO_KIND thumbnail bitmap = ThumbnailUtil.extractMiniThumb(bitmap, ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.RECYCLE_INPUT); } return bitmap; } public static Bitmap transform(Matrix scaler, Bitmap source, int targetWidth, Loading Loading @@ -396,6 +437,108 @@ public class ThumbnailUtil { return b2; } private static final String[] THUMB_PROJECTION = new String[] { BaseColumns._ID // 0 }; /** * Look up thumbnail uri by given imageId, it will be automatically created if it's not created * yet. Most of the time imageId is identical to thumbId, but it's not always true. * @param req * @param width * @param height * @return Uri Thumbnail uri */ private static Uri getImageThumbnailUri(ContentResolver cr, long origId, int width, int height) { Uri thumbUri = Images.Thumbnails.EXTERNAL_CONTENT_URI; Cursor c = cr.query(thumbUri, THUMB_PROJECTION, Thumbnails.IMAGE_ID + "=?", new String[]{String.valueOf(origId)}, null); try { if (c.moveToNext()) { return ContentUris.withAppendedId(thumbUri, c.getLong(0)); } } finally { if (c != null) c.close(); } ContentValues values = new ContentValues(4); values.put(Thumbnails.KIND, Thumbnails.MINI_KIND); values.put(Thumbnails.IMAGE_ID, origId); values.put(Thumbnails.HEIGHT, height); values.put(Thumbnails.WIDTH, width); try { return cr.insert(thumbUri, values); } catch (Exception ex) { Log.w(TAG, ex); return null; } } /** * Store a given thumbnail in the database. (Bitmap) */ private static boolean storeThumbnail(ContentResolver cr, long origId, Bitmap thumb) { if (thumb == null) return false; try { Uri uri = getImageThumbnailUri(cr, origId, thumb.getWidth(), thumb.getHeight()); OutputStream thumbOut = cr.openOutputStream(uri); thumb.compress(Bitmap.CompressFormat.JPEG, 85, thumbOut); thumbOut.close(); return true; } catch (Throwable t) { Log.e(TAG, "Unable to store thumbnail", t); return false; } } /** * Store a given thumbnail in the database. (byte array) */ private static boolean storeThumbnail(ContentResolver cr, long origId, byte[] jpegThumbnail, int width, int height) { if (jpegThumbnail == null) return false; Uri uri = getImageThumbnailUri(cr, origId, width, height); if (uri == null) { return false; } try { OutputStream thumbOut = cr.openOutputStream(uri); thumbOut.write(jpegThumbnail); thumbOut.close(); return true; } catch (Throwable t) { Log.e(TAG, "Unable to store thumbnail", t); return false; } } // Extract thumbnail in image that meets the targetSize criteria. static byte[] createThumbnailFromEXIF(String filePath, int targetSize) { if (filePath == null) return null; try { ExifInterface exif = new ExifInterface(filePath); if (exif == null) return null; byte [] thumbData = exif.getThumbnail(); if (thumbData == null) return null; // Sniff the size of the EXIF thumbnail before decoding it. Photos // from the device will pass, but images that are side loaded from // other cameras may not. BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); int width = options.outWidth; int height = options.outHeight; if (width >= targetSize && height >= targetSize) { return thumbData; } } catch (IOException ex) { Log.w(TAG, ex); } return null; } } Loading
core/java/android/provider/MediaStore.java +17 −13 Original line number Diff line number Diff line Loading @@ -245,7 +245,7 @@ public final class MediaStore { * requests can cancel their own requests. * * @param cr ContentResolver * @param origId original image or video id * @param origId original image or video id. use -1 to cancel all requests. * @param baseUri the base URI of requested thumbnails */ static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri) { Loading Loading @@ -340,24 +340,28 @@ public final class MediaStore { // We probably run out of space, so create the thumbnail in memory. if (bitmap == null) { Log.v(TAG, "We probably run out of space, so create the thumbnail in memory."); int targetSize = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE; int maxPixelNum = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS; Uri uri = Uri.parse( baseUri.buildUpon().appendPath(String.valueOf(origId)) .toString().replaceFirst("thumbnails", "media")); if (isVideo) { if (filePath == null) { c = cr.query(uri, PROJECTION, null, null, null); if (c != null && c.moveToFirst()) { bitmap = ThumbnailUtil.createVideoThumbnail(c.getString(1)); if (c == null || !c.moveToFirst()) { return null; } filePath = c.getString(1); } if (isVideo) { bitmap = ThumbnailUtil.createVideoThumbnail(filePath); if (kind == MICRO_KIND) { bitmap = ThumbnailUtil.extractMiniThumb(bitmap, targetSize, targetSize, ThumbnailUtil.RECYCLE_INPUT); } ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.RECYCLE_INPUT); } } else { bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixelNum, uri, cr); bitmap = ThumbnailUtil.createImageThumbnail(cr, filePath, uri, origId, kind, false); } } } catch (SQLiteException ex) { Loading
media/java/android/media/MiniThumbFile.java +6 −9 Original line number Diff line number Diff line Loading @@ -61,6 +61,9 @@ public class MiniThumbFile { * we should hashcode of content://media/external/images/media remains the same. */ public static synchronized void reset() { for (MiniThumbFile file : sThumbFiles.values()) { file.deactivate(); } sThumbFiles.clear(); } Loading Loading @@ -144,7 +147,7 @@ public class MiniThumbFile { // Get the magic number for the specified id in the mini-thumb file. // Returns 0 if the magic is not available. public long getMagic(long id) { public synchronized long getMagic(long id) { // check the mini thumb file for the right data. Right is // defined as having the right magic number at the offset // reserved for this "id". Loading Loading @@ -183,13 +186,7 @@ public class MiniThumbFile { return 0; } public void saveMiniThumbToFile(Bitmap bitmap, long id, long magic) throws IOException { byte[] data = ThumbnailUtil.miniThumbData(bitmap); saveMiniThumbToFile(data, id, magic); } public void saveMiniThumbToFile(byte[] data, long id, long magic) public synchronized void saveMiniThumbToFile(byte[] data, long id, long magic) throws IOException { RandomAccessFile r = miniThumbDataFile(); if (r == null) return; Loading Loading @@ -237,7 +234,7 @@ public class MiniThumbFile { * @param id the ID of the image (same of full size image). * @param data the buffer to store mini-thumbnail. */ public byte [] getMiniThumbFromFile(long id, byte [] data) { public synchronized byte [] getMiniThumbFromFile(long id, byte [] data) { RandomAccessFile r = miniThumbDataFile(); if (r == null) return null; Loading
media/java/android/media/ThumbnailUtil.java +169 −26 Original line number Diff line number Diff line Loading @@ -18,9 +18,15 @@ package android.media; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.BaseColumns; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.Thumbnails; import android.util.Log; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; Loading Loading @@ -249,32 +255,6 @@ public class ThumbnailUtil { return miniThumbnail; } /** * Creates a byte[] for a given bitmap of the desired size. Recycles the * input bitmap. */ public static byte[] miniThumbData(Bitmap source) { if (source == null) return null; Bitmap miniThumbnail = extractMiniThumb( source, MINI_THUMB_TARGET_SIZE, MINI_THUMB_TARGET_SIZE, RECYCLE_INPUT); ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream(); miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream); miniThumbnail.recycle(); try { miniOutStream.close(); byte [] data = miniOutStream.toByteArray(); return data; } catch (java.io.IOException ex) { Log.e(TAG, "got exception ex " + ex); } return null; } /** * Create a video thumbnail for a video. May return null if the video is * corrupt. Loading Loading @@ -302,6 +282,67 @@ public class ThumbnailUtil { return bitmap; } /** * 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. * * This method always returns a "square thumbnail" for MICRO_KIND thumbnail. * * @param cr ContentResolver * @param filePath file path needed by EXIF interface * @param uri URI of original image * @param origId image id * @param kind either MINI_KIND or MICRO_KIND * @param saveImage Whether to save MINI_KIND thumbnail obtained in this method. * @return Bitmap */ public static Bitmap createImageThumbnail(ContentResolver cr, String filePath, Uri uri, long origId, int kind, boolean saveMini) { boolean wantMini = (kind == Images.Thumbnails.MINI_KIND || saveMini); int targetSize = wantMini ? ThumbnailUtil.THUMBNAIL_TARGET_SIZE : ThumbnailUtil.MINI_THUMB_TARGET_SIZE; int maxPixels = wantMini ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS : ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS; byte[] thumbData = createThumbnailFromEXIF(filePath, targetSize); Bitmap bitmap = null; if (thumbData != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = computeSampleSize(options, targetSize, maxPixels); options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inJustDecodeBounds = false; bitmap = BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); } if (bitmap == null) { bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixels, uri, cr); } if (bitmap == null) { return null; } if (saveMini) { if (thumbData != null) { ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(), bitmap.getHeight()); } else { ThumbnailUtil.storeThumbnail(cr, origId, bitmap); } } if (kind == Images.Thumbnails.MICRO_KIND) { // now we make it a "square thumbnail" for MICRO_KIND thumbnail bitmap = ThumbnailUtil.extractMiniThumb(bitmap, ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.MINI_THUMB_TARGET_SIZE, ThumbnailUtil.RECYCLE_INPUT); } return bitmap; } public static Bitmap transform(Matrix scaler, Bitmap source, int targetWidth, Loading Loading @@ -396,6 +437,108 @@ public class ThumbnailUtil { return b2; } private static final String[] THUMB_PROJECTION = new String[] { BaseColumns._ID // 0 }; /** * Look up thumbnail uri by given imageId, it will be automatically created if it's not created * yet. Most of the time imageId is identical to thumbId, but it's not always true. * @param req * @param width * @param height * @return Uri Thumbnail uri */ private static Uri getImageThumbnailUri(ContentResolver cr, long origId, int width, int height) { Uri thumbUri = Images.Thumbnails.EXTERNAL_CONTENT_URI; Cursor c = cr.query(thumbUri, THUMB_PROJECTION, Thumbnails.IMAGE_ID + "=?", new String[]{String.valueOf(origId)}, null); try { if (c.moveToNext()) { return ContentUris.withAppendedId(thumbUri, c.getLong(0)); } } finally { if (c != null) c.close(); } ContentValues values = new ContentValues(4); values.put(Thumbnails.KIND, Thumbnails.MINI_KIND); values.put(Thumbnails.IMAGE_ID, origId); values.put(Thumbnails.HEIGHT, height); values.put(Thumbnails.WIDTH, width); try { return cr.insert(thumbUri, values); } catch (Exception ex) { Log.w(TAG, ex); return null; } } /** * Store a given thumbnail in the database. (Bitmap) */ private static boolean storeThumbnail(ContentResolver cr, long origId, Bitmap thumb) { if (thumb == null) return false; try { Uri uri = getImageThumbnailUri(cr, origId, thumb.getWidth(), thumb.getHeight()); OutputStream thumbOut = cr.openOutputStream(uri); thumb.compress(Bitmap.CompressFormat.JPEG, 85, thumbOut); thumbOut.close(); return true; } catch (Throwable t) { Log.e(TAG, "Unable to store thumbnail", t); return false; } } /** * Store a given thumbnail in the database. (byte array) */ private static boolean storeThumbnail(ContentResolver cr, long origId, byte[] jpegThumbnail, int width, int height) { if (jpegThumbnail == null) return false; Uri uri = getImageThumbnailUri(cr, origId, width, height); if (uri == null) { return false; } try { OutputStream thumbOut = cr.openOutputStream(uri); thumbOut.write(jpegThumbnail); thumbOut.close(); return true; } catch (Throwable t) { Log.e(TAG, "Unable to store thumbnail", t); return false; } } // Extract thumbnail in image that meets the targetSize criteria. static byte[] createThumbnailFromEXIF(String filePath, int targetSize) { if (filePath == null) return null; try { ExifInterface exif = new ExifInterface(filePath); if (exif == null) return null; byte [] thumbData = exif.getThumbnail(); if (thumbData == null) return null; // Sniff the size of the EXIF thumbnail before decoding it. Photos // from the device will pass, but images that are side loaded from // other cameras may not. BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(thumbData, 0, thumbData.length, options); int width = options.outWidth; int height = options.outHeight; if (width >= targetSize && height >= targetSize) { return thumbData; } } catch (IOException ex) { Log.w(TAG, ex); } return null; } }