OSDN Git Service

Add extras to AFD, send orientation metadata.
authorJeff Sharkey <jsharkey@android.com>
Mon, 14 Oct 2013 21:57:33 +0000 (14:57 -0700)
committerJeff Sharkey <jsharkey@android.com>
Mon, 14 Oct 2013 22:28:19 +0000 (15:28 -0700)
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

api/current.txt
core/java/android/content/res/AssetFileDescriptor.java
core/java/android/provider/DocumentsContract.java
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java

index 40c7bf5..abb330e 100644 (file)
@@ -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();
index e4cc77f..28edde0 100644 (file)
@@ -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) {
index c5e4f21..9d35847 100644 (file)
@@ -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);
+    }
 }
index 189e985..11ff2d8 100644 (file)
@@ -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) {