Loading core/java/android/provider/DocumentsContract.java +45 −11 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.provider; import static android.net.TrafficStats.KB_IN_BYTES; import static libcore.io.OsConstants.SEEK_SET; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; Loading @@ -36,7 +39,10 @@ import android.util.Log; import com.google.android.collect.Lists; import libcore.io.ErrnoException; import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; import java.io.FileDescriptor; import java.io.IOException; Loading Loading @@ -527,25 +533,53 @@ public final class DocumentsContract { * @return decoded thumbnail, or {@code null} if problem was encountered. */ public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) { final Bundle opts = new Bundle(); opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size); final Bundle openOpts = new Bundle(); openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); AssetFileDescriptor afd = null; try { afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts); afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts); final FileDescriptor fd = afd.getFileDescriptor(); final BitmapFactory.Options bitmapOpts = new BitmapFactory.Options(); bitmapOpts.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); final int widthSample = bitmapOpts.outWidth / size.x; final int heightSample = bitmapOpts.outHeight / size.y; bitmapOpts.inJustDecodeBounds = false; bitmapOpts.inSampleSize = Math.min(widthSample, heightSample); return BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); final long offset = afd.getStartOffset(); final long length = afd.getDeclaredLength(); // Some thumbnails might be a region inside a larger file, such as // an EXIF thumbnail. Since BitmapFactory aggressively seeks around // the entire file, we read the region manually. byte[] region = null; if (offset > 0 && length <= 64 * KB_IN_BYTES) { region = new byte[(int) length]; Libcore.os.lseek(fd, offset, SEEK_SET); if (IoBridge.read(fd, region, 0, region.length) != region.length) { region = null; } } // We requested a rough thumbnail size, but the remote size may have // returned something giant, so defensively scale down as needed. final BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; if (region != null) { BitmapFactory.decodeByteArray(region, 0, region.length, opts); } else { BitmapFactory.decodeFileDescriptor(fd, null, opts); } final int widthSample = opts.outWidth / size.x; final int heightSample = opts.outHeight / size.y; opts.inJustDecodeBounds = false; opts.inSampleSize = Math.min(widthSample, heightSample); Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); if (region != null) { return BitmapFactory.decodeByteArray(region, 0, region.length, opts); } else { return BitmapFactory.decodeFileDescriptor(fd, null, opts); } } catch (ErrnoException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; } catch (IOException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; Loading media/java/android/media/ExifInterface.java +16 −0 Original line number Diff line number Diff line Loading @@ -290,6 +290,20 @@ public class ExifInterface { } } /** * Returns the offset and length of thumbnail inside the JPEG file, or * {@code null} if there is no thumbnail. * * @return two-element array, the offset in the first value, and length in * the second, or {@code null} if no thumbnail was found. * @hide */ public long[] getThumbnailRange() { synchronized (sLock) { return getThumbnailRangeNative(mFilename); } } /** * Stores the latitude and longitude value in a float array. The first element is * the latitude, and the second element is the longitude. Returns false if the Loading Loading @@ -416,4 +430,6 @@ public class ExifInterface { private native void commitChangesNative(String fileName); private native byte[] getThumbnailNative(String fileName); private native long[] getThumbnailRangeNative(String fileName); } packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +36 −1 Original line number Diff line number Diff line Loading @@ -20,10 +20,13 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.UriMatcher; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; import android.media.ExifInterface; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; Loading Loading @@ -296,7 +299,6 @@ public class ExternalStorageProvider extends ContentProvider { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); // TODO: offer as thumbnail final File file = docIdToFile(root, docId); return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode)); } Loading @@ -306,6 +308,39 @@ public class ExternalStorageProvider extends ContentProvider { } } @Override public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) throws FileNotFoundException { if (opts == null || !opts.containsKey(DocumentsContract.EXTRA_THUMBNAIL_SIZE)) { return super.openTypedAssetFile(uri, mimeTypeFilter, opts); } switch (sMatcher.match(uri)) { case URI_DOCS_ID: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); final File file = docIdToFile(root, docId); final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( file, ParcelFileDescriptor.MODE_READ_ONLY); try { final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); final long[] thumb = exif.getThumbnailRange(); if (thumb != null) { return new AssetFileDescriptor(pfd, thumb[0], thumb[1]); } } catch (IOException e) { } return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); } default: { throw new UnsupportedOperationException("Unsupported Uri " + uri); } } } @Override public Uri insert(Uri uri, ContentValues values) { switch (sMatcher.match(uri)) { Loading Loading
core/java/android/provider/DocumentsContract.java +45 −11 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.provider; import static android.net.TrafficStats.KB_IN_BYTES; import static libcore.io.OsConstants.SEEK_SET; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; Loading @@ -36,7 +39,10 @@ import android.util.Log; import com.google.android.collect.Lists; import libcore.io.ErrnoException; import libcore.io.IoBridge; import libcore.io.IoUtils; import libcore.io.Libcore; import java.io.FileDescriptor; import java.io.IOException; Loading Loading @@ -527,25 +533,53 @@ public final class DocumentsContract { * @return decoded thumbnail, or {@code null} if problem was encountered. */ public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) { final Bundle opts = new Bundle(); opts.putParcelable(EXTRA_THUMBNAIL_SIZE, size); final Bundle openOpts = new Bundle(); openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); AssetFileDescriptor afd = null; try { afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", opts); afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts); final FileDescriptor fd = afd.getFileDescriptor(); final BitmapFactory.Options bitmapOpts = new BitmapFactory.Options(); bitmapOpts.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); final int widthSample = bitmapOpts.outWidth / size.x; final int heightSample = bitmapOpts.outHeight / size.y; bitmapOpts.inJustDecodeBounds = false; bitmapOpts.inSampleSize = Math.min(widthSample, heightSample); return BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); final long offset = afd.getStartOffset(); final long length = afd.getDeclaredLength(); // Some thumbnails might be a region inside a larger file, such as // an EXIF thumbnail. Since BitmapFactory aggressively seeks around // the entire file, we read the region manually. byte[] region = null; if (offset > 0 && length <= 64 * KB_IN_BYTES) { region = new byte[(int) length]; Libcore.os.lseek(fd, offset, SEEK_SET); if (IoBridge.read(fd, region, 0, region.length) != region.length) { region = null; } } // We requested a rough thumbnail size, but the remote size may have // returned something giant, so defensively scale down as needed. final BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; if (region != null) { BitmapFactory.decodeByteArray(region, 0, region.length, opts); } else { BitmapFactory.decodeFileDescriptor(fd, null, opts); } final int widthSample = opts.outWidth / size.x; final int heightSample = opts.outHeight / size.y; opts.inJustDecodeBounds = false; opts.inSampleSize = Math.min(widthSample, heightSample); Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); if (region != null) { return BitmapFactory.decodeByteArray(region, 0, region.length, opts); } else { return BitmapFactory.decodeFileDescriptor(fd, null, opts); } } catch (ErrnoException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; } catch (IOException e) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); return null; Loading
media/java/android/media/ExifInterface.java +16 −0 Original line number Diff line number Diff line Loading @@ -290,6 +290,20 @@ public class ExifInterface { } } /** * Returns the offset and length of thumbnail inside the JPEG file, or * {@code null} if there is no thumbnail. * * @return two-element array, the offset in the first value, and length in * the second, or {@code null} if no thumbnail was found. * @hide */ public long[] getThumbnailRange() { synchronized (sLock) { return getThumbnailRangeNative(mFilename); } } /** * Stores the latitude and longitude value in a float array. The first element is * the latitude, and the second element is the longitude. Returns false if the Loading Loading @@ -416,4 +430,6 @@ public class ExifInterface { private native void commitChangesNative(String fileName); private native byte[] getThumbnailNative(String fileName); private native long[] getThumbnailRangeNative(String fileName); }
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +36 −1 Original line number Diff line number Diff line Loading @@ -20,10 +20,13 @@ import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.UriMatcher; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; import android.media.ExifInterface; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; Loading Loading @@ -296,7 +299,6 @@ public class ExternalStorageProvider extends ContentProvider { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); // TODO: offer as thumbnail final File file = docIdToFile(root, docId); return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode)); } Loading @@ -306,6 +308,39 @@ public class ExternalStorageProvider extends ContentProvider { } } @Override public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) throws FileNotFoundException { if (opts == null || !opts.containsKey(DocumentsContract.EXTRA_THUMBNAIL_SIZE)) { return super.openTypedAssetFile(uri, mimeTypeFilter, opts); } switch (sMatcher.match(uri)) { case URI_DOCS_ID: { final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final String docId = DocumentsContract.getDocId(uri); final File file = docIdToFile(root, docId); final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( file, ParcelFileDescriptor.MODE_READ_ONLY); try { final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); final long[] thumb = exif.getThumbnailRange(); if (thumb != null) { return new AssetFileDescriptor(pfd, thumb[0], thumb[1]); } } catch (IOException e) { } return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); } default: { throw new UnsupportedOperationException("Unsupported Uri " + uri); } } } @Override public Uri insert(Uri uri, ContentValues values) { switch (sMatcher.match(uri)) { Loading