Merge "Return EXIF thumbnails when available." into klp-dev
This commit is contained in:
@ -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();
|
||||
|
||||
bitmapOpts.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
final int widthSample = bitmapOpts.outWidth / size.x;
|
||||
final int heightSample = bitmapOpts.outHeight / size.y;
|
||||
// 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);
|
||||
}
|
||||
|
||||
bitmapOpts.inJustDecodeBounds = false;
|
||||
bitmapOpts.inSampleSize = Math.min(widthSample, heightSample);
|
||||
return BitmapFactory.decodeFileDescriptor(fd, null, bitmapOpts);
|
||||
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;
|
||||
|
@ -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
|
||||
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
@ -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)) {
|
||||
|
Reference in New Issue
Block a user