Merge "Replace stream wrap-function w/ more specific ones" into klp-dev
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index fd9fbae..eea9ee1 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -5,6 +5,7 @@
 #include "GraphicsJNI.h"

 #include "SkDither.h"

 #include "SkUnPreMultiply.h"

+#include "SkStream.h"

 

 #include <binder/Parcel.h>

 #include "android_os_Parcel.h"

diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index c433874..16beb02 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -202,7 +202,7 @@
 // since we "may" create a purgeable imageref, we require the stream be ref'able
 // i.e. dynamically allocated, since its lifetime may exceed the current stack
 // frame.
-static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
+static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
         jobject options, bool allowPurgeable, bool forcePurgeable = false) {
 
     int sampleSize = 1;
@@ -459,26 +459,17 @@
         jobject padding, jobject options) {
 
     jobject bitmap = NULL;
-    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
+    SkAutoTUnref<SkStreamRewindable> stream(GetRewindableStream(env, is, storage));
 
-    if (stream) {
+    if (stream.get()) {
         // for now we don't allow purgeable with java inputstreams
+        // FIXME: GetRewindableStream may have made a copy, in which case
+        // purgeable should be allowed.
         bitmap = doDecode(env, stream, padding, options, false, false);
-        stream->unref();
     }
     return bitmap;
 }
 
-static ssize_t getFDSize(int fd) {
-    off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
-    if (curr < 0) {
-        return 0;
-    }
-    size_t size = ::lseek(fd, 0, SEEK_END);
-    ::lseek64(fd, curr, SEEK_SET);
-    return size;
-}
-
 static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
         jobject padding, jobject bitmapFactoryOptions) {
 
@@ -512,44 +503,16 @@
     return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
 }
 
-/*  make a deep copy of the asset, and return it as a stream, or NULL if there
-    was an error.
- */
-static SkStream* copyAssetToStream(Asset* asset) {
-    // if we could "ref/reopen" the asset, we may not need to copy it here
-    off64_t size = asset->seek(0, SEEK_SET);
-    if ((off64_t)-1 == size) {
-        SkDebugf("---- copyAsset: asset rewind failed\n");
-        return NULL;
-    }
-
-    size = asset->getLength();
-    if (size <= 0) {
-        SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
-        return NULL;
-    }
-
-    SkStream* stream = new SkMemoryStream(size);
-    void* data = const_cast<void*>(stream->getMemoryBase());
-    off64_t len = asset->read(data, size);
-    if (len != size) {
-        SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
-        delete stream;
-        stream = NULL;
-    }
-    return stream;
-}
-
 static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
         jobject padding, jobject options) {
 
-    SkStream* stream;
+    SkStreamRewindable* stream;
     Asset* asset = reinterpret_cast<Asset*>(native_asset);
     bool forcePurgeable = optionsPurgeable(env, options);
     if (forcePurgeable) {
         // if we could "ref/reopen" the asset, we may not need to copy it here
         // and we could assume optionsShareable, since assets are always RO
-        stream = copyAssetToStream(asset);
+        stream = CopyAssetToStream(asset);
         if (stream == NULL) {
             return NULL;
         }
@@ -559,7 +522,7 @@
         stream = new AssetStreamAdaptor(asset);
     }
     SkAutoUnref aur(stream);
-    return doDecode(env, stream, padding, options, true, forcePurgeable);
+    return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable);
 }
 
 static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
@@ -572,7 +535,7 @@
      */
     bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options);
     AutoJavaByteArray ar(env, byteArray);
-    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
+    SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
     SkAutoUnref aur(stream);
     return doDecode(env, stream, NULL, options, purgeable);
 }
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 8867a11..6646579 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -76,27 +76,6 @@
     int fHeight;
 };
 
-static SkMemoryStream* buildSkMemoryStream(SkStream *stream) {
-    size_t bufferSize = 4096;
-    size_t streamLen = 0;
-    size_t len;
-    char* data = (char*)sk_malloc_throw(bufferSize);
-
-    while ((len = stream->read(data + streamLen,
-                    bufferSize - streamLen)) != 0) {
-        streamLen += len;
-        if (streamLen == bufferSize) {
-            bufferSize *= 2;
-            data = (char*)sk_realloc_throw(data, bufferSize);
-        }
-    }
-    data = (char*)sk_realloc_throw(data, streamLen);
-
-    SkMemoryStream* streamMem = new SkMemoryStream();
-    streamMem->setMemoryOwned(data, streamLen);
-    return streamMem;
-}
-
 static jobject createBitmapRegionDecoder(JNIEnv* env, SkStream* stream) {
     SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
     int width, height;
@@ -161,14 +140,12 @@
                                   jbyteArray storage, // byte[]
                                   jboolean isShareable) {
     jobject brd = NULL;
-    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024);
+    // for now we don't allow shareable with java inputstreams
+    SkStream* stream = CopyJavaInputStream(env, is, storage);
 
     if (stream) {
-        // for now we don't allow shareable with java inputstreams
-        SkMemoryStream* mStream = buildSkMemoryStream(stream);
-        brd = createBitmapRegionDecoder(env, mStream);
-        SkSafeUnref(mStream); // the decoder now holds a reference
-        stream->unref();
+        brd = createBitmapRegionDecoder(env, stream);
+        stream->unref(); // the decoder now holds a reference
     }
     return brd;
 }
@@ -176,14 +153,14 @@
 static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
                                  jint native_asset, // Asset
                                  jboolean isShareable) {
-    SkStream* stream, *assStream;
     Asset* asset = reinterpret_cast<Asset*>(native_asset);
-    assStream = new AssetStreamAdaptor(asset);
-    stream = buildSkMemoryStream(assStream);
-    assStream->unref();
+    SkAutoTUnref<SkMemoryStream> stream(CopyAssetToStream(asset));
+    if (NULL == stream.get()) {
+        return NULL;
+    }
 
-    jobject brd = createBitmapRegionDecoder(env, stream);
-    SkSafeUnref(stream); // the decoder now holds a reference
+    jobject brd = createBitmapRegionDecoder(env, stream.get());
+    // The decoder now holds a reference to stream.
     return brd;
 }
 
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
index aa4cbde..797d155 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -1,28 +1,83 @@
 #include "CreateJavaOutputStreamAdaptor.h"
+#include "JNIHelp.h"
+#include "SkData.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypes.h"
+#include "Utils.h"
+#include <androidfw/Asset.h>
 
 #define RETURN_NULL_IF_NULL(value) \
     do { if (!(value)) { SkASSERT(0); return NULL; } } while (false)
 
+#define RETURN_ZERO_IF_NULL(value) \
+    do { if (!(value)) { SkASSERT(0); return 0; } } while (false)
+
 static jmethodID    gInputStream_resetMethodID;
 static jmethodID    gInputStream_markMethodID;
-static jmethodID    gInputStream_availableMethodID;
+static jmethodID    gInputStream_markSupportedMethodID;
 static jmethodID    gInputStream_readMethodID;
 static jmethodID    gInputStream_skipMethodID;
 
+class RewindableJavaStream;
+
+/**
+ *  Non-rewindable wrapper for a Java InputStream.
+ */
 class JavaInputStreamAdaptor : public SkStream {
 public:
     JavaInputStreamAdaptor(JNIEnv* env, jobject js, jbyteArray ar)
         : fEnv(env), fJavaInputStream(js), fJavaByteArray(ar) {
         SkASSERT(ar);
-        fCapacity   = env->GetArrayLength(ar);
+        fCapacity = env->GetArrayLength(ar);
         SkASSERT(fCapacity > 0);
-        fBytesRead  = 0;
+        fBytesRead = 0;
+        fIsAtEnd = false;
     }
 
-	virtual bool rewind() {
+    virtual size_t read(void* buffer, size_t size) {
+        JNIEnv* env = fEnv;
+        if (NULL == buffer) {
+            if (0 == size) {
+                return 0;
+            } else {
+                /*  InputStream.skip(n) can return <=0 but still not be at EOF
+                    If we see that value, we need to call read(), which will
+                    block if waiting for more data, or return -1 at EOF
+                 */
+                size_t amountSkipped = 0;
+                do {
+                    size_t amount = this->doSkip(size - amountSkipped);
+                    if (0 == amount) {
+                        char tmp;
+                        amount = this->doRead(&tmp, 1);
+                        if (0 == amount) {
+                            // if read returned 0, we're at EOF
+                            fIsAtEnd = true;
+                            break;
+                        }
+                    }
+                    amountSkipped += amount;
+                } while (amountSkipped < size);
+                return amountSkipped;
+            }
+        }
+        return this->doRead(buffer, size);
+    }
+
+    virtual bool isAtEnd() const {
+        return fIsAtEnd;
+    }
+
+private:
+    // Does not override rewind, since a JavaInputStreamAdaptor's interface
+    // does not support rewinding. RewindableJavaStream, which is a friend,
+    // will be able to call this method to rewind.
+    bool doRewind() {
         JNIEnv* env = fEnv;
 
         fBytesRead = 0;
+        fIsAtEnd = false;
 
         env->CallVoidMethod(fJavaInputStream, gInputStream_resetMethodID);
         if (env->ExceptionCheck()) {
@@ -53,6 +108,7 @@
             }
 
             if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications.
+                fIsAtEnd = true;
                 break;  // eof
             }
 
@@ -92,58 +148,19 @@
         return (size_t)skipped;
     }
 
-    size_t doSize() {
-        JNIEnv* env = fEnv;
-        jint avail = env->CallIntMethod(fJavaInputStream,
-                                        gInputStream_availableMethodID);
-        if (env->ExceptionCheck()) {
-            env->ExceptionDescribe();
-            env->ExceptionClear();
-            SkDebugf("------- available threw an exception\n");
-            avail = 0;
-        }
-        return avail;
-    }
-
-	virtual size_t read(void* buffer, size_t size) {
-        JNIEnv* env = fEnv;
-        if (NULL == buffer) {
-            if (0 == size) {
-                return this->doSize();
-            } else {
-                /*  InputStream.skip(n) can return <=0 but still not be at EOF
-                    If we see that value, we need to call read(), which will
-                    block if waiting for more data, or return -1 at EOF
-                 */
-                size_t amountSkipped = 0;
-                do {
-                    size_t amount = this->doSkip(size - amountSkipped);
-                    if (0 == amount) {
-                        char tmp;
-                        amount = this->doRead(&tmp, 1);
-                        if (0 == amount) {
-                            // if read returned 0, we're at EOF
-                            break;
-                        }
-                    }
-                    amountSkipped += amount;
-                } while (amountSkipped < size);
-                return amountSkipped;
-            }
-        }
-        return this->doRead(buffer, size);
-    }
-
-private:
     JNIEnv*     fEnv;
     jobject     fJavaInputStream;   // the caller owns this object
     jbyteArray  fJavaByteArray;     // the caller owns this object
     size_t      fCapacity;
     size_t      fBytesRead;
+    bool        fIsAtEnd;
+
+    // Allows access to doRewind and fBytesRead.
+    friend class RewindableJavaStream;
 };
 
-SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
-                                       jbyteArray storage, int markSize) {
+SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream,
+                              jbyteArray storage) {
     static bool gInited;
 
     if (!gInited) {
@@ -154,8 +171,8 @@
                                                            "reset", "()V");
         gInputStream_markMethodID       = env->GetMethodID(inputStream_Clazz,
                                                            "mark", "(I)V");
-        gInputStream_availableMethodID  = env->GetMethodID(inputStream_Clazz,
-                                                           "available", "()I");
+        gInputStream_markSupportedMethodID = env->GetMethodID(inputStream_Clazz,
+                                                              "markSupported", "()Z");
         gInputStream_readMethodID       = env->GetMethodID(inputStream_Clazz,
                                                            "read", "([BII)I");
         gInputStream_skipMethodID       = env->GetMethodID(inputStream_Clazz,
@@ -163,18 +180,167 @@
 
         RETURN_NULL_IF_NULL(gInputStream_resetMethodID);
         RETURN_NULL_IF_NULL(gInputStream_markMethodID);
-        RETURN_NULL_IF_NULL(gInputStream_availableMethodID);
+        RETURN_NULL_IF_NULL(gInputStream_markSupportedMethodID);
         RETURN_NULL_IF_NULL(gInputStream_readMethodID);
         RETURN_NULL_IF_NULL(gInputStream_skipMethodID);
 
         gInited = true;
     }
 
-    if (markSize) {
-        env->CallVoidMethod(stream, gInputStream_markMethodID, markSize);
+    return new JavaInputStreamAdaptor(env, stream, storage);
+}
+
+static SkMemoryStream* adaptor_to_mem_stream(SkStream* adaptor) {
+    SkASSERT(adaptor != NULL);
+    SkDynamicMemoryWStream wStream;
+    const int bufferSize = 256 * 1024; // 256 KB, same as ViewStateSerializer.
+    uint8_t buffer[bufferSize];
+    do {
+        size_t bytesRead = adaptor->read(buffer, bufferSize);
+        wStream.write(buffer, bytesRead);
+    } while (!adaptor->isAtEnd());
+    SkAutoTUnref<SkData> data(wStream.copyToData());
+    return new SkMemoryStream(data.get());
+}
+
+SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream,
+                                    jbyteArray storage) {
+    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
+    if (NULL == adaptor.get()) {
+        return NULL;
+    }
+    return adaptor_to_mem_stream(adaptor.get());
+}
+
+/**
+ *  Wrapper for a Java InputStream which is rewindable and
+ *  has a length.
+ */
+class RewindableJavaStream : public SkStreamRewindable {
+public:
+    // RewindableJavaStream takes ownership of adaptor.
+    RewindableJavaStream(JavaInputStreamAdaptor* adaptor, size_t length)
+        : fAdaptor(adaptor)
+        , fLength(length) {
+        SkASSERT(fAdaptor != NULL);
     }
 
-    return new JavaInputStreamAdaptor(env, stream, storage);
+    virtual ~RewindableJavaStream() {
+        fAdaptor->unref();
+    }
+
+    virtual bool rewind() {
+        return fAdaptor->doRewind();
+    }
+
+    virtual size_t read(void* buffer, size_t size) {
+        return fAdaptor->read(buffer, size);
+    }
+
+    virtual bool isAtEnd() const {
+        return fAdaptor->isAtEnd();
+    }
+
+    virtual size_t getLength() const {
+        return fLength;
+    }
+
+    virtual bool hasLength() const {
+        return true;
+    }
+
+    virtual SkStreamRewindable* duplicate() const {
+        // Duplicating this stream requires rewinding and
+        // reading, which modify this Stream (and could
+        // fail, leaving this one invalid).
+        SkASSERT(false);
+        return NULL;
+    }
+
+private:
+    JavaInputStreamAdaptor* fAdaptor;
+    const size_t            fLength;
+};
+
+/**
+ *  If jstream is a ByteArrayInputStream, return its remaining length. Otherwise
+ *  return 0.
+ */
+static size_t get_length_from_byte_array_stream(JNIEnv* env, jobject jstream) {
+    static jclass byteArrayInputStream_Clazz;
+    static jfieldID countField;
+    static jfieldID posField;
+
+    byteArrayInputStream_Clazz = env->FindClass("java/io/ByteArrayInputStream");
+    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
+
+    countField = env->GetFieldID(byteArrayInputStream_Clazz, "count", "I");
+    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
+    posField = env->GetFieldID(byteArrayInputStream_Clazz, "pos", "I");
+    RETURN_ZERO_IF_NULL(byteArrayInputStream_Clazz);
+
+    if (env->IsInstanceOf(jstream, byteArrayInputStream_Clazz)) {
+        // Return the remaining length, to keep the same behavior of using the rest of the
+        // stream.
+        return env->GetIntField(jstream, countField) - env->GetIntField(jstream, posField);
+    }
+    return 0;
+}
+
+/**
+ *  If jstream is a class that has a length, return it. Otherwise
+ *  return 0.
+ *  Only checks for a set of subclasses.
+ */
+static size_t get_length_if_supported(JNIEnv* env, jobject jstream) {
+    size_t len = get_length_from_byte_array_stream(env, jstream);
+    if (len > 0) {
+        return len;
+    }
+    return 0;
+}
+
+SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
+                                        jbyteArray storage) {
+    SkAutoTUnref<SkStream> adaptor(WrapJavaInputStream(env, stream, storage));
+    if (NULL == adaptor.get()) {
+        return NULL;
+    }
+
+    const size_t length = get_length_if_supported(env, stream);
+    if (length > 0 && env->CallBooleanMethod(stream, gInputStream_markSupportedMethodID)) {
+        // Set the readLimit for mark to the end of the stream, so it can
+        // be rewound regardless of how much has been read.
+        env->CallVoidMethod(stream, gInputStream_markMethodID, length);
+        // RewindableJavaStream will unref adaptor when it is destroyed.
+        return new RewindableJavaStream(static_cast<JavaInputStreamAdaptor*>(adaptor.detach()),
+                                        length);
+    }
+
+    return adaptor_to_mem_stream(adaptor.get());
+}
+
+android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject jstream) {
+    static jclass assetInputStream_Clazz;
+    static jmethodID getAssetIntMethodID;
+
+    assetInputStream_Clazz = env->FindClass("android/content/res/AssetManager$AssetInputStream");
+    RETURN_NULL_IF_NULL(assetInputStream_Clazz);
+
+    getAssetIntMethodID = env->GetMethodID(assetInputStream_Clazz, "getAssetInt", "()I");
+    RETURN_NULL_IF_NULL(getAssetIntMethodID);
+
+    if (!env->IsInstanceOf(jstream, assetInputStream_Clazz)) {
+        return NULL;
+    }
+
+    jint jasset = env->CallIntMethod(jstream, getAssetIntMethodID);
+    android::Asset* a = reinterpret_cast<android::Asset*>(jasset);
+    if (NULL == a) {
+        jniThrowNullPointerException(env, "NULL native asset");
+        return NULL;
+    }
+    return new android::AssetStreamAdaptor(a);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
index c34c96a..5218dc5 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h
@@ -3,10 +3,70 @@
 
 //#include <android_runtime/AndroidRuntime.h>
 #include "jni.h"
-#include "SkStream.h"
 
-SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream,
-                                       jbyteArray storage, int markSize = 0);
+namespace android {
+    class AssetStreamAdaptor;
+}
+
+class SkMemoryStream;
+class SkStream;
+class SkStreamRewindable;
+class SkWStream;
+
+/**
+ *  Return an adaptor from a Java InputStream to an SkStream.
+ *  @param env JNIEnv object.
+ *  @param stream Pointer to Java InputStream.
+ *  @param storage Java byte array for retrieving data from the
+ *      Java InputStream.
+ *  @return SkStream Simple subclass of SkStream which supports its
+ *      basic methods like reading. Only valid until the calling
+ *      function returns, since the Java InputStream is not managed
+ *      by the SkStream.
+ */
+SkStream* WrapJavaInputStream(JNIEnv* env, jobject stream,
+                              jbyteArray storage);
+
+/**
+ *  Copy a Java InputStream.
+ *  @param env JNIEnv object.
+ *  @param stream Pointer to Java InputStream.
+ *  @param storage Java byte array for retrieving data from the
+ *      Java InputStream.
+ *  @return SkMemoryStream The data in stream will be copied to a new
+ *      SkMemoryStream.
+ *  FIXME: Could return a more generic return type if ViewStateSerializer
+ *  did not require an SkMemoryStream.
+ */
+SkMemoryStream* CopyJavaInputStream(JNIEnv* env, jobject stream,
+                                    jbyteArray storage);
+
+/**
+ *  Get a rewindable stream from a Java InputStream.
+ *  @param env JNIEnv object.
+ *  @param stream Pointer to Java InputStream.
+ *  @param storage Java byte array for retrieving data from the
+ *      Java InputStream.
+ *  @return SkStreamRewindable Either a wrapper around the Java
+ *      InputStream, if possible, or a copy which is rewindable.
+ *      Since it may be a wrapper, must not be used after the
+ *      caller returns, like the result of WrapJavaInputStream.
+ */
+SkStreamRewindable* GetRewindableStream(JNIEnv* env, jobject stream,
+                                        jbyteArray storage);
+
+/**
+ *  If the Java InputStream is an AssetInputStream, return an adaptor.
+ *  This should not be used after the calling function returns, since
+ *  the caller may close the asset. Returns NULL if the stream is
+ *  not an AssetInputStream.
+ *  @param env JNIEnv object.
+ *  @param stream Pointer to Java InputStream.
+ *  @return AssetStreamAdaptor representing the InputStream, or NULL.
+ *      Must not be held onto.
+ */
+android::AssetStreamAdaptor* CheckForAssetStream(JNIEnv* env, jobject stream);
+
 SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream,
                                          jbyteArray storage);
 
diff --git a/core/jni/android/graphics/Movie.cpp b/core/jni/android/graphics/Movie.cpp
index 4f64ff8..2eae841 100644
--- a/core/jni/android/graphics/Movie.cpp
+++ b/core/jni/android/graphics/Movie.cpp
@@ -1,8 +1,10 @@
+#include "ScopedLocalRef.h"
 #include "SkMovie.h"
 #include "SkStream.h"
 #include "GraphicsJNI.h"
 #include "SkTemplates.h"
 #include "SkUtils.h"
+#include "Utils.h"
 #include "CreateJavaOutputStreamAdaptor.h"
 
 #include <androidfw/Asset.h>
@@ -83,9 +85,14 @@
 
     NPE_CHECK_RETURN_ZERO(env, istream);
 
-    // what is the lifetime of the array? Can the skstream hold onto it?
-    jbyteArray byteArray = env->NewByteArray(16*1024);
-    SkStream* strm = CreateJavaInputStreamAdaptor(env, istream, byteArray);
+    SkStreamRewindable* strm = CheckForAssetStream(env, istream);
+    jbyteArray byteArray = NULL;
+    ScopedLocalRef<jbyteArray> scoper(env, NULL);
+    if (NULL == strm) {
+        byteArray = env->NewByteArray(16*1024);
+        scoper.reset(byteArray);
+        strm = GetRewindableStream(env, istream, byteArray);
+    }
     if (NULL == strm) {
         return 0;
     }
diff --git a/core/jni/android/graphics/Picture.cpp b/core/jni/android/graphics/Picture.cpp
index 9c02219..dff2b18 100644
--- a/core/jni/android/graphics/Picture.cpp
+++ b/core/jni/android/graphics/Picture.cpp
@@ -20,6 +20,7 @@
 
 #include "SkCanvas.h"
 #include "SkPicture.h"
+#include "SkStream.h"
 #include "SkTemplates.h"
 #include "CreateJavaOutputStreamAdaptor.h"
 
@@ -38,10 +39,9 @@
     static SkPicture* deserialize(JNIEnv* env, jobject, jobject jstream,
                                   jbyteArray jstorage) {
         SkPicture* picture = NULL;
-        SkStream* strm = CreateJavaInputStreamAdaptor(env, jstream, jstorage);
-        if (strm) {
-            picture = SkPicture::CreateFromStream(strm);
-            delete strm;
+        SkAutoTUnref<SkStream> strm(WrapJavaInputStream(env, jstream, jstorage));
+        if (strm.get()) {
+            picture = SkPicture::CreateFromStream(strm.get());
         }
         return picture;
     }
diff --git a/core/jni/android/graphics/Utils.cpp b/core/jni/android/graphics/Utils.cpp
index cf6977e..b7d1f3a 100644
--- a/core/jni/android/graphics/Utils.cpp
+++ b/core/jni/android/graphics/Utils.cpp
@@ -28,12 +28,28 @@
     return true;
 }
 
+size_t AssetStreamAdaptor::getLength() const {
+    return fAsset->getLength();
+}
+
+bool AssetStreamAdaptor::isAtEnd() const {
+    return fAsset->getRemainingLength() == 0;
+}
+
+SkStreamRewindable* AssetStreamAdaptor::duplicate() const {
+    SkASSERT(false);
+    // Cannot create a duplicate, since each AssetStreamAdaptor
+    // would be modifying the Asset.
+    //return new AssetStreamAdaptor(fAsset);
+    return NULL;
+}
+
 size_t AssetStreamAdaptor::read(void* buffer, size_t size) {
     ssize_t amount;
 
     if (NULL == buffer) {
-        if (0 == size) {  // caller is asking us for our total length
-            return fAsset->getLength();
+        if (0 == size) {
+            return 0;
         }
         // asset->seek returns new total offset
         // we want to return amount that was skipped
@@ -62,6 +78,34 @@
     return amount;
 }
 
+SkMemoryStream* android::CopyAssetToStream(Asset* asset) {
+    if (NULL == asset) {
+        return NULL;
+    }
+
+    off64_t size = asset->seek(0, SEEK_SET);
+    if ((off64_t)-1 == size) {
+        SkDebugf("---- copyAsset: asset rewind failed\n");
+        return NULL;
+    }
+
+    size = asset->getLength();
+    if (size <= 0) {
+        SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
+        return NULL;
+    }
+
+    SkMemoryStream* stream = new SkMemoryStream(size);
+    void* data = const_cast<void*>(stream->getMemoryBase());
+    off64_t len = asset->read(data, size);
+    if (len != size) {
+        SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
+        delete stream;
+        stream = NULL;
+    }
+    return stream;
+}
+
 jobject android::nullObjectReturn(const char msg[]) {
     if (msg) {
         SkDebugf("--- %s\n", msg);
diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h
index 75ceaa2..a1ac72a 100644
--- a/core/jni/android/graphics/Utils.h
+++ b/core/jni/android/graphics/Utils.h
@@ -26,16 +26,27 @@
 
 namespace android {
 
-class AssetStreamAdaptor : public SkStream {
+class AssetStreamAdaptor : public SkStreamRewindable {
 public:
     AssetStreamAdaptor(Asset* a) : fAsset(a) {}
     virtual bool rewind();
     virtual size_t read(void* buffer, size_t size);
+    virtual bool hasLength() const { return true; }
+    virtual size_t getLength() const;
+    virtual bool isAtEnd() const;
 
+    virtual SkStreamRewindable* duplicate() const;
 private:
     Asset*  fAsset;
 };
 
+/**
+ *  Make a deep copy of the asset, and return it as a stream, or NULL if there
+ *  was an error.
+ *  FIXME: If we could "ref/reopen" the asset, we may not need to copy it here.
+ */
+
+SkMemoryStream* CopyAssetToStream(Asset*);
 
 /** Restore the file descriptor's offset in our destructor
  */
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 1c426fd..1721bee 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -23,7 +23,6 @@
 import android.util.Log;
 import android.util.TypedValue;
 
-import java.io.BufferedInputStream;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -545,28 +544,28 @@
             return null;
         }
 
-        Bitmap bm;
+        Bitmap bm = null;
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
         try {
-            // we need mark/reset to work properly
-            if (!is.markSupported()) {
-                is = new BufferedInputStream(is, DECODE_BUFFER_SIZE);
-            }
-
-            // so we can call reset() if a given codec gives up after reading up to
-            // this many bytes. FIXME: need to find out from the codecs what this
-            // value should be.
-            is.mark(1024);
-
+            boolean decodeGenericStream = true;
             if (is instanceof AssetManager.AssetInputStream) {
                 final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
                 bm = nativeDecodeAsset(asset, outPadding, opts);
-            } else {
-                // pass some temp storage down to the native code. 1024 is made up,
-                // but should be large enough to avoid too many small calls back
-                // into is.read(...) This number is not related to the value passed
-                // to mark(...) above.
+                // Do not follow the normal case.
+                decodeGenericStream = false;
+            } else if (is instanceof FileInputStream) {
+                try {
+                    FileDescriptor fd = ((FileInputStream) is).getFD();
+                    // decodeFileDescriptor will take care of throwing the IAE and
+                    // calling setDensityFromOptions.
+                    return decodeFileDescriptor(fd, outPadding, opts);
+                } catch (IOException e) {
+                    // Fall through to nativeDecodeStream.
+                }
+            }
+
+            if (decodeGenericStream) {
                 byte [] tempStorage = null;
                 if (opts != null) tempStorage = opts.inTempStorage;
                 if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
@@ -610,26 +609,41 @@
      *                   no bitmap is returned (null) then padding is
      *                   unchanged.
      * @param opts null-ok; Options that control downsampling and whether the
-     *             image should be completely decoded, or just is size returned.
+     *             image should be completely decoded, or just its size returned.
      * @return the decoded bitmap, or null
      */
     public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) {
-        if (nativeIsSeekable(fd)) {
-            Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+        Bitmap bm;
+
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeFileDescriptor");
+        try {
+            if (nativeIsSeekable(fd)) {
+                bm = nativeDecodeFileDescriptor(fd, outPadding, opts);
+            } else {
+                FileInputStream fis = new FileInputStream(fd);
+                // FIXME: If nativeDecodeStream grabbed the pointer to tempStorage
+                // from Options, this code would not need to be duplicated.
+                byte [] tempStorage = null;
+                if (opts != null) tempStorage = opts.inTempStorage;
+                if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE];
+                try {
+                    bm = nativeDecodeStream(fis, tempStorage, outPadding, opts);
+                } finally {
+                    try {
+                        fis.close();
+                    } catch (Throwable t) {/* ignore */}
+                }
+            }
+
             if (bm == null && opts != null && opts.inBitmap != null) {
                 throw new IllegalArgumentException("Problem decoding into existing bitmap");
             }
-            return bm;
-        } else {
-            FileInputStream fis = new FileInputStream(fd);
-            try {
-                return decodeStream(fis, outPadding, opts);
-            } finally {
-                try {
-                    fis.close();
-                } catch (Throwable t) {/* ignore */}
-            }
+
+            setDensityFromOptions(bm, opts);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
         }
+        return bm;
     }
 
     /**
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index b38d107..3524b25 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -17,7 +17,6 @@
 
 import android.content.res.AssetManager;
 
-import java.io.BufferedInputStream;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -108,12 +107,6 @@
      */
     public static BitmapRegionDecoder newInstance(InputStream is,
             boolean isShareable) throws IOException {
-        // we need mark/reset to work properly in JNI
-
-        if (!is.markSupported()) {
-            is = new BufferedInputStream(is, 16 * 1024);
-        }
-
         if (is instanceof AssetManager.AssetInputStream) {
             return nativeNewInstance(
                     ((AssetManager.AssetInputStream) is).getAssetInt(),