Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 365768fd authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Return EXIF thumbnails when available." into klp-dev

parents 98d62123 6398343e
Loading
Loading
Loading
Loading
+45 −11
Original line number Diff line number Diff line
@@ -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();

            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;
+16 −0
Original line number Diff line number Diff line
@@ -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);
}
+36 −1
Original line number Diff line number Diff line
@@ -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)) {