summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-08-23 23:18:11 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2013-08-23 23:18:11 +0000
commit365768fd3533343d6631875d7d46882907f7ab09 (patch)
tree2229069f7e5fdcd8006a97c4664e8466f82bda3e
parent98d6212327d1c483894b642d5e24c3aac9fca9cf (diff)
parent6398343e83b3fd11dd6536cf6f390a52c1e19d2e (diff)
downloadframeworks_base-365768fd3533343d6631875d7d46882907f7ab09.zip
frameworks_base-365768fd3533343d6631875d7d46882907f7ab09.tar.gz
frameworks_base-365768fd3533343d6631875d7d46882907f7ab09.tar.bz2
Merge "Return EXIF thumbnails when available." into klp-dev
-rw-r--r--core/java/android/provider/DocumentsContract.java56
-rw-r--r--media/java/android/media/ExifInterface.java16
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java37
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: {