Add extras to AFD, send orientation metadata.
AssetFileDescriptor augments a ParcelFileDescriptor with details about how it should be interpreted, so extend it to support a Bundle of extras. Then use these extras to share thumbnail orientation metadata. The raw image data of EXIF thumbnails matches the orientation of the enclosing image, but the thumbnail data doesn't repeat the EXIF flags. This meant that receivers of openDocumentThumbnail() would get an image that needed to be transformed, but without enough context to actually transform it. Instead of transforming and recompressing the image on the fly on the provider side, send a transformation hint that the receiver side can interpret. Bug: 11205688 Change-Id: Ibc5a7ad002377a55e6ffcb5ac5c8829841002e06
This commit is contained in:
@@ -7497,11 +7497,13 @@ package android.content.res {
|
||||
|
||||
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, android.os.Bundle);
|
||||
method public void close() 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 int describeContents();
|
||||
method public long getDeclaredLength();
|
||||
method public android.os.Bundle getExtras();
|
||||
method public java.io.FileDescriptor getFileDescriptor();
|
||||
method public long getLength();
|
||||
method public android.os.ParcelFileDescriptor getParcelFileDescriptor();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.content.res;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
@@ -42,17 +43,35 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
|
||||
private final ParcelFileDescriptor mFd;
|
||||
private final long mStartOffset;
|
||||
private final long mLength;
|
||||
|
||||
private final Bundle mExtras;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
* {@link #UNKNOWN_LENGTH} if it extends to the end of the file.
|
||||
*/
|
||||
public AssetFileDescriptor(ParcelFileDescriptor fd, long startOffset,
|
||||
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) {
|
||||
throw new IllegalArgumentException("fd must not be null");
|
||||
}
|
||||
@@ -63,8 +82,9 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
|
||||
mFd = fd;
|
||||
mStartOffset = startOffset;
|
||||
mLength = length;
|
||||
mExtras = extras;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The AssetFileDescriptor contains its own ParcelFileDescriptor, which
|
||||
* 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() {
|
||||
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
|
||||
* {@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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Parcelable interface */
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return mFd.describeContents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel out, int flags) {
|
||||
mFd.writeToParcel(out, flags);
|
||||
out.writeLong(mStartOffset);
|
||||
out.writeLong(mLength);
|
||||
if (mExtras != null) {
|
||||
out.writeInt(1);
|
||||
out.writeBundle(mExtras);
|
||||
} else {
|
||||
out.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
AssetFileDescriptor(Parcel src) {
|
||||
mFd = ParcelFileDescriptor.CREATOR.createFromParcel(src);
|
||||
mStartOffset = src.readLong();
|
||||
mLength = src.readLong();
|
||||
if (src.readInt() != 0) {
|
||||
mExtras = src.readBundle();
|
||||
} else {
|
||||
mExtras = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static final Parcelable.Creator<AssetFileDescriptor> CREATOR
|
||||
= new Parcelable.Creator<AssetFileDescriptor>() {
|
||||
public AssetFileDescriptor createFromParcel(Parcel in) {
|
||||
|
||||
@@ -28,7 +28,9 @@ import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
@@ -42,8 +44,10 @@ import libcore.io.IoUtils;
|
||||
import libcore.io.Libcore;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@@ -76,6 +80,15 @@ public final class DocumentsContract {
|
||||
/** {@hide} */
|
||||
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} */
|
||||
public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT";
|
||||
/** {@hide} */
|
||||
@@ -657,6 +670,7 @@ public final class DocumentsContract {
|
||||
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
|
||||
|
||||
AssetFileDescriptor afd = null;
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
|
||||
|
||||
@@ -688,21 +702,36 @@ public final class DocumentsContract {
|
||||
|
||||
opts.inJustDecodeBounds = false;
|
||||
opts.inSampleSize = Math.min(widthSample, heightSample);
|
||||
Log.d(TAG, "Decoding with sample size " + opts.inSampleSize);
|
||||
if (is != null) {
|
||||
is.reset();
|
||||
return BitmapFactory.decodeStream(is, null, opts);
|
||||
bitmap = BitmapFactory.decodeStream(is, null, opts);
|
||||
} else {
|
||||
try {
|
||||
Libcore.os.lseek(fd, offset, SEEK_SET);
|
||||
} catch (ErrnoException e) {
|
||||
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 {
|
||||
IoUtils.closeQuietly(afd);
|
||||
}
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -770,4 +799,44 @@ public final class DocumentsContract {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.Environment;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsProvider;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
@@ -313,19 +314,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
String documentId, Point sizeHint, CancellationSignal signal)
|
||||
throws FileNotFoundException {
|
||||
final File file = getFileForDocId(documentId);
|
||||
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);
|
||||
return DocumentsContract.openImageThumbnail(file);
|
||||
}
|
||||
|
||||
private static String getTypeForFile(File file) {
|
||||
|
||||
Reference in New Issue
Block a user