summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt17
-rw-r--r--core/jni/android/graphics/AnimatedImageDrawable.cpp48
-rw-r--r--core/jni/android/graphics/ByteBufferStreamAdaptor.cpp9
-rw-r--r--core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp13
-rw-r--r--core/jni/android/graphics/Utils.cpp8
-rw-r--r--core/jni/android/graphics/Utils.h2
-rw-r--r--core/res/res/values/attrs.xml7
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java14
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedImageDrawable.java309
-rw-r--r--graphics/java/android/graphics/drawable/DrawableInflater.java2
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.h16
11 files changed, 387 insertions, 58 deletions
diff --git a/api/current.txt b/api/current.txt
index 5996bb4aded6..2b6da9f5652a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13569,6 +13569,7 @@ package android.graphics {
public static class ImageDecoder.ImageInfo {
method public java.lang.String getMimeType();
method public android.util.Size getSize();
+ method public boolean isAnimated();
}
public static class ImageDecoder.IncompleteException extends java.io.IOException {
@@ -14439,6 +14440,22 @@ package android.graphics.drawable {
method public void onAnimationStart(android.graphics.drawable.Drawable);
}
+ public class AnimatedImageDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Animatable2 {
+ ctor public AnimatedImageDrawable();
+ method public void clearAnimationCallbacks();
+ method public void draw(android.graphics.Canvas);
+ method public int getOpacity();
+ method public boolean isRunning();
+ method public void registerAnimationCallback(android.graphics.drawable.Animatable2.AnimationCallback);
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setLoopCount(int);
+ method public void start();
+ method public void stop();
+ method public boolean unregisterAnimationCallback(android.graphics.drawable.Animatable2.AnimationCallback);
+ field public static final int LOOP_INFINITE = -1; // 0xffffffff
+ }
+
public class AnimatedStateListDrawable extends android.graphics.drawable.StateListDrawable {
ctor public AnimatedStateListDrawable();
method public void addState(int[], android.graphics.drawable.Drawable, int);
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index 262b55323cca..0e562c03b762 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -17,6 +17,7 @@
#include "GraphicsJNI.h"
#include "ImageDecoder.h"
#include "core_jni_helpers.h"
+#include "Utils.h"
#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>
@@ -28,6 +29,7 @@
using namespace android;
+static jmethodID gAnimatedImageDrawable_postOnAnimationEndMethodID;
// Note: jpostProcess holds a handle to the ImageDecoder.
static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -113,6 +115,9 @@ static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/,
return drawable->isRunning();
}
+// Java's NOT_RUNNING relies on this being -2.0.
+static_assert(SkAnimatedImage::kNotRunning == -2.0);
+
static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
return drawable->start();
@@ -123,6 +128,44 @@ static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong na
drawable->stop();
}
+// Java's LOOP_INFINITE relies on this being the same.
+static_assert(SkCodec::kRepetitionCountInfinite == -1);
+
+static void AnimatedImageDrawable_nSetLoopCount(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jint loopCount) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ drawable->setRepetitionCount(loopCount);
+}
+
+class JniAnimationEndListener : public OnAnimationEndListener {
+public:
+ JniAnimationEndListener(JNIEnv* env, jobject javaObject) {
+ LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK);
+ mJavaObject = env->NewGlobalRef(javaObject);
+ }
+
+ ~JniAnimationEndListener() override {
+ auto* env = get_env_or_die(mJvm);
+ env->DeleteGlobalRef(mJavaObject);
+ }
+
+ void onAnimationEnd() override {
+ auto* env = get_env_or_die(mJvm);
+ env->CallVoidMethod(mJavaObject, gAnimatedImageDrawable_postOnAnimationEndMethodID);
+ }
+
+private:
+ JavaVM* mJvm;
+ jobject mJavaObject;
+};
+
+static void AnimatedImageDrawable_nSetOnAnimationEndListener(JNIEnv* env, jobject /*clazz*/,
+ jlong nativePtr, jobject jdrawable) {
+ auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+ drawable->setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener>(
+ new JniAnimationEndListener(env, jdrawable)));
+}
+
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.
@@ -139,10 +182,15 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
{ "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning },
{ "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart },
{ "nStop", "(J)V", (void*) AnimatedImageDrawable_nStop },
+ { "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount },
+ { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
{ "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize },
};
int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
+ jclass animatedImageDrawable_class = FindClassOrDie(env, "android/graphics/drawable/AnimatedImageDrawable");
+ gAnimatedImageDrawable_postOnAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "postOnAnimationEnd", "()V");
+
return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));
}
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
index 85c9ef36b82e..173818b13837 100644
--- a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
@@ -1,5 +1,6 @@
#include "ByteBufferStreamAdaptor.h"
#include "core_jni_helpers.h"
+#include "Utils.h"
#include <SkStream.h>
@@ -8,14 +9,6 @@ using namespace android;
static jmethodID gByteBuffer_getMethodID;
static jmethodID gByteBuffer_setPositionMethodID;
-static JNIEnv* get_env_or_die(JavaVM* jvm) {
- JNIEnv* env;
- if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
- }
- return env;
-}
-
class ByteBufferStream : public SkStreamAsset {
private:
ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length,
diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
index 4257c981be18..7a9fea72a78e 100644
--- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
+++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp
@@ -12,15 +12,6 @@
static jmethodID gInputStream_readMethodID;
static jmethodID gInputStream_skipMethodID;
-// FIXME: Share with ByteBufferStreamAdaptor.cpp?
-static JNIEnv* get_env_or_die(JavaVM* jvm) {
- JNIEnv* env;
- if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
- }
- return env;
-}
-
/**
* Wrapper for a Java InputStream.
*/
@@ -57,13 +48,13 @@ public:
}
~JavaInputStreamAdaptor() override {
- auto* env = get_env_or_die(fJvm);
+ auto* env = android::get_env_or_die(fJvm);
env->DeleteGlobalRef(fJavaInputStream);
env->DeleteGlobalRef(fJavaByteArray);
}
size_t read(void* buffer, size_t size) override {
- auto* env = get_env_or_die(fJvm);
+ auto* env = android::get_env_or_die(fJvm);
if (!fSwallowExceptions && checkException(env)) {
// Just in case the caller did not clear from a previous exception.
return 0;
diff --git a/core/jni/android/graphics/Utils.cpp b/core/jni/android/graphics/Utils.cpp
index 630220a4c9ed..dd9bafe3b411 100644
--- a/core/jni/android/graphics/Utils.cpp
+++ b/core/jni/android/graphics/Utils.cpp
@@ -117,3 +117,11 @@ jobject android::nullObjectReturn(const char msg[]) {
bool android::isSeekable(int descriptor) {
return ::lseek64(descriptor, 0, SEEK_CUR) != -1;
}
+
+JNIEnv* android::get_env_or_die(JavaVM* jvm) {
+ JNIEnv* env;
+ if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
+ }
+ return env;
+}
diff --git a/core/jni/android/graphics/Utils.h b/core/jni/android/graphics/Utils.h
index 69930a581468..2f2ee9654489 100644
--- a/core/jni/android/graphics/Utils.h
+++ b/core/jni/android/graphics/Utils.h
@@ -74,6 +74,8 @@ jobject nullObjectReturn(const char msg[]);
*/
bool isSeekable(int descriptor);
+JNIEnv* get_env_or_die(JavaVM* jvm);
+
}; // namespace android
#endif // _ANDROID_GRAPHICS_UTILS_H_
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 354d65811164..5184dda70252 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5878,6 +5878,13 @@
<attr name="insetBottom" format="fraction|dimension" />
</declare-styleable>
+ <!-- Drawable used to draw animated images (gif) -->
+ <declare-styleable name="AnimatedImageDrawable">
+ <!-- Identifier of the image file. This attribute is mandatory.
+ It must be an image file with multiple frames, e.g. gif or webp -->
+ <attr name="src" />
+ </declare-styleable>
+
<!-- Drawable used to draw bitmaps. -->
<declare-styleable name="BitmapDrawable">
<!-- Identifier of the bitmap file. This attribute is mandatory. -->
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 3de050b5ffa5..3ead5911d890 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -339,6 +339,16 @@ public final class ImageDecoder implements AutoCloseable {
public String getMimeType() {
return mDecoder.getMimeType();
}
+
+ /**
+ * Whether the image is animated.
+ *
+ * <p>Calling {@link #decodeDrawable} will return an
+ * {@link AnimatedImageDrawable}.</p>
+ */
+ public boolean isAnimated() {
+ return mDecoder.mAnimated;
+ }
};
/**
@@ -454,6 +464,10 @@ public final class ImageDecoder implements AutoCloseable {
mCloseGuard.warnIfOpen();
}
+ // Avoid closing these in finalizer.
+ mInputStream = null;
+ mAssetFd = null;
+
close();
} finally {
super.finalize();
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 3034a1056000..0ec19f9a4aee 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -20,14 +20,26 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.InflateException;
import android.graphics.Bitmap;
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 android.os.Handler;
+import android.os.Looper;
import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import libcore.io.IoUtils;
import libcore.util.NativeAllocationRegistry;
@@ -35,17 +47,38 @@ import libcore.util.NativeAllocationRegistry;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Runnable;
+import java.util.ArrayList;
/**
- * @hide
+ * {@link Drawable} for drawing animated images (like GIF).
+ *
+ * <p>Created by {@link ImageDecoder#decodeDrawable}. A user needs to call
+ * {@link #start} to start the animation.</p>
*/
-public class AnimatedImageDrawable extends Drawable implements Animatable {
- private final long mNativePtr;
- private final InputStream mInputStream;
- private final AssetFileDescriptor mAssetFd;
+public class AnimatedImageDrawable extends Drawable implements Animatable2 {
+ private int mIntrinsicWidth;
+ private int mIntrinsicHeight;
+
+ private boolean mStarting;
+ private boolean mRunning;
+
+ private Handler mHandler;
+
+ private class State {
+ State(long nativePtr, InputStream is, AssetFileDescriptor afd) {
+ mNativePtr = nativePtr;
+ mInputStream = is;
+ mAssetFd = afd;
+ }
+
+ public final long mNativePtr;
+
+ // These just keep references so the native code can continue using them.
+ private final InputStream mInputStream;
+ private final AssetFileDescriptor mAssetFd;
+ }
- private final int mIntrinsicWidth;
- private final int mIntrinsicHeight;
+ private State mState;
private Runnable mRunnable = new Runnable() {
@Override
@@ -55,6 +88,95 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
};
/**
+ * Pass this to {@link #setLoopCount} to loop infinitely.
+ *
+ * <p>{@link Animatable2.AnimationCallback#onAnimationEnd} will never be
+ * called unless there is an error.</p>
+ */
+ public static final int LOOP_INFINITE = -1;
+
+ /**
+ * Specify the number of times to loop the animation.
+ *
+ * <p>By default, the loop count in the encoded data is respected.</p>
+ */
+ public void setLoopCount(int loopCount) {
+ if (mState == null) {
+ throw new IllegalStateException("called setLoopCount on empty AnimatedImageDrawable");
+ }
+ nSetLoopCount(mState.mNativePtr, loopCount);
+ }
+
+ /**
+ * Create an empty AnimatedImageDrawable.
+ */
+ public AnimatedImageDrawable() {
+ mState = null;
+ }
+
+ @Override
+ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
+ super.inflate(r, parser, attrs, theme);
+
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedImageDrawable);
+ updateStateFromTypedArray(a, mSrcDensityOverride);
+ }
+
+ private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride)
+ throws XmlPullParserException {
+ final Resources r = a.getResources();
+ final int srcResId = a.getResourceId(R.styleable.AnimatedImageDrawable_src, 0);
+ if (srcResId != 0) {
+ // Follow the density handling in BitmapDrawable.
+ final TypedValue value = new TypedValue();
+ r.getValueForDensity(srcResId, srcDensityOverride, value, true);
+ if (srcDensityOverride > 0 && value.density > 0
+ && value.density != TypedValue.DENSITY_NONE) {
+ if (value.density == srcDensityOverride) {
+ value.density = r.getDisplayMetrics().densityDpi;
+ } else {
+ value.density =
+ (value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride;
+ }
+ }
+
+ int density = Bitmap.DENSITY_NONE;
+ if (value.density == TypedValue.DENSITY_DEFAULT) {
+ density = DisplayMetrics.DENSITY_DEFAULT;
+ } else if (value.density != TypedValue.DENSITY_NONE) {
+ density = value.density;
+ }
+
+ Drawable drawable = null;
+ try {
+ InputStream is = r.openRawResource(srcResId, value);
+ ImageDecoder.Source source = ImageDecoder.createSource(r, is, density);
+ drawable = ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
+ if (!info.isAnimated()) {
+ throw new IllegalArgumentException("image is not animated");
+ }
+ });
+ } catch (IOException e) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <animated-image> requires a valid 'src' attribute", null, e);
+ }
+
+ if (!(drawable instanceof AnimatedImageDrawable)) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <animated-image> did not decode animated");
+ }
+
+ // Transfer the state of other to this one. other will be discarded.
+ AnimatedImageDrawable other = (AnimatedImageDrawable) drawable;
+ mState = other.mState;
+ other.mState = null;
+ mIntrinsicWidth = other.mIntrinsicWidth;
+ mIntrinsicHeight = other.mIntrinsicHeight;
+ }
+ }
+
+ /**
* @hide
* This should only be called by ImageDecoder.
*
@@ -80,30 +202,14 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
mIntrinsicHeight = cropRect.height();
}
- mNativePtr = nCreate(nativeImageDecoder, decoder, width, height, cropRect);
- mInputStream = inputStream;
- mAssetFd = afd;
+ mState = new State(nCreate(nativeImageDecoder, decoder, width, height, cropRect),
+ inputStream, afd);
// FIXME: Use the right size for the native allocation.
long nativeSize = 200;
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();
- }
+ registry.registerNativeAllocation(mState, mState.mNativePtr);
}
@Override
@@ -116,13 +222,34 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
return mIntrinsicHeight;
}
+ // nDraw returns -2 if the animation is not running.
+ private static final int NOT_RUNNING = -2;
+
@Override
public void draw(@NonNull Canvas canvas) {
- long nextUpdate = nDraw(mNativePtr, canvas.getNativeCanvasWrapper());
+ if (mState == null) {
+ throw new IllegalStateException("called draw on empty AnimatedImageDrawable");
+ }
+
+ if (mStarting) {
+ mStarting = false;
+
+ postOnAnimationStart();
+
+ mRunning = true;
+ }
+
+ long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper());
// a value <= 0 indicates that the drawable is stopped or that renderThread
// will manage the animation
if (nextUpdate > 0) {
scheduleSelf(mRunnable, nextUpdate);
+ } else if (nextUpdate == NOT_RUNNING) {
+ // -2 means the animation ended, when drawn in software mode.
+ if (mRunning) {
+ postOnAnimationEnd();
+ mRunning = false;
+ }
}
}
@@ -132,19 +259,31 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
throw new IllegalArgumentException("Alpha must be between 0 and"
+ " 255! provided " + alpha);
}
- nSetAlpha(mNativePtr, alpha);
+
+ if (mState == null) {
+ throw new IllegalStateException("called setAlpha on empty AnimatedImageDrawable");
+ }
+
+ nSetAlpha(mState.mNativePtr, alpha);
invalidateSelf();
}
@Override
public int getAlpha() {
- return nGetAlpha(mNativePtr);
+ if (mState == null) {
+ throw new IllegalStateException("called getAlpha on empty AnimatedImageDrawable");
+ }
+ return nGetAlpha(mState.mNativePtr);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ if (mState == null) {
+ throw new IllegalStateException("called setColorFilter on empty AnimatedImageDrawable");
+ }
+
long nativeFilter = colorFilter == null ? 0 : colorFilter.getNativeInstance();
- nSetColorFilter(mNativePtr, nativeFilter);
+ nSetColorFilter(mState.mNativePtr, nativeFilter);
invalidateSelf();
}
@@ -153,29 +292,116 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
return PixelFormat.TRANSLUCENT;
}
- // TODO: Add a Constant State?
- // @Override
- // public @Nullable ConstantState getConstantState() {}
-
-
// Animatable overrides
+ /**
+ * Return whether the animation is currently running.
+ *
+ * <p>When this drawable is created, this will return {@code false}. A client
+ * needs to call {@link #start} to start the animation.</p>
+ */
@Override
public boolean isRunning() {
- return nIsRunning(mNativePtr);
+ return mRunning;
}
+ /**
+ * Start the animation.
+ *
+ * <p>Does nothing if the animation is already running.
+ *
+ * <p>If the animation starts, this will call
+ * {@link Animatable2.AnimationCallback#onAnimationStart}.</p>
+ */
@Override
public void start() {
- if (nStart(mNativePtr)) {
+ if (mState == null) {
+ throw new IllegalStateException("called start on empty AnimatedImageDrawable");
+ }
+
+ if (nStart(mState.mNativePtr)) {
+ mStarting = true;
invalidateSelf();
}
}
+ /**
+ * Stop the animation.
+ *
+ * <p>If the animation is stopped, it will continue to display the frame
+ * it was displaying when stopped.</p>
+ */
@Override
public void stop() {
- nStop(mNativePtr);
+ if (mState == null) {
+ throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
+ }
+ nStop(mState.mNativePtr);
+ mRunning = false;
}
+ // Animatable2 overrides
+ private ArrayList<Animatable2.AnimationCallback> mAnimationCallbacks = null;
+
+ @Override
+ public void registerAnimationCallback(@NonNull AnimationCallback callback) {
+ if (callback == null) {
+ return;
+ }
+
+ if (mAnimationCallbacks == null) {
+ mAnimationCallbacks = new ArrayList<Animatable2.AnimationCallback>();
+ nSetOnAnimationEndListener(mState.mNativePtr, this);
+ }
+
+ mAnimationCallbacks.add(callback);
+ }
+
+ @Override
+ public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
+ if (callback == null || mAnimationCallbacks == null) {
+ return false;
+ }
+
+ return mAnimationCallbacks.remove(callback);
+ }
+
+ @Override
+ public void clearAnimationCallbacks() {
+ mAnimationCallbacks = null;
+ }
+
+ private void postOnAnimationStart() {
+ if (mAnimationCallbacks == null) {
+ return;
+ }
+
+ getHandler().post(() -> {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationStart(this);
+ }
+ });
+ }
+
+ private void postOnAnimationEnd() {
+ if (mAnimationCallbacks == null) {
+ return;
+ }
+
+ getHandler().post(() -> {
+ for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
+ callback.onAnimationEnd(this);
+ }
+ });
+ }
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+ return mHandler;
+ }
+
+
private static native long nCreate(long nativeImageDecoder,
@Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
throws IOException;
@@ -185,7 +411,12 @@ public class AnimatedImageDrawable extends Drawable implements Animatable {
private static native int nGetAlpha(long nativePtr);
private static native void nSetColorFilter(long nativePtr, long nativeFilter);
private static native boolean nIsRunning(long nativePtr);
+ // Return whether the animation started.
private static native boolean nStart(long nativePtr);
private static native void nStop(long nativePtr);
+ private static native void nSetLoopCount(long nativePtr, int loopCount);
+ // Pass the drawable down to native so it can call onAnimationEnd.
+ private static native void nSetOnAnimationEndListener(long nativePtr,
+ @Nullable AnimatedImageDrawable drawable);
private static native long nNativeByteSize(long nativePtr);
}
diff --git a/graphics/java/android/graphics/drawable/DrawableInflater.java b/graphics/java/android/graphics/drawable/DrawableInflater.java
index eea7048ca534..0ee9071f4d06 100644
--- a/graphics/java/android/graphics/drawable/DrawableInflater.java
+++ b/graphics/java/android/graphics/drawable/DrawableInflater.java
@@ -185,6 +185,8 @@ public final class DrawableInflater {
return new BitmapDrawable();
case "nine-patch":
return new NinePatchDrawable();
+ case "animated-image":
+ return new AnimatedImageDrawable();
default:
return null;
}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 1ebb58561f4d..df8a5e48d18e 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -33,6 +33,13 @@ namespace uirenderer {
class TaskManager;
}
+class OnAnimationEndListener {
+public:
+ virtual ~OnAnimationEndListener() {}
+
+ virtual void onAnimationEnd() = 0;
+};
+
/**
* Native component of android.graphics.drawable.AnimatedImageDrawables.java. This class can be
* drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread.
@@ -62,6 +69,13 @@ public:
bool start();
void stop();
bool isRunning();
+ void setRepetitionCount(int count) {
+ mSkAnimatedImage->setRepetitionCount(count);
+ }
+
+ void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) {
+ mEndListener = std::move(listener);
+ }
void scheduleUpdate(uirenderer::TaskManager* taskManager);
@@ -87,6 +101,8 @@ private:
class AnimatedImageTaskProcessor;
sp<AnimatedImageTask> mDecodeTask;
sp<AnimatedImageTaskProcessor> mDecodeTaskProcessor;
+
+ std::unique_ptr<OnAnimationEndListener> mEndListener;
};
}; // namespace android