diff options
| -rw-r--r-- | api/current.txt | 17 | ||||
| -rw-r--r-- | core/jni/android/graphics/AnimatedImageDrawable.cpp | 48 | ||||
| -rw-r--r-- | core/jni/android/graphics/ByteBufferStreamAdaptor.cpp | 9 | ||||
| -rw-r--r-- | core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp | 13 | ||||
| -rw-r--r-- | core/jni/android/graphics/Utils.cpp | 8 | ||||
| -rw-r--r-- | core/jni/android/graphics/Utils.h | 2 | ||||
| -rw-r--r-- | core/res/res/values/attrs.xml | 7 | ||||
| -rw-r--r-- | graphics/java/android/graphics/ImageDecoder.java | 14 | ||||
| -rw-r--r-- | graphics/java/android/graphics/drawable/AnimatedImageDrawable.java | 309 | ||||
| -rw-r--r-- | graphics/java/android/graphics/drawable/DrawableInflater.java | 2 | ||||
| -rw-r--r-- | libs/hwui/hwui/AnimatedImageDrawable.h | 16 |
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 |