Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit beaf5d91 authored by Leon Scroggins III's avatar Leon Scroggins III
Browse files

Fixes for animationEnd in AnimatedImageDrawable

Bug: 63908092
Test: Manual

If the animation is running on the render thread, post a message (in
JNI) to call the listener.

Use a weak reference in the native listener so that it does not create
a cycle.

Don't add a listener if it's already in the list. Remove the native
listener when the Java listeners are all removed.

Call onAnimationEnd when the animation is stopped manually.

Change-Id: I5dc070089fb1cf399ac3316292592f614f2738f0
parent ce9bcc49
Loading
Loading
Loading
Loading
+46 −15
Original line number Diff line number Diff line
@@ -26,10 +26,11 @@
#include <SkPictureRecorder.h>
#include <hwui/AnimatedImageDrawable.h>
#include <hwui/Canvas.h>
#include <utils/Looper.h>

using namespace android;

static jmethodID gAnimatedImageDrawable_postOnAnimationEndMethodID;
static jmethodID gAnimatedImageDrawable_onAnimationEndMethodID;

// Note: jpostProcess holds a handle to the ImageDecoder.
static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
@@ -123,9 +124,9 @@ static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlo
    return drawable->start();
}

static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
static jboolean AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
    drawable->stop();
    return drawable->stop();
}

// Java's LOOP_INFINITE relies on this being the same.
@@ -137,33 +138,63 @@ static void AnimatedImageDrawable_nSetLoopCount(JNIEnv* env, jobject /*clazz*/,
    drawable->setRepetitionCount(loopCount);
}

class JniAnimationEndListener : public OnAnimationEndListener {
class InvokeListener : public MessageHandler {
public:
    JniAnimationEndListener(JNIEnv* env, jobject javaObject) {
    InvokeListener(JNIEnv* env, jobject javaObject) {
        LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJvm) != JNI_OK);
        mJavaObject = env->NewGlobalRef(javaObject);
        // Hold a weak reference to break a cycle that would prevent GC.
        mWeakRef = env->NewWeakGlobalRef(javaObject);
    }

    ~JniAnimationEndListener() override {
    ~InvokeListener() override {
        auto* env = get_env_or_die(mJvm);
        env->DeleteGlobalRef(mJavaObject);
        env->DeleteWeakGlobalRef(mWeakRef);
    }

    void onAnimationEnd() override {
    virtual void handleMessage(const Message&) override {
        auto* env = get_env_or_die(mJvm);
        env->CallVoidMethod(mJavaObject, gAnimatedImageDrawable_postOnAnimationEndMethodID);
        jobject localRef = env->NewLocalRef(mWeakRef);
        if (localRef) {
            env->CallVoidMethod(localRef, gAnimatedImageDrawable_onAnimationEndMethodID);
        }
    }

private:
    JavaVM* mJvm;
    jobject mJavaObject;
    jweak mWeakRef;
};

class JniAnimationEndListener : public OnAnimationEndListener {
public:
    JniAnimationEndListener(sp<Looper>&& looper, JNIEnv* env, jobject javaObject) {
        mListener = new InvokeListener(env, javaObject);
        mLooper = std::move(looper);
    }

    void onAnimationEnd() override { mLooper->sendMessage(mListener, 0); }

private:
    sp<InvokeListener> mListener;
    sp<Looper> mLooper;
};

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)));
    if (!jdrawable) {
        drawable->setOnAnimationEndListener(nullptr);
    } else {
        sp<Looper> looper = Looper::getForThread();
        if (!looper.get()) {
            doThrowISE(env,
                       "Must set AnimatedImageDrawable's AnimationCallback on a thread with a "
                       "looper!");
            return;
        }

        drawable->setOnAnimationEndListener(
                std::make_unique<JniAnimationEndListener>(std::move(looper), env, jdrawable));
    }
}

static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -186,7 +217,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
    { "nSetColorFilter",     "(JJ)V",                                                        (void*) AnimatedImageDrawable_nSetColorFilter },
    { "nIsRunning",          "(J)Z",                                                         (void*) AnimatedImageDrawable_nIsRunning },
    { "nStart",              "(J)Z",                                                         (void*) AnimatedImageDrawable_nStart },
    { "nStop",               "(J)V",                                                         (void*) AnimatedImageDrawable_nStop },
    { "nStop",               "(J)Z",                                                         (void*) AnimatedImageDrawable_nStop },
    { "nSetLoopCount",       "(JI)V",                                                        (void*) AnimatedImageDrawable_nSetLoopCount },
    { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener },
    { "nNativeByteSize",     "(J)J",                                                         (void*) AnimatedImageDrawable_nNativeByteSize },
@@ -195,7 +226,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = {

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");
    gAnimatedImageDrawable_onAnimationEndMethodID = GetMethodIDOrDie(env, animatedImageDrawable_class, "onAnimationEnd", "()V");

    return android::RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedImageDrawable",
            gAnimatedImageDrawableMethods, NELEM(gAnimatedImageDrawableMethods));
+33 −6
Original line number Diff line number Diff line
@@ -348,7 +348,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
        if (mState == null) {
            throw new IllegalStateException("called stop on empty AnimatedImageDrawable");
        }
        nStop(mState.mNativePtr);
        if (nStop(mState.mNativePtr)) {
            postOnAnimationEnd();
        }
    }

    // Animatable2 overrides
@@ -365,21 +367,31 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
            nSetOnAnimationEndListener(mState.mNativePtr, this);
        }

        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }
    }

    @Override
    public boolean unregisterAnimationCallback(@NonNull AnimationCallback callback) {
        if (callback == null || mAnimationCallbacks == null) {
        if (callback == null || mAnimationCallbacks == null
                || !mAnimationCallbacks.remove(callback)) {
            return false;
        }

        return mAnimationCallbacks.remove(callback);
        if (mAnimationCallbacks.isEmpty()) {
            clearAnimationCallbacks();
        }

        return true;
    }

    @Override
    public void clearAnimationCallbacks() {
        if (mAnimationCallbacks != null) {
            mAnimationCallbacks = null;
            nSetOnAnimationEndListener(mState.mNativePtr, null);
        }
    }

    private void postOnAnimationStart() {
@@ -413,6 +425,21 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
        return mHandler;
    }

    /**
     *  Called by JNI.
     *
     *  The JNI code has already posted this to the thread that created the
     *  callback, so no need to post.
     */
    @SuppressWarnings("unused")
    private void onAnimationEnd() {
        if (mAnimationCallbacks != null) {
            for (Animatable2.AnimationCallback callback : mAnimationCallbacks) {
                callback.onAnimationEnd(this);
            }
        }
    }


    private static native long nCreate(long nativeImageDecoder,
            @Nullable ImageDecoder decoder, int width, int height, Rect cropRect)
@@ -432,7 +459,7 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 {
    @FastNative
    private static native boolean nStart(long nativePtr);
    @FastNative
    private static native void nStop(long nativePtr);
    private static native boolean nStop(long nativePtr);
    @FastNative
    private static native void nSetLoopCount(long nativePtr, int loopCount);
    // Pass the drawable down to native so it can call onAnimationEnd.
+3 −2
Original line number Diff line number Diff line
@@ -48,8 +48,10 @@ bool AnimatedImageDrawable::start() {
    return true;
}

void AnimatedImageDrawable::stop() {
bool AnimatedImageDrawable::stop() {
    bool wasRunning = mRunning;
    mRunning = false;
    return wasRunning;
}

bool AnimatedImageDrawable::isRunning() {
@@ -180,7 +182,6 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) {
    if (finalFrame) {
        if (mEndListener) {
            mEndListener->onAnimationEnd();
            mEndListener = nullptr;
        }
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -68,7 +68,9 @@ public:
    // Returns true if the animation was started; false otherwise (e.g. it was
    // already running)
    bool start();
    void stop();
    // Returns true if the animation was stopped; false otherwise (e.g. it was
    // already stopped)
    bool stop();
    bool isRunning();
    void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }