ExifInterface: add RAW input stream support

And also the following things are included:

- Remove mInputStream.
- Update javadoc accordingly.

Bug: 11224701
Change-Id: I30b4c29ac800ae396fca8f6b2c2c0f68028a44b3
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 1a387be..3fa078f 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import android.annotation.NonNull;
+import android.content.res.AssetManager;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.system.ErrnoException;
@@ -24,6 +26,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -145,6 +148,7 @@
     private static final String TAG_HAS_THUMBNAIL = "hasThumbnail";
     private static final String TAG_THUMBNAIL_OFFSET = "thumbnailOffset";
     private static final String TAG_THUMBNAIL_LENGTH = "thumbnailLength";
+    private static final String TAG_THUMBNAIL_DATA = "thumbnailData";
 
     // Constants used for the Orientation Exif tag.
     public static final int ORIENTATION_UNDEFINED = 0;
@@ -163,6 +167,9 @@
     public static final int WHITEBALANCE_AUTO = 0;
     public static final int WHITEBALANCE_MANUAL = 1;
 
+    private static final byte[] JPEG_SIGNATURE = new byte[] {(byte) 0xff, (byte) 0xd8, (byte) 0xff};
+    private static final int JPEG_SIGNATURE_SIZE = 3;
+
     private static SimpleDateFormat sFormatter;
 
     // See Exchangeable image file format for digital still cameras: Exif version 2.2.
@@ -408,8 +415,8 @@
     // Mappings from tag number to tag name and each item represents one IFD tag group.
     private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length];
     // Mapping from tag name to tag number and the corresponding tag group.
-    private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting
-            = new HashMap<>();
+    private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting =
+            new HashMap<>();
 
     // See JPEG File Interchange Format Version 1.02.
     // The following values are defined for handling JPEG streams. In this implementation, we are
@@ -443,7 +450,7 @@
 
     static {
         System.loadLibrary("media_jni");
-        initRawNative();
+        nativeInitRaw();
         sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
         sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
 
@@ -468,10 +475,10 @@
     }
 
     private final String mFilename;
-    private final FileDescriptor mFileDescriptor;
-    private final InputStream mInputStream;
-    private boolean mIsRaw;
+    private final FileDescriptor mSeekableFileDescriptor;
+    private final AssetManager.AssetInputStream mAssetInputStream;
     private final HashMap<String, String> mAttributes = new HashMap<>();
+    private boolean mIsRaw;
     private boolean mHasThumbnail;
     // The following values used for indicating a thumbnail position.
     private int mThumbnailOffset;
@@ -488,23 +495,33 @@
         if (filename == null) {
             throw new IllegalArgumentException("filename cannot be null");
         }
+        FileInputStream in = new FileInputStream(filename);
+        mAssetInputStream = null;
         mFilename = filename;
-        mFileDescriptor = null;
-        mInputStream = new FileInputStream(filename);
-        loadAttributes();
+        if (isSeekableFD(in.getFD())) {
+            mSeekableFileDescriptor = in.getFD();
+        } else {
+            mSeekableFileDescriptor = null;
+        }
+        loadAttributes(in);
     }
 
     /**
-     * Reads Exif tags from the specified image file descriptor.
+     * Reads Exif tags from the specified image file descriptor. Attribute mutation is supported
+     * for seekable file descriptors only.
      */
     public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
         if (fileDescriptor == null) {
             throw new IllegalArgumentException("parcelFileDescriptor cannot be null");
         }
+        mAssetInputStream = null;
         mFilename = null;
-        mFileDescriptor = fileDescriptor;
-        mInputStream = new FileInputStream(fileDescriptor);
-        loadAttributes();
+        if (isSeekableFD(fileDescriptor)) {
+            mSeekableFileDescriptor = fileDescriptor;
+        } else {
+            mSeekableFileDescriptor = null;
+        }
+        loadAttributes(new FileInputStream(fileDescriptor));
     }
 
     /**
@@ -516,9 +533,18 @@
             throw new IllegalArgumentException("inputStream cannot be null");
         }
         mFilename = null;
-        mFileDescriptor = null;
-        mInputStream = inputStream;
-        loadAttributes();
+        if (inputStream instanceof AssetManager.AssetInputStream) {
+            mAssetInputStream = (AssetManager.AssetInputStream) inputStream;
+            mSeekableFileDescriptor = null;
+        } else if (inputStream instanceof FileInputStream
+                && isSeekableFD(((FileInputStream) inputStream).getFD())) {
+            mAssetInputStream = null;
+            mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD();
+        } else {
+            mAssetInputStream = null;
+            mSeekableFileDescriptor = null;
+        }
+        loadAttributes(inputStream);
     }
 
     /**
@@ -587,77 +613,93 @@
     }
 
     /**
-     * Initialize mAttributes with the attributes from the file mFilename.
-     *
-     * mAttributes is a HashMap which stores the Exif attributes of the file.
-     * The key is the standard tag name and the value is the tag's value: e.g.
-     * Model -&gt; Nikon. Numeric values are stored as strings.
-     *
-     * This function also initialize mHasThumbnail to indicate whether the
-     * file has a thumbnail inside.
+     * This function decides which parser to read the image data according to the given input stream
+     * type and the content of the input stream. In each case, it reads the first three bytes to
+     * determine whether the image data format is JPEG or not.
      */
-    private void loadAttributes() throws IOException {
-        FileInputStream in = null;
-        try {
-            if (mFilename != null) {
-                in = new FileInputStream(mFilename);
+    private void loadAttributes(@NonNull InputStream in) throws IOException {
+        // Process RAW input stream
+        if (mAssetInputStream != null) {
+            long asset = mAssetInputStream.getNativeAsset();
+            if (handleRawResult(nativeGetRawAttributesFromAsset(asset))) {
+                return;
             }
-            if (mFileDescriptor != null) {
-                in = new FileInputStream(mFileDescriptor);
+        } else if (mSeekableFileDescriptor != null) {
+            if (handleRawResult(nativeGetRawAttributesFromFileDescriptor(
+                    mSeekableFileDescriptor))) {
+                return;
             }
-            if (in != null) {
-                // First test whether a given file is a one of RAW format or not.
-                HashMap map = getRawAttributesNative(Os.dup(in.getFD()));
-                mIsRaw = map != null;
-                if (mIsRaw) {
-                    for (Object obj : map.entrySet()) {
-                        Map.Entry entry = (Map.Entry) obj;
-                        String attrName = (String) entry.getKey();
-                        String attrValue = (String) entry.getValue();
-
-                        switch (attrName) {
-                            case TAG_HAS_THUMBNAIL:
-                                mHasThumbnail = attrValue.equalsIgnoreCase("true");
-                                break;
-                            case TAG_THUMBNAIL_OFFSET:
-                                mThumbnailOffset = Integer.parseInt(attrValue);
-                                break;
-                            case TAG_THUMBNAIL_LENGTH:
-                                mThumbnailLength = Integer.parseInt(attrValue);
-                                break;
-                            default:
-                                mAttributes.put(attrName, attrValue);
-                                break;
-                        }
-                    }
-
-                    if (DEBUG) {
-                        printAttributes();
-                    }
-                    return;
-                }
+        } else {
+            in = new BufferedInputStream(in, JPEG_SIGNATURE_SIZE);
+            if (!isJpegInputStream((BufferedInputStream) in) && handleRawResult(
+                    nativeGetRawAttributesFromInputStream(in))) {
+                return;
             }
-        } catch (ErrnoException e) {
-            e.rethrowAsIOException();
-        } finally {
-            IoUtils.closeQuietly(in);
         }
 
-        try {
-            if (mFileDescriptor != null) {
-                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
-            }
+        // Process JPEG input stream
+        getJpegAttributes(in);
 
-            getJpegAttributes(mInputStream);
-        } catch (ErrnoException e) {
-            e.rethrowAsIOException();
-        } finally {
-            IoUtils.closeQuietly(mInputStream);
+        if (DEBUG) {
+            printAttributes();
+        }
+    }
+
+    private static boolean isJpegInputStream(BufferedInputStream in) throws IOException {
+        in.mark(JPEG_SIGNATURE_SIZE);
+        byte[] signatureBytes = new byte[JPEG_SIGNATURE_SIZE];
+        if (in.read(signatureBytes) != JPEG_SIGNATURE_SIZE) {
+            throw new EOFException();
+        }
+        boolean isJpeg = Arrays.equals(JPEG_SIGNATURE, signatureBytes);
+        in.reset();
+        return isJpeg;
+    }
+
+    private boolean handleRawResult(HashMap map) {
+        if (map == null) {
+            return false;
+        }
+
+        // Mark for disabling the save feature.
+        mIsRaw = true;
+
+        for (Object obj : map.entrySet()) {
+            Map.Entry entry = (Map.Entry) obj;
+            String attrName = (String) entry.getKey();
+
+            switch (attrName) {
+                case TAG_HAS_THUMBNAIL:
+                    mHasThumbnail = ((String) entry.getValue()).equalsIgnoreCase("true");
+                    break;
+                case TAG_THUMBNAIL_OFFSET:
+                    mThumbnailOffset = Integer.parseInt((String) entry.getValue());
+                    break;
+                case TAG_THUMBNAIL_LENGTH:
+                    mThumbnailLength = Integer.parseInt((String) entry.getValue());
+                    break;
+                case TAG_THUMBNAIL_DATA:
+                    mThumbnailBytes = (byte[]) entry.getValue();
+                    break;
+                default:
+                    mAttributes.put(attrName, (String) entry.getValue());
+                    break;
+            }
         }
 
         if (DEBUG) {
             printAttributes();
         }
+        return true;
+    }
+
+    private static boolean isSeekableFD(FileDescriptor fd) throws IOException {
+        try {
+            Os.lseek(fd, 0, OsConstants.SEEK_CUR);
+            return true;
+        } catch (ErrnoException e) {
+            return false;
+        }
     }
 
     // Prints out attributes for debugging.
@@ -679,9 +721,9 @@
             throw new UnsupportedOperationException(
                     "ExifInterface does not support saving attributes on RAW formats.");
         }
-        if (mFileDescriptor == null && mFilename == null) {
+        if (mSeekableFileDescriptor == null && mFilename == null) {
             throw new UnsupportedOperationException(
-                    "ExifInterface does not support saving attributes for input streams.");
+                    "ExifInterface does not support saving attributes for the current input.");
         }
 
         // Keep the thumbnail in memory
@@ -698,11 +740,10 @@
                 if (!originalFile.renameTo(tempFile)) {
                     throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath());
                 }
-            }
-            if (mFileDescriptor != null) {
+            } else if (mSeekableFileDescriptor != null) {
                 tempFile = File.createTempFile("temp", "jpg");
-                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
-                in = new FileInputStream(mFileDescriptor);
+                Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
+                in = new FileInputStream(mSeekableFileDescriptor);
                 out = new FileOutputStream(tempFile);
                 Streams.copy(in, out);
             }
@@ -720,10 +761,9 @@
             in = new FileInputStream(tempFile);
             if (mFilename != null) {
                 out = new FileOutputStream(mFilename);
-            }
-            if (mFileDescriptor != null) {
-                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
-                out = new FileOutputStream(mFileDescriptor);
+            } else if (mSeekableFileDescriptor != null) {
+                Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
+                out = new FileOutputStream(mSeekableFileDescriptor);
             }
             saveJpegAttributes(in, out);
         } catch (ErrnoException e) {
@@ -760,13 +800,15 @@
 
         // Read the thumbnail.
         FileInputStream in = null;
-        try  {
-            if (mFileDescriptor != null) {
-                Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
-                in = new FileInputStream(mFileDescriptor);
-            }
-            if (mFilename != null) {
+        try {
+            if (mAssetInputStream != null) {
+                return nativeGetThumbnailFromAsset(
+                        mAssetInputStream.getNativeAsset(), mThumbnailOffset, mThumbnailLength);
+            } else if (mFilename != null) {
                 in = new FileInputStream(mFilename);
+            } else if (mSeekableFileDescriptor != null) {
+                Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
+                in = new FileInputStream(mSeekableFileDescriptor);
             }
             if (in == null) {
                 // Should not be reached this.
@@ -1180,8 +1222,9 @@
                 mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat;
                 mThumbnailLength = jpegInterchangeFormatLength;
 
-                // Do not store a thumbnail in memory if the given input can be re-read.
-                if (mFileDescriptor == null && mFilename == null) {
+                if (mFilename == null && mAssetInputStream == null
+                        && mSeekableFileDescriptor == null) {
+                    // Save the thumbnail in memory if the input doesn't support reading again.
                     byte[] thumbnailBytes = new byte[jpegInterchangeFormatLength];
                     dataInputStream.seek(jpegInterchangeFormat);
                     dataInputStream.readFully(thumbnailBytes);
@@ -1988,6 +2031,10 @@
     }
 
     // JNI methods for RAW formats.
-    private static native void initRawNative();
-    private static native HashMap getRawAttributesNative(FileDescriptor fileDescriptor);
+    private static native void nativeInitRaw();
+    private static native byte[] nativeGetThumbnailFromAsset(
+            long asset, int thumbnailOffset, int thumbnailLength);
+    private static native HashMap nativeGetRawAttributesFromAsset(long asset);
+    private static native HashMap nativeGetRawAttributesFromFileDescriptor(FileDescriptor fd);
+    private static native HashMap nativeGetRawAttributesFromInputStream(InputStream in);
 }
diff --git a/media/jni/android_media_ExifInterface.cpp b/media/jni/android_media_ExifInterface.cpp
index f7481af..418a3f2 100644
--- a/media/jni/android_media_ExifInterface.cpp
+++ b/media/jni/android_media_ExifInterface.cpp
@@ -19,12 +19,15 @@
 
 #include "android_media_Utils.h"
 
+#include "android/graphics/CreateJavaOutputStreamAdaptor.h"
 #include "src/piex_types.h"
 #include "src/piex.h"
 
 #include <jni.h>
 #include <JNIHelp.h>
+#include <androidfw/Asset.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <android/graphics/Utils.h>
 #include <nativehelper/ScopedLocalRef.h>
 
 #include <utils/Log.h>
@@ -35,6 +38,9 @@
 
 using namespace android;
 
+static const char kJpegSignatureChars[] = {(char)0xff, (char)0xd8, (char)0xff};
+static const int kJpegSignatureSize = 3;
+
 #define FIND_CLASS(var, className) \
     var = env->FindClass(className); \
     LOG_FATAL_IF(! var, "Unable to find class " className);
@@ -82,18 +88,48 @@
                   "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
 }
 
-static jobject ExifInterface_getRawMetadata(
-        JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
-    int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
-    if (fd < 0) {
-        ALOGI("Invalid file descriptor");
+static bool is_asset_stream(const SkStream& stream) {
+    return stream.hasLength() && stream.hasPosition();
+}
+
+static jobject ExifInterface_getThumbnailFromAsset(
+        JNIEnv* env, jclass /* clazz */, jlong jasset, jint jthumbnailOffset,
+        jint jthumbnailLength) {
+    Asset* asset = reinterpret_cast<Asset*>(jasset);
+    std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
+
+    std::unique_ptr<jbyte[]> thumbnailData(new jbyte[(int)jthumbnailLength]);
+    if (thumbnailData.get() == NULL) {
+        ALOGI("No memory to get thumbnail");
         return NULL;
     }
 
-    piex::PreviewImageData image_data;
-    std::unique_ptr<FileStream> stream(new FileStream(fd));
+    // Do not know the current offset. So rewind it.
+    stream->rewind();
 
-    if (!GetExifFromRawImage(stream.get(), String8("[file descriptor]"), image_data)) {
+    // Read thumbnail.
+    stream->skip((int)jthumbnailOffset);
+    stream->read((void*)thumbnailData.get(), (int)jthumbnailLength);
+
+    // Copy to the byte array.
+    jbyteArray byteArray = env->NewByteArray(jthumbnailLength);
+    env->SetByteArrayRegion(byteArray, 0, jthumbnailLength, thumbnailData.get());
+    return byteArray;
+}
+
+static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbnail) {
+    std::unique_ptr<SkStream> streamDeleter(stream);
+
+    std::unique_ptr<::piex::StreamInterface> piexStream;
+    if (is_asset_stream(*stream)) {
+        piexStream.reset(new AssetStream(streamDeleter.release()));
+    } else {
+        piexStream.reset(new BufferedStream(streamDeleter.release()));
+    }
+
+    piex::PreviewImageData image_data;
+
+    if (!GetExifFromRawImage(piexStream.get(), String8("[piex stream]"), image_data)) {
         ALOGI("Raw image not detected");
         return NULL;
     }
@@ -253,7 +289,117 @@
         }
     }
 
-    return KeyedVectorToHashMap(env, map);
+    jobject hashMap = KeyedVectorToHashMap(env, map);
+
+    if (returnThumbnail) {
+        std::unique_ptr<jbyte[]> thumbnailData(new jbyte[image_data.thumbnail.length]);
+        if (thumbnailData.get() == NULL) {
+            ALOGE("No memory to parse a thumbnail");
+            return NULL;
+        }
+        jbyteArray jthumbnailByteArray = env->NewByteArray(image_data.thumbnail.length);
+        if (jthumbnailByteArray == NULL) {
+            ALOGE("No memory to parse a thumbnail");
+            return NULL;
+        }
+        piexStream.get()->GetData(image_data.thumbnail.offset, image_data.thumbnail.length,
+                (uint8_t*)thumbnailData.get());
+        env->SetByteArrayRegion(
+                jthumbnailByteArray, 0, image_data.thumbnail.length, thumbnailData.get());
+        jstring jkey = env->NewStringUTF(String8("thumbnailData"));
+        env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jthumbnailByteArray);
+        env->DeleteLocalRef(jkey);
+        env->DeleteLocalRef(jthumbnailByteArray);
+    }
+    return hashMap;
+}
+
+static jobject ExifInterface_getRawAttributesFromAsset(
+        JNIEnv* env, jclass /* clazz */, jlong jasset) {
+    std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
+    if (jpegSignature.get() == NULL) {
+        ALOGE("No enough memory to parse");
+        return NULL;
+    }
+
+    Asset* asset = reinterpret_cast<Asset*>(jasset);
+    std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
+
+    if (stream.get()->read(jpegSignature.get(), kJpegSignatureSize) != kJpegSignatureSize) {
+        // Rewind the stream.
+        stream.get()->rewind();
+
+        ALOGI("Corrupted image.");
+        return NULL;
+    }
+
+    // Rewind the stream.
+    stream.get()->rewind();
+
+    if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
+        ALOGI("Should be a JPEG stream.");
+        return NULL;
+    }
+
+    // Try to parse from the given stream.
+    jobject result = getRawAttributes(env, stream.get(), false);
+
+    // Rewind the stream for the chance to read JPEG.
+    if (result == NULL) {
+        stream.get()->rewind();
+    }
+    return result;
+}
+
+static jobject ExifInterface_getRawAttributesFromFileDescriptor(
+        JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
+    std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
+    if (jpegSignature.get() == NULL) {
+        ALOGE("No enough memory to parse");
+        return NULL;
+    }
+
+    int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
+    if (fd < 0) {
+        ALOGI("Invalid file descriptor");
+        return NULL;
+    }
+
+    // Restore the file descriptor's offset on exiting this function.
+    AutoFDSeek autoRestore(fd);
+
+    int dupFd = dup(fd);
+
+    FILE* file = fdopen(dupFd, "r");
+    if (file == NULL) {
+        ALOGI("Failed to open the file descriptor");
+        return NULL;
+    }
+
+    if (fgets(jpegSignature.get(), kJpegSignatureSize, file) == NULL) {
+        ALOGI("Corrupted image.");
+        return NULL;
+    }
+
+    if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
+        ALOGI("Should be a JPEG stream.");
+        return NULL;
+    }
+
+    // Rewind the file descriptor.
+    fseek(file, 0L, SEEK_SET);
+
+    std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file,
+                SkFILEStream::kCallerPasses_Ownership));
+    return getRawAttributes(env, fileStream.get(), false);
+}
+
+static jobject ExifInterface_getRawAttributesFromInputStream(
+        JNIEnv* env, jclass /* clazz */, jobject jinputStream) {
+    jbyteArray byteArray = env->NewByteArray(8*1024);
+    ScopedLocalRef<jbyteArray> scoper(env, byteArray);
+    std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, jinputStream, scoper.get()));
+    return getRawAttributes(env, stream.get(), true);
 }
 
 } // extern "C"
@@ -261,9 +407,14 @@
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
-    { "initRawNative", "()V", (void *)ExifInterface_initRaw },
-    { "getRawAttributesNative", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
-      (void*)ExifInterface_getRawMetadata },
+    { "nativeInitRaw", "()V", (void *)ExifInterface_initRaw },
+    { "nativeGetThumbnailFromAsset", "(JII)[B", (void *)ExifInterface_getThumbnailFromAsset },
+    { "nativeGetRawAttributesFromAsset", "(J)Ljava/util/HashMap;",
+      (void*)ExifInterface_getRawAttributesFromAsset },
+    { "nativeGetRawAttributesFromFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
+      (void*)ExifInterface_getRawAttributesFromFileDescriptor },
+    { "nativeGetRawAttributesFromInputStream", "(Ljava/io/InputStream;)Ljava/util/HashMap;",
+      (void*)ExifInterface_getRawAttributesFromInputStream },
 };
 
 int register_android_media_ExifInterface(JNIEnv *env) {
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index f429661..527e6c2 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -30,30 +30,78 @@
 
 namespace android {
 
+AssetStream::AssetStream(SkStream* stream)
+    : mStream(stream) {
+}
+
+AssetStream::~AssetStream() {
+}
+
+piex::Error AssetStream::GetData(
+        const size_t offset, const size_t length, std::uint8_t* data) {
+    // Seek first.
+    if (mPosition != offset) {
+        if (!mStream->seek(offset)) {
+            return piex::Error::kFail;
+        }
+    }
+
+    // Read bytes.
+    size_t size = mStream->read((void*)data, length);
+    mPosition += size;
+
+    return size == length ? piex::Error::kOk : piex::Error::kFail;
+}
+
+BufferedStream::BufferedStream(SkStream* stream)
+    : mStream(stream) {
+}
+
+BufferedStream::~BufferedStream() {
+}
+
+piex::Error BufferedStream::GetData(
+        const size_t offset, const size_t length, std::uint8_t* data) {
+    // Seek first.
+    if (offset + length > mStreamBuffer.bytesWritten()) {
+        size_t sizeToRead = offset + length - mStreamBuffer.bytesWritten();
+        if (sizeToRead <= kMinSizeToRead) {
+            sizeToRead = kMinSizeToRead;
+        }
+        void* tempBuffer = malloc(sizeToRead);
+        if (tempBuffer != NULL) {
+            size_t bytesRead = mStream->read(tempBuffer, sizeToRead);
+            if (bytesRead != sizeToRead) {
+                free(tempBuffer);
+                return piex::Error::kFail;
+            }
+            mStreamBuffer.write(tempBuffer, bytesRead);
+            free(tempBuffer);
+        }
+    }
+
+    // Read bytes.
+    if (mStreamBuffer.read((void*)data, offset, length)) {
+        return piex::Error::kOk;
+    } else {
+        return piex::Error::kFail;
+    }
+}
+
 FileStream::FileStream(const int fd)
-    : mPosition(0),
-      mSize(0) {
+    : mPosition(0) {
     mFile = fdopen(fd, "r");
     if (mFile == NULL) {
         return;
     }
-    // Get the size.
-    fseek(mFile, 0l, SEEK_END);
-    mSize = ftell(mFile);
-    fseek(mFile, 0l, SEEK_SET);
 }
 
 FileStream::FileStream(const String8 filename)
-    : mPosition(0),
-      mSize(0) {
+    : mPosition(0) {
     mFile = fopen(filename.string(), "r");
     if (mFile == NULL) {
         return;
     }
-    // Get the size.
-    fseek(mFile, 0l, SEEK_END);
-    mSize = ftell(mFile);
-    fseek(mFile, 0l, SEEK_SET);
 }
 
 FileStream::~FileStream() {
@@ -79,7 +127,7 @@
     mPosition += size;
 
     // Handle errors.
-    if (ferror(mFile) || (size == 0 && feof(mFile))) {
+    if (ferror(mFile)) {
         ALOGV("GetData read failed: (offset: %zu, length: %zu)", offset, length);
         return piex::Error::kFail;
     }
@@ -90,21 +138,12 @@
     return mFile != NULL;
 }
 
-size_t FileStream::size() const {
-    return mSize;
-}
-
 bool GetExifFromRawImage(
-        FileStream* stream, const String8& filename, piex::PreviewImageData& image_data) {
+        piex::StreamInterface* stream, const String8& filename,
+        piex::PreviewImageData& image_data) {
     // Reset the PreviewImageData to its default.
     image_data = piex::PreviewImageData();
 
-    if (!stream->exists()) {
-        // File is not exists.
-        ALOGV("File is not exists: %s", filename.string());
-        return false;
-    }
-
     if (!piex::IsRaw(stream)) {
         // Format not supported.
         ALOGV("Format not supported: %s", filename.string());
@@ -119,12 +158,6 @@
         return false;
     }
 
-    if (image_data.thumbnail_offset + image_data.thumbnail_length > stream->size()) {
-        // Corrupted image.
-        ALOGV("Corrupted file: %s", filename.string());
-        return false;
-    }
-
     return true;
 }
 
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 3791ec9..8184f94 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -21,20 +21,62 @@
 #include "src/piex.h"
 
 #include <android_runtime/AndroidRuntime.h>
+#include <camera3.h>
+#include <gui/CpuConsumer.h>
 #include <jni.h>
 #include <JNIHelp.h>
 #include <utils/KeyedVector.h>
 #include <utils/String8.h>
-#include <gui/CpuConsumer.h>
-#include <camera3.h>
+#include <SkStream.h>
 
 namespace android {
 
+class AssetStream : public piex::StreamInterface {
+private:
+    SkStream *mStream;
+    size_t mPosition;
+
+public:
+    AssetStream(SkStream* stream);
+    ~AssetStream();
+
+    // Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
+    // provided by the caller, guaranteed to be at least "length" bytes long.
+    // On 'kOk' the 'data' pointer contains 'length' valid bytes beginning at
+    // 'offset' bytes from the start of the stream.
+    // Returns 'kFail' if 'offset' + 'length' exceeds the stream and does not
+    // change the contents of 'data'.
+    piex::Error GetData(
+            const size_t offset, const size_t length, std::uint8_t* data) override;
+};
+
+class BufferedStream : public piex::StreamInterface {
+private:
+    SkStream *mStream;
+    // Growable memory stream
+    SkDynamicMemoryWStream mStreamBuffer;
+
+    // Minimum size to read on filling the buffer.
+    const size_t kMinSizeToRead = 8192;
+
+public:
+    BufferedStream(SkStream* stream);
+    ~BufferedStream();
+
+    // Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
+    // provided by the caller, guaranteed to be at least "length" bytes long.
+    // On 'kOk' the 'data' pointer contains 'length' valid bytes beginning at
+    // 'offset' bytes from the start of the stream.
+    // Returns 'kFail' if 'offset' + 'length' exceeds the stream and does not
+    // change the contents of 'data'.
+    piex::Error GetData(
+            const size_t offset, const size_t length, std::uint8_t* data) override;
+};
+
 class FileStream : public piex::StreamInterface {
 private:
     FILE *mFile;
     size_t mPosition;
-    size_t mSize;
 
 public:
     FileStream(const int fd);
@@ -50,13 +92,12 @@
     piex::Error GetData(
             const size_t offset, const size_t length, std::uint8_t* data) override;
     bool exists() const;
-    size_t size() const;
 };
 
 // Reads EXIF metadata from a given raw image via piex.
 // And returns true if the operation is successful; otherwise, false.
 bool GetExifFromRawImage(
-        FileStream* stream, const String8& filename, piex::PreviewImageData& image_data);
+        piex::StreamInterface* stream, const String8& filename, piex::PreviewImageData& image_data);
 
 // Returns true if the conversion is successful; otherwise, false.
 bool ConvertKeyValueArraysToKeyedVector(
diff --git a/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_ii.jpg b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_ii.jpg
new file mode 100644
index 0000000..477cd3a
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_ii.jpg
Binary files differ
diff --git a/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_mm.jpg b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_mm.jpg
new file mode 100644
index 0000000..78ac703
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/assets/image_exif_byte_order_mm.jpg
Binary files differ
diff --git a/media/tests/MediaFrameworkTest/assets/lg_g4_iso_800.dng b/media/tests/MediaFrameworkTest/assets/lg_g4_iso_800.dng
new file mode 100644
index 0000000..5fcc720
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/assets/lg_g4_iso_800.dng
Binary files differ
diff --git a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
index 8fc6adc..eb13ff37 100644
--- a/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
+++ b/media/tests/MediaFrameworkTest/res/values/exifinterface.xml
@@ -76,9 +76,9 @@
         <item>0</item>
     </array>
     <array name="lg_g4_iso_800_dng">
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
+        <item>true</item>
+        <item>256</item>
+        <item>144</item>
         <item>true</item>
         <item>53.834507</item>
         <item>10.69585</item>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
index 1c80746..cff18cf 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java
@@ -23,20 +23,20 @@
 import android.graphics.BitmapFactory;
 import android.media.ExifInterface;
 import android.os.Environment;
-import android.os.ParcelFileDescriptor;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
 
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
-import java.lang.reflect.Type;
 
 import libcore.io.IoUtils;
 import libcore.io.Streams;
@@ -46,11 +46,10 @@
     private static final boolean VERBOSE = false;  // lots of logging
 
     private static final double DIFFERENCE_TOLERANCE = .005;
-    private static final int BUFFER_SIZE = 32768;
 
     // List of files.
-    private static final String EXIF_BYTE_ORDER_II_JPEG = "ExifByteOrderII.jpg";
-    private static final String EXIF_BYTE_ORDER_MM_JPEG = "ExifByteOrderMM.jpg";
+    private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
+    private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
     private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
     private static final int[] IMAGE_RESOURCES = new int[] {
             R.raw.image_exif_byte_order_ii,  R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800 };
@@ -165,8 +164,6 @@
 
     @Override
     protected void setUp() throws Exception {
-        byte[] buffer = new byte[BUFFER_SIZE];
-
         for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
             String outputPath = new File(Environment.getExternalStorageDirectory(),
                     IMAGE_FILENAMES[i]).getAbsolutePath();
@@ -314,26 +311,30 @@
                 expectedValue.whiteBalance);
     }
 
-    private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
+    private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
             throws IOException {
-        ExpectedValue expectedValue = new ExpectedValue(
-                getContext().getResources().obtainTypedArray(typedArrayResourceId));
-        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
-
         // Created via path.
         ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         if (VERBOSE) {
-            printExifTagsAndValues(fileName, exifInterface);
+            printExifTagsAndValues(imageFile.getName(), exifInterface);
+        }
+        compareWithExpectedValue(exifInterface, expectedValue);
+
+        // Created from an asset file.
+        InputStream in = mContext.getAssets().open(imageFile.getName());
+        exifInterface = new ExifInterface(in);
+        if (VERBOSE) {
+            printExifTagsAndValues(imageFile.getName(), exifInterface);
         }
         compareWithExpectedValue(exifInterface, expectedValue);
 
         // Created via InputStream.
-        FileInputStream in = null;
+        in = null;
         try {
-            in = new FileInputStream(imageFile.getAbsolutePath());
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
             exifInterface = new ExifInterface(in);
             if (VERBOSE) {
-                printExifTagsAndValues(fileName, exifInterface);
+                printExifTagsAndValues(imageFile.getName(), exifInterface);
             }
             compareWithExpectedValue(exifInterface, expectedValue);
         } finally {
@@ -345,18 +346,30 @@
             FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
             exifInterface = new ExifInterface(fd);
             if (VERBOSE) {
-                printExifTagsAndValues(fileName, exifInterface);
+                printExifTagsAndValues(imageFile.getName(), exifInterface);
             }
             compareWithExpectedValue(exifInterface, expectedValue);
         } catch (ErrnoException e) {
             e.rethrowAsIOException();
         }
+    }
+
+    private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
+            throws IOException {
+        ExpectedValue expectedValue = new ExpectedValue(
+                getContext().getResources().obtainTypedArray(typedArrayResourceId));
+        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
+
+        // Test for reading from various inputs.
+        testExifInterfaceCommon(imageFile, expectedValue);
 
         // Test for saving attributes.
+        ExifInterface exifInterface;
         try {
             FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
             exifInterface = new ExifInterface(fd);
             exifInterface.saveAttributes();
+            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
             exifInterface = new ExifInterface(fd);
             if (VERBOSE) {
                 printExifTagsAndValues(fileName, exifInterface);
@@ -383,25 +396,11 @@
                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
         File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
 
-        // Created via path.
-        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        if (VERBOSE) {
-            printExifTagsAndValues(fileName, exifInterface);
-        }
-        compareWithExpectedValue(exifInterface, expectedValue);
+        // Test for reading from various inputs.
+        testExifInterfaceCommon(imageFile, expectedValue);
 
-        // Created via FileDescriptor.
-        FileInputStream in = null;
-        try {
-            in = new FileInputStream(imageFile);
-            exifInterface = new ExifInterface(in.getFD());
-            if (VERBOSE) {
-                printExifTagsAndValues(fileName, exifInterface);
-            }
-            compareWithExpectedValue(exifInterface, expectedValue);
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
+        // Since ExifInterface does not support for saving attributes for RAW files, do not test
+        // about writing back in here.
     }
 
     public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
@@ -415,4 +414,14 @@
     public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
         testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
     }
+
+    public void testCorruptedImage() {
+        byte[] bytes = new byte[1024];
+        try {
+            new ExifInterface(new ByteArrayInputStream(bytes));
+            fail("Should not reach here!");
+        } catch (IOException e) {
+            // Success
+        }
+    }
 }