Merge "Add extras to AFD, send orientation metadata." into klp-dev

This commit is contained in:
Jeff Sharkey
2013-10-15 00:00:20 +00:00
committed by Android (Google) Code Review
4 changed files with 124 additions and 24 deletions

View File

@ -7497,11 +7497,13 @@ package android.content.res {
public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable { public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
ctor public AssetFileDescriptor(android.os.ParcelFileDescriptor, long, long); ctor public AssetFileDescriptor(android.os.ParcelFileDescriptor, long, long);
ctor public AssetFileDescriptor(android.os.ParcelFileDescriptor, long, long, android.os.Bundle);
method public void close() throws java.io.IOException; method public void close() throws java.io.IOException;
method public java.io.FileInputStream createInputStream() throws java.io.IOException; method public java.io.FileInputStream createInputStream() throws java.io.IOException;
method public java.io.FileOutputStream createOutputStream() throws java.io.IOException; method public java.io.FileOutputStream createOutputStream() throws java.io.IOException;
method public int describeContents(); method public int describeContents();
method public long getDeclaredLength(); method public long getDeclaredLength();
method public android.os.Bundle getExtras();
method public java.io.FileDescriptor getFileDescriptor(); method public java.io.FileDescriptor getFileDescriptor();
method public long getLength(); method public long getLength();
method public android.os.ParcelFileDescriptor getParcelFileDescriptor(); method public android.os.ParcelFileDescriptor getParcelFileDescriptor();

View File

@ -16,6 +16,7 @@
package android.content.res; package android.content.res;
import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.os.Parcelable; import android.os.Parcelable;
@ -42,17 +43,35 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
private final ParcelFileDescriptor mFd; private final ParcelFileDescriptor mFd;
private final long mStartOffset; private final long mStartOffset;
private final long mLength; private final long mLength;
private final Bundle mExtras;
/** /**
* Create a new AssetFileDescriptor from the given values. * Create a new AssetFileDescriptor from the given values.
*
* @param fd The underlying file descriptor. * @param fd The underlying file descriptor.
* @param startOffset The location within the file that the asset starts. * @param startOffset The location within the file that the asset starts.
* This must be 0 if length is UNKNOWN_LENGTH. * This must be 0 if length is UNKNOWN_LENGTH.
* @param length The number of bytes of the asset, or * @param length The number of bytes of the asset, or
* {@link #UNKNOWN_LENGTH} if it extends to the end of the file. * {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
*/ */
public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset, public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
long length) { long length) {
this(fd, startOffset, length, null);
}
/**
* Create a new AssetFileDescriptor from the given values.
*
* @param fd The underlying file descriptor.
* @param startOffset The location within the file that the asset starts.
* This must be 0 if length is UNKNOWN_LENGTH.
* @param length The number of bytes of the asset, or
* {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
* @param extras additional details that can be used to interpret the
* underlying file descriptor. May be null.
*/
public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
long length, Bundle extras) {
if (fd == null) { if (fd == null) {
throw new IllegalArgumentException("fd must not be null"); throw new IllegalArgumentException("fd must not be null");
} }
@ -63,8 +82,9 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
mFd = fd; mFd = fd;
mStartOffset = startOffset; mStartOffset = startOffset;
mLength = length; mLength = length;
mExtras = extras;
} }
/** /**
* The AssetFileDescriptor contains its own ParcelFileDescriptor, which * The AssetFileDescriptor contains its own ParcelFileDescriptor, which
* in addition to the normal FileDescriptor object also allows you to close * in addition to the normal FileDescriptor object also allows you to close
@ -88,7 +108,15 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
public long getStartOffset() { public long getStartOffset() {
return mStartOffset; return mStartOffset;
} }
/**
* Returns any additional details that can be used to interpret the
* underlying file descriptor. May be null.
*/
public Bundle getExtras() {
return mExtras;
}
/** /**
* Returns the total number of bytes of this asset entry's data. May be * Returns the total number of bytes of this asset entry's data. May be
* {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file. * {@link #UNKNOWN_LENGTH} if the asset extends to the end of the file.
@ -307,25 +335,37 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
super.write(oneByte); super.write(oneByte);
} }
} }
/* Parcelable interface */ /* Parcelable interface */
@Override
public int describeContents() { public int describeContents() {
return mFd.describeContents(); return mFd.describeContents();
} }
@Override
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(Parcel out, int flags) {
mFd.writeToParcel(out, flags); mFd.writeToParcel(out, flags);
out.writeLong(mStartOffset); out.writeLong(mStartOffset);
out.writeLong(mLength); out.writeLong(mLength);
if (mExtras != null) {
out.writeInt(1);
out.writeBundle(mExtras);
} else {
out.writeInt(0);
}
} }
AssetFileDescriptor(Parcel src) { AssetFileDescriptor(Parcel src) {
mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src); mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
mStartOffset = src.readLong(); mStartOffset = src.readLong();
mLength = src.readLong(); mLength = src.readLong();
if (src.readInt() != 0) {
mExtras = src.readBundle();
} else {
mExtras = null;
}
} }
public static final Parcelable.Creator<AssetFileDescriptor> CREATOR public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
= new Parcelable.Creator<AssetFileDescriptor>() { = new Parcelable.Creator<AssetFileDescriptor>() {
public AssetFileDescriptor createFromParcel(Parcel in) { public AssetFileDescriptor createFromParcel(Parcel in) {

View File

@ -28,7 +28,9 @@ import android.content.res.AssetFileDescriptor;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Point; import android.graphics.Point;
import android.media.ExifInterface;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.CancellationSignal; import android.os.CancellationSignal;
@ -42,8 +44,10 @@ import libcore.io.IoUtils;
import libcore.io.Libcore; import libcore.io.Libcore;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -76,6 +80,15 @@ public final class DocumentsContract {
/** {@hide} */ /** {@hide} */
public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME"; public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
/**
* Included in {@link AssetFileDescriptor#getExtras()} when returned
* thumbnail should be rotated.
*
* @see MediaStore.Images.ImageColumns#ORIENTATION
* @hide
*/
public static final String EXTRA_ORIENTATION = "android.content.extra.ORIENTATION";
/** {@hide} */ /** {@hide} */
public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT"; public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
/** {@hide} */ /** {@hide} */
@ -657,6 +670,7 @@ public final class DocumentsContract {
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
AssetFileDescriptor afd = null; AssetFileDescriptor afd = null;
Bitmap bitmap = null;
try { try {
afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
@ -688,21 +702,36 @@ public final class DocumentsContract {
opts.inJustDecodeBounds = false; opts.inJustDecodeBounds = false;
opts.inSampleSize = Math.min(widthSample, heightSample); opts.inSampleSize = Math.min(widthSample, heightSample);
Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
if (is != null) { if (is != null) {
is.reset(); is.reset();
return BitmapFactory.decodeStream(is, null, opts); bitmap = BitmapFactory.decodeStream(is, null, opts);
} else { } else {
try { try {
Libcore.os.lseek(fd, offset, SEEK_SET); Libcore.os.lseek(fd, offset, SEEK_SET);
} catch (ErrnoException e) { } catch (ErrnoException e) {
e.rethrowAsIOException(); e.rethrowAsIOException();
} }
return BitmapFactory.decodeFileDescriptor(fd, null, opts); bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
}
// Transform the bitmap if requested. We use a side-channel to
// communicate the orientation, since EXIF thumbnails don't contain
// the rotation flags of the original image.
final Bundle extras = afd.getExtras();
final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0;
if (orientation != 0) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final Matrix m = new Matrix();
m.setRotate(orientation, width / 2, height / 2);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false);
} }
} finally { } finally {
IoUtils.closeQuietly(afd); IoUtils.closeQuietly(afd);
} }
return bitmap;
} }
/** /**
@ -770,4 +799,44 @@ public final class DocumentsContract {
client.call(METHOD_DELETE_DOCUMENT, null, in); client.call(METHOD_DELETE_DOCUMENT, null, in);
} }
/**
* Open the given image for thumbnail purposes, using any embedded EXIF
* thumbnail if available, and providing orientation hints from the parent
* image.
*
* @hide
*/
public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException {
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
file, ParcelFileDescriptor.MODE_READ_ONLY);
Bundle extras = null;
try {
final ExifInterface exif = new ExifInterface(file.getAbsolutePath());
switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
case ExifInterface.ORIENTATION_ROTATE_90:
extras = new Bundle(1);
extras.putInt(EXTRA_ORIENTATION, 90);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
extras = new Bundle(1);
extras.putInt(EXTRA_ORIENTATION, 180);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
extras = new Bundle(1);
extras.putInt(EXTRA_ORIENTATION, 270);
break;
}
final long[] thumb = exif.getThumbnailRange();
if (thumb != null) {
return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras);
}
} catch (IOException e) {
}
return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras);
}
} }

View File

@ -27,6 +27,7 @@ import android.os.Environment;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Root;
import android.provider.DocumentsContract;
import android.provider.DocumentsProvider; import android.provider.DocumentsProvider;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
@ -313,19 +314,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
String documentId, Point sizeHint, CancellationSignal signal) String documentId, Point sizeHint, CancellationSignal signal)
throws FileNotFoundException { throws FileNotFoundException {
final File file = getFileForDocId(documentId); final File file = getFileForDocId(documentId);
final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( return DocumentsContract.openImageThumbnail(file);
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);
} }
private static String getTypeForFile(File file) { private static String getTypeForFile(File file) {