Make ImageDecoder return animated Drawables

Bug: 63909536
Bug: 63908092
Test: TODO

If ImageDecoder.decodeDrawable is called with an animated image Source
(currently GIF or WebP), return an object of a new (hidden) Drawable
subclass. The new Drawable animates, and it implements Animatable (TODO:
implement Animatable2) so users have some control over the animation.

In addition to the normal features of Drawable, this new one supports
many of the features of ImageDecoder, including scaling, cropping and
PostProcess, which enables circle masks/rounded corners and other
arbitrary after-effects. It does *not* support decoding directly to a
Hardware Bitmap, since it cycles through frames and reuses the same
bitmap memory. But it could be made to use shared memory (TODO?).

TODO: Use a better number for the native allocation registry
TODO: Use the RenderThread to drive the animation, and remove decoding
on the UI thread.
TODO: Add support for modifying the loop count

Android.bp:
- build new AnimatedImageDrawable.cpp

AndroidRuntime.cpp:
- register new native methods

AnimatedImageDrawable.java
AnimatedImageDrawable.cpp:
- new Drawable that handles animated images

Canvas.h, SkiaCanvas.h/.cpp
- New virtual method and implementation for drawing SkAnimatedImages

RecordingCanvas.h/.cpp
- Stub implementation of drawing SkAnimatedImages

ImageDecoder.h/cpp
- Allow code sharing with AnimatedImageDrawable.cpp
  - postProcess
  - access the ImageDecoder struct

Depends on https://skia-review.googlesource.com/c/skia/+/94660 in Skia.

Change-Id: Ie2ec98d9c52deda4d439c6ef8e5dea2861bb93a3
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 96f3308..53913f1 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -120,6 +120,7 @@
         "android_util_jar_StrictJarFile.cpp",
         "android_graphics_Canvas.cpp",
         "android_graphics_Picture.cpp",
+        "android/graphics/AnimatedImageDrawable.cpp",
         "android/graphics/Bitmap.cpp",
         "android/graphics/BitmapFactory.cpp",
         "android/graphics/ByteBufferStreamAdaptor.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6569b47..ef0e260 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -63,6 +63,7 @@
 extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
 extern int register_android_graphics_Graphics(JNIEnv* env);
 extern int register_android_graphics_ImageDecoder(JNIEnv*);
+extern int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv*);
 extern int register_android_graphics_Interpolator(JNIEnv* env);
 extern int register_android_graphics_MaskFilter(JNIEnv* env);
 extern int register_android_graphics_Movie(JNIEnv* env);
@@ -1397,6 +1398,7 @@
     REG_JNI(register_android_graphics_FontFamily),
     REG_JNI(register_android_graphics_GraphicBuffer),
     REG_JNI(register_android_graphics_ImageDecoder),
+    REG_JNI(register_android_graphics_drawable_AnimatedImageDrawable),
     REG_JNI(register_android_graphics_Interpolator),
     REG_JNI(register_android_graphics_MaskFilter),
     REG_JNI(register_android_graphics_Matrix),
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
new file mode 100644
index 0000000..12feaab
--- /dev/null
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "core_jni_helpers.h"
+
+#include <hwui/Canvas.h>
+#include <SkAndroidCodec.h>
+#include <SkAnimatedImage.h>
+#include <SkColorFilter.h>
+#include <SkPicture.h>
+#include <SkPictureRecorder.h>
+
+using namespace android;
+
+struct AnimatedImageDrawable {
+    sk_sp<SkAnimatedImage> mDrawable;
+    SkPaint                mPaint;
+};
+
+// Note: jpostProcess holds a handle to the ImageDecoder.
+static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
+                                           jlong nativeImageDecoder, jobject jpostProcess,
+                                           jint width, jint height, jobject jsubset) {
+    if (nativeImageDecoder == 0) {
+        doThrowIOE(env, "Cannot create AnimatedImageDrawable from null!");
+        return 0;
+    }
+
+    auto* imageDecoder = reinterpret_cast<ImageDecoder*>(nativeImageDecoder);
+    auto info = imageDecoder->mCodec->getInfo();
+    const SkISize scaledSize = SkISize::Make(width, height);
+    SkIRect subset;
+    if (jsubset) {
+        GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+    } else {
+        subset = SkIRect::MakeWH(width, height);
+    }
+
+    sk_sp<SkPicture> picture;
+    if (jpostProcess) {
+        SkRect bounds = SkRect::MakeWH(subset.width(), subset.height());
+
+        SkPictureRecorder recorder;
+        SkCanvas* skcanvas = recorder.beginRecording(bounds);
+        std::unique_ptr<Canvas> canvas(Canvas::create_canvas(skcanvas));
+        postProcessAndRelease(env, jpostProcess, std::move(canvas), bounds.width(),
+                              bounds.height());
+        if (env->ExceptionCheck()) {
+            return 0;
+        }
+        picture = recorder.finishRecordingAsPicture();
+    }
+
+    std::unique_ptr<AnimatedImageDrawable> drawable(new AnimatedImageDrawable);
+    drawable->mDrawable = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
+                scaledSize, subset, std::move(picture));
+    if (!drawable->mDrawable) {
+        doThrowIOE(env, "Failed to create drawable");
+        return 0;
+    }
+    drawable->mDrawable->start();
+
+    return reinterpret_cast<jlong>(drawable.release());
+}
+
+static void AnimatedImageDrawable_destruct(AnimatedImageDrawable* drawable) {
+    delete drawable;
+}
+
+static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject /*clazz*/) {
+    return static_cast<jlong>(reinterpret_cast<uintptr_t>(&AnimatedImageDrawable_destruct));
+}
+
+static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                         jlong canvasPtr, jlong msecs) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    double timeToNextUpdate = drawable->mDrawable->update(msecs);
+    auto* canvas = reinterpret_cast<Canvas*>(canvasPtr);
+    canvas->drawAnimatedImage(drawable->mDrawable.get(), 0, 0, &drawable->mPaint);
+    return (jlong) timeToNextUpdate;
+}
+
+static void AnimatedImageDrawable_nSetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                            jint alpha) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    drawable->mPaint.setAlpha(alpha);
+}
+
+static jlong AnimatedImageDrawable_nGetAlpha(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    return drawable->mPaint.getAlpha();
+}
+
+static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                                  jlong nativeFilter) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
+    drawable->mPaint.setColorFilter(sk_ref_sp(filter));
+}
+
+static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    return drawable->mDrawable->isRunning();
+}
+
+static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    drawable->mDrawable->start();
+}
+
+static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    drawable->mDrawable->stop();
+}
+
+static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    // FIXME: Report the size of the internal SkBitmap etc.
+    return sizeof(drawable);
+}
+
+static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
+    { "nCreate",             "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate },
+    { "nGetNativeFinalizer", "()J",                                                          (void*) AnimatedImageDrawable_nGetNativeFinalizer },
+    { "nDraw",               "(JJJ)J",                                                       (void*) AnimatedImageDrawable_nDraw },
+    { "nSetAlpha",           "(JI)V",                                                        (void*) AnimatedImageDrawable_nSetAlpha },
+    { "nGetAlpha",           "(J)I",                                                         (void*) AnimatedImageDrawable_nGetAlpha },
+    { "nSetColorFilter",     "(JJ)V",                                                        (void*) AnimatedImageDrawable_nSetColorFilter },
+    { "nIsRunning",          "(J)Z",                                                         (void*) AnimatedImageDrawable_nIsRunning },
+    { "nStart",              "(J)V",                                                         (void*) AnimatedImageDrawable_nStart },
+    { "nStop",               "(J)V",                                                         (void*) AnimatedImageDrawable_nStop },
+    { "nNativeByteSize",     "(J)J",                                                         (void*) AnimatedImageDrawable_nNativeByteSize },
+};
+
+int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
+    return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
+            gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));
+}
+
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 9e68037..ed9d0e9 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -19,12 +19,11 @@
 #include "ByteBufferStreamAdaptor.h"
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "GraphicsJNI.h"
-#include "NinePatchPeeker.h"
+#include "ImageDecoder.h"
 #include "Utils.h"
 #include "core_jni_helpers.h"
 
 #include <hwui/Bitmap.h>
-#include <hwui/Canvas.h>
 
 #include <SkAndroidCodec.h>
 #include <SkEncodedImageFormat.h>
@@ -51,26 +50,6 @@
 static jmethodID gCanvas_constructorMethodID;
 static jmethodID gCanvas_releaseMethodID;
 
-struct ImageDecoder {
-    // These need to stay in sync with ImageDecoder.java's Allocator constants.
-    enum Allocator {
-        kDefault_Allocator      = 0,
-        kSoftware_Allocator     = 1,
-        kSharedMemory_Allocator = 2,
-        kHardware_Allocator     = 3,
-    };
-
-    // These need to stay in sync with PixelFormat.java's Format constants.
-    enum PixelFormat {
-        kUnknown     =  0,
-        kTranslucent = -3,
-        kOpaque      = -1,
-    };
-
-    NinePatchPeeker mPeeker;
-    std::unique_ptr<SkAndroidCodec> mCodec;
-};
-
 static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
     if (!stream.get()) {
         doThrowIOE(env, "Failed to create a stream");
@@ -78,7 +57,7 @@
     }
     std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
     SkCodec::Result result;
-    auto codec = SkCodec::MakeFromStream(std::move(stream), &result, &decoder->mPeeker);
+    auto codec = SkCodec::MakeFromStream(std::move(stream), &result, decoder->mPeeker.get());
     if (!codec) {
         switch (result) {
             case SkCodec::kIncompleteInput:
@@ -90,22 +69,24 @@
                            SkCodec::ResultToString(result));
                 doThrowIOE(env, msg.c_str());
                 break;
-        }
 
+        }
         return nullptr;
     }
 
+    // FIXME: Avoid parsing the whole image?
+    const bool animated = codec->getFrameCount() > 1;
     decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
     if (!decoder->mCodec.get()) {
         doThrowIOE(env, "Could not create AndroidCodec");
         return nullptr;
     }
-
     const auto& info = decoder->mCodec->getInfo();
     const int width = info.width();
     const int height = info.height();
     return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
-                          reinterpret_cast<jlong>(decoder.release()), width, height);
+                          reinterpret_cast<jlong>(decoder.release()), width, height,
+                          animated);
 }
 
 static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
@@ -176,8 +157,8 @@
     return native_create(env, std::move(stream));
 }
 
-static jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder,
-                                  std::unique_ptr<Canvas> canvas, int width, int height) {
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
+                           int width, int height) {
     jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
                                      reinterpret_cast<jlong>(canvas.get()));
     if (!jcanvas) {
@@ -340,23 +321,23 @@
     // Ignore ninepatch when post-processing.
     if (!jpostProcess) {
         // FIXME: Share more code with BitmapFactory.cpp.
-        if (decoder->mPeeker.mPatch != nullptr) {
+        if (decoder->mPeeker->mPatch != nullptr) {
             if (scale) {
-                decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight);
+                decoder->mPeeker->scale(scaleX, scaleY, desiredWidth, desiredHeight);
             }
-            size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
+            size_t ninePatchArraySize = decoder->mPeeker->mPatch->serializedSize();
             ninePatchChunk = env->NewByteArray(ninePatchArraySize);
             if (ninePatchChunk == nullptr) {
                 doThrowOOME(env, "Failed to allocate nine patch chunk.");
                 return nullptr;
             }
 
-            env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
-                                    reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
+            env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize,
+                                    reinterpret_cast<jbyte*>(decoder->mPeeker->mPatch));
         }
 
-        if (decoder->mPeeker.mHasInsets) {
-            ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
+        if (decoder->mPeeker->mHasInsets) {
+            ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f);
             if (ninePatchInsets == nullptr) {
                 doThrowOOME(env, "Failed to allocate nine patch insets.");
                 return nullptr;
@@ -497,7 +478,7 @@
 static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                      jobject outPadding) {
     auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
-    decoder->mPeeker.getPadding(env, outPadding);
+    decoder->mPeeker->getPadding(env, outPadding);
 }
 
 static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
@@ -525,7 +506,7 @@
 
 int register_android_graphics_ImageDecoder(JNIEnv* env) {
     gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
-    gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JII)V");
+    gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZ)V");
     gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;II)I");
 
     gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h
new file mode 100644
index 0000000..2df71eb
--- /dev/null
+++ b/core/jni/android/graphics/ImageDecoder.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NinePatchPeeker.h"
+
+#include <hwui/Canvas.h>
+
+#include <jni.h>
+
+class SkAndroidCodec;
+
+using namespace android;
+
+struct ImageDecoder {
+    // These need to stay in sync with ImageDecoder.java's Allocator constants.
+    enum Allocator {
+        kDefault_Allocator      = 0,
+        kSoftware_Allocator     = 1,
+        kSharedMemory_Allocator = 2,
+        kHardware_Allocator     = 3,
+    };
+
+    // These need to stay in sync with PixelFormat.java's Format constants.
+    enum PixelFormat {
+        kUnknown     =  0,
+        kTranslucent = -3,
+        kOpaque      = -1,
+    };
+
+    std::unique_ptr<SkAndroidCodec> mCodec;
+    sk_sp<NinePatchPeeker> mPeeker;
+
+    ImageDecoder()
+        :mPeeker(new NinePatchPeeker)
+    {}
+};
+
+// Creates a Java Canvas object from canvas, calls jimageDecoder's PostProcess on it, and then
+// releases the Canvas.
+// Caller needs to check for exceptions.
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas,
+                           int width, int height);
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index f8ca29e..05dadc9 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -26,6 +26,7 @@
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
+import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.NinePatchDrawable;
@@ -294,9 +295,10 @@
     };
 
     // Fields
-    private long      mNativePtr;
-    private final int mWidth;
-    private final int mHeight;
+    private long          mNativePtr;
+    private final int     mWidth;
+    private final int     mHeight;
+    private final boolean mAnimated;
 
     private int     mDesiredWidth;
     private int     mDesiredHeight;
@@ -322,12 +324,14 @@
      * called after decoding to delete native resources.
      */
     @SuppressWarnings("unused")
-    private ImageDecoder(long nativePtr, int width, int height) {
+    private ImageDecoder(long nativePtr, int width, int height,
+            boolean animated) {
         mNativePtr = nativePtr;
         mWidth = width;
         mHeight = height;
         mDesiredWidth = width;
         mDesiredHeight = height;
+        mAnimated = animated;
         mCloseGuard.open("close");
     }
 
@@ -726,6 +730,21 @@
                                                 "Drawable!");
             }
 
+            if (decoder.mAnimated) {
+                // AnimatedImageDrawable calls postProcessAndRelease only if
+                // mPostProcess exists.
+                ImageDecoder postProcessPtr = decoder.mPostProcess == null ?
+                        null : decoder;
+                Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
+                        postProcessPtr, decoder.mDesiredWidth,
+                        decoder.mDesiredHeight, decoder.mCropRect,
+                        decoder.mInputStream, decoder.mAssetFd);
+                // d has taken ownership of these objects.
+                decoder.mInputStream = null;
+                decoder.mAssetFd = null;
+                return d;
+            }
+
             Bitmap bm = decoder.decodeBitmap();
             Resources res = src.getResources();
             if (res == null) {
@@ -742,7 +761,6 @@
                         opticalInsets, null);
             }
 
-            // TODO: Handle animation.
             return new BitmapDrawable(res, bm);
         }
     }
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
new file mode 100644
index 0000000..ce3bd9a
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.ImageDecoder;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.SystemClock;
+
+import libcore.io.IoUtils;
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.Runnable;
+
+/**
+ * @hide
+ */
+public class AnimatedImageDrawable extends Drawable implements Animatable {
+    private final long                mNativePtr;
+    private final InputStream         mInputStream;
+    private final AssetFileDescriptor mAssetFd;
+
+    private final int                 mIntrinsicWidth;
+    private final int                 mIntrinsicHeight;
+
+    private Runnable mRunnable = new Runnable() {
+        @Override
+        public void run() {
+            invalidateSelf();
+        }
+    };
+
+    /**
+     * @hide
+     * This should only be called by ImageDecoder.
+     *
+     * decoder is only non-null if it has a PostProcess
+     */
+    public AnimatedImageDrawable(long nativeImageDecoder,
+            @Nullable ImageDecoder decoder, int width, int height, Rect cropRect,
+            InputStream inputStream, AssetFileDescriptor afd)
+            throws IOException {
+        mNativePtr = nCreate(nativeImageDecoder, decoder, width, height, cropRect);
+        mInputStream = inputStream;
+        mAssetFd = afd;
+
+        if (cropRect == null) {
+            mIntrinsicWidth  = width;
+            mIntrinsicHeight = height;
+        } else {
+            mIntrinsicWidth  = cropRect.width();
+            mIntrinsicHeight = cropRect.height();
+        }
+
+        long nativeSize = nNativeByteSize(mNativePtr);
+        NativeAllocationRegistry registry = new NativeAllocationRegistry(
+                AnimatedImageDrawable.class.getClassLoader(), nGetNativeFinalizer(), nativeSize);
+        registry.registerNativeAllocation(this, mNativePtr);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        // FIXME: It's a shame that we have *both* a native finalizer and a Java
+        // one. The native one is necessary to report how much memory is being
+        // used natively, and this one is necessary to close the input. An
+        // alternative might be to read the entire stream ahead of time, so we
+        // can eliminate the Java finalizer.
+        try {
+            IoUtils.closeQuietly(mInputStream);
+            IoUtils.closeQuietly(mAssetFd);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mIntrinsicWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mIntrinsicHeight;
+    }
+
+    @Override
+    public void draw(@NonNull Canvas canvas) {
+        long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper(),
+                SystemClock.uptimeMillis());
+        scheduleSelf(mRunnable, nextUpdate);
+    }
+
+    @Override
+    public void setAlpha(@IntRange(from=0,to=255) int alpha) {
+        if (alpha < 0 || alpha > 255) {
+            throw new IllegalArgumentException("Alpha must be between 0 and"
+                   + " 255! provided " + alpha);
+        }
+        nSetAlpha(mNativePtr, alpha);
+    }
+
+    @Override
+    public int getAlpha() {
+        return nGetAlpha(mNativePtr);
+    }
+
+    @Override
+    public void setColorFilter(@Nullable ColorFilter colorFilter) {
+        long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
+        nSetColorFilter(mNativePtr, nativeFilter);
+    }
+
+    @Override
+    public @PixelFormat.Opacity int getOpacity() {
+        return PixelFormat.TRANSLUCENT;
+    }
+
+    // TODO: Add a Constant State?
+    // @Override
+    // public @Nullable ConstantState getConstantState() {}
+
+
+    // Animatable overrides
+    @Override
+    public boolean isRunning() {
+        return nIsRunning(mNativePtr);
+    }
+
+    @Override
+    public void start() {
+        nStart(mNativePtr);
+    }
+
+    @Override
+    public void stop() {
+        nStop(mNativePtr);
+    }
+
+    private static native long nCreate(long nativeImageDecoder,
+            @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
+        throws IOException;
+    private static native long nGetNativeFinalizer();
+    private static native long nDraw(long nativePtr, long canvasNativePtr, long msecs);
+    private static native void nSetAlpha(long nativePtr, int alpha);
+    private static native int nGetAlpha(long nativePtr);
+    private static native void nSetColorFilter(long nativePtr, long nativeFilter);
+    private static native boolean nIsRunning(long nativePtr);
+    private static native void nStart(long nativePtr);
+    private static native void nStop(long nativePtr);
+    private static native long nNativeByteSize(long nativePtr);
+}
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3fb1c0d..fb7b246 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -495,6 +495,11 @@
                                           refPaint(paint), refBitmap(bitmap), refPatch(&patch)));
 }
 
+void RecordingCanvas::drawAnimatedImage(SkAnimatedImage*, float left, float top,
+                                        const SkPaint*) {
+    // Unimplemented
+}
+
 // Text
 void RecordingCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int glyphCount, const SkPaint& paint,
                                  float x, float y, float boundsLeft, float boundsTop,
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 3087db0..dd06ada 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -183,6 +183,8 @@
     virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
                                float dstTop, float dstRight, float dstBottom,
                                const SkPaint* paint) override;
+    virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
+                                   const SkPaint* paint) override;
 
     // Text
     virtual bool drawTextAbsolutePos() const override { return false; }
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 2e08670..dc274cf 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -23,6 +23,7 @@
 #include "hwui/MinikinUtils.h"
 #include "pipeline/skia/AnimatedDrawables.h"
 
+#include <SkAnimatedImage.h>
 #include <SkCanvasStateUtils.h>
 #include <SkColorFilter.h>
 #include <SkColorSpaceXformCanvas.h>
@@ -32,6 +33,7 @@
 #include <SkGraphics.h>
 #include <SkImage.h>
 #include <SkImagePriv.h>
+#include <SkPicture.h>
 #include <SkRSXform.h>
 #include <SkShader.h>
 #include <SkTemplates.h>
@@ -723,6 +725,20 @@
     mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
 }
 
+void SkiaCanvas::drawAnimatedImage(SkAnimatedImage* image, float left, float top,
+                                   const SkPaint* paint) {
+    sk_sp<SkPicture> pic(image->newPictureSnapshot());
+    SkMatrix matrixStorage;
+    SkMatrix* matrix;
+    if (left == 0.0f && top == 0.0f) {
+        matrix = nullptr;
+    } else {
+        matrixStorage = SkMatrix::MakeTrans(left, top);
+        matrix = &matrixStorage;
+    }
+    mCanvas->drawPicture(pic.get(), matrix, paint);
+}
+
 void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
     vectorDrawable->drawStaging(this);
 }
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 99e676a..7137210 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -124,6 +124,8 @@
     virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft,
                                float dstTop, float dstRight, float dstBottom,
                                const SkPaint* paint) override;
+    virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
+                                   const SkPaint* paint) override;
 
     virtual bool drawTextAbsolutePos() const override { return true; }
     virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index e682a2e..5efd357 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -28,6 +28,7 @@
 #include <SkCanvas.h>
 #include <SkMatrix.h>
 
+class SkAnimatedImage;
 class SkCanvasState;
 class SkVertices;
 
@@ -237,6 +238,9 @@
                                float dstTop, float dstRight, float dstBottom,
                                const SkPaint* paint) = 0;
 
+    virtual void drawAnimatedImage(SkAnimatedImage*, float left, float top,
+                                   const SkPaint* paint) = 0;
+
     /**
      * Specifies if the positions passed to ::drawText are absolute or relative
      * to the (x,y) value provided.