diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-08-23 23:18:11 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-08-23 23:18:11 +0000 |
commit | 365768fd3533343d6631875d7d46882907f7ab09 (patch) | |
tree | 2229069f7e5fdcd8006a97c4664e8466f82bda3e | |
parent | 98d6212327d1c483894b642d5e24c3aac9fca9cf (diff) | |
parent | 6398343e83b3fd11dd6536cf6f390a52c1e19d2e (diff) | |
download | frameworks_base-365768fd3533343d6631875d7d46882907f7ab09.zip frameworks_base-365768fd3533343d6631875d7d46882907f7ab09.tar.gz frameworks_base-365768fd3533343d6631875d7d46882907f7ab09.tar.bz2 |
Merge "Return EXIF thumbnails when available." into klp-dev
3 files changed, 97 insertions, 12 deletions
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 0d1a740..65c9220 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -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; @@ -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; @@ -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(); + 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; + } + } - bitmapOpts.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); + // 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 = bitmapOpts.outWidth / size.x; - final int heightSample = bitmapOpts.outHeight / size.y; + final int widthSample = opts.outWidth / size.x; + final int heightSample = opts.outHeight / size.y; - bitmapOpts.inJustDecodeBounds = false; - bitmapOpts.inSampleSize = Math.min(widthSample, heightSample); - return BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts); + 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; diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 4cd3e37..20eb356 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -291,6 +291,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 * Exif tags are not available. @@ -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); } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index b4bf563..8843e19 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -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; @@ -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)); } @@ -307,6 +309,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)) { case URI_DOCS_ID: { |