1a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik/*
2a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * Copyright (C) 2013 The Android Open Source Project
3a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik *
4a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * Licensed under the Apache License, Version 2.0 (the "License");
5a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * you may not use this file except in compliance with the License.
6a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * You may obtain a copy of the License at
7a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik *
8a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik *      http://www.apache.org/licenses/LICENSE-2.0
9a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik *
10a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * Unless required by applicable law or agreed to in writing, software
11a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * distributed under the License is distributed on an "AS IS" BASIS,
12a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * See the License for the specific language governing permissions and
14a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik * limitations under the License.
15a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik */
16a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
17a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikpackage android.support.rastermill;
18a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
19a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.Bitmap;
20537754df0d7348949e2324a91cc9e4df7571d7bfChris Craikimport android.graphics.BitmapShader;
21a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.Canvas;
22a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.ColorFilter;
23a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.Paint;
24a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.PixelFormat;
25a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.Rect;
26537754df0d7348949e2324a91cc9e4df7571d7bfChris Craikimport android.graphics.RectF;
27537754df0d7348949e2324a91cc9e4df7571d7bfChris Craikimport android.graphics.Shader;
28a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.drawable.Animatable;
29a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.drawable.Drawable;
30a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.os.Handler;
31a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.os.HandlerThread;
323105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craikimport android.os.Process;
33a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.os.SystemClock;
34a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
35a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikpublic class FrameSequenceDrawable extends Drawable implements Animatable, Runnable {
36e532fb97ba883a488bb751d51732274e03a052afChris Craik    /**
37e532fb97ba883a488bb751d51732274e03a052afChris Craik     * These constants are chosen to imitate common browser behavior for WebP/GIF.
38e532fb97ba883a488bb751d51732274e03a052afChris Craik     * If other decoders are added, this behavior should be moved into the WebP/GIF decoders.
39e532fb97ba883a488bb751d51732274e03a052afChris Craik     *
40e532fb97ba883a488bb751d51732274e03a052afChris Craik     * Note that 0 delay is undefined behavior in the GIF standard.
41e532fb97ba883a488bb751d51732274e03a052afChris Craik     */
42e532fb97ba883a488bb751d51732274e03a052afChris Craik    private static final long MIN_DELAY_MS = 20;
43e532fb97ba883a488bb751d51732274e03a052afChris Craik    private static final long DEFAULT_DELAY_MS = 100;
44e532fb97ba883a488bb751d51732274e03a052afChris Craik
45a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final Object sLock = new Object();
46a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static HandlerThread sDecodingThread;
47a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static Handler sDecodingThreadHandler;
48a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static void initializeDecodingThread() {
49a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (sLock) {
50a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            if (sDecodingThread != null) return;
51a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
523105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            sDecodingThread = new HandlerThread("FrameSequence decoding thread",
533105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                    Process.THREAD_PRIORITY_BACKGROUND);
54a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            sDecodingThread.start();
55a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            sDecodingThreadHandler = new Handler(sDecodingThread.getLooper());
56a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
57a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
58a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
59e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static interface OnFinishedListener {
60e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        /**
61e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * Called when a FrameSequenceDrawable has finished looping.
62e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         *
63e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * Note that this is will not be called if the drawable is explicitly
64e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * stopped, or marked invisible.
65e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         */
66e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        public abstract void onFinished(FrameSequenceDrawable drawable);
67e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
68e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
693105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    public static interface BitmapProvider {
703105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        /**
713105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         * Called by FrameSequenceDrawable to aquire an 8888 Bitmap with minimum dimensions.
723105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         */
733105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        public abstract Bitmap acquireBitmap(int minWidth, int minHeight);
743105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
753105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        /**
763105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         * Called by FrameSequenceDrawable to release a Bitmap it no longer needs. The Bitmap
773105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         * will no longer be used at all by the drawable, so it is safe to reuse elsewhere.
784eb541aff092a057b27b917f09d33aba226dffedChris Craik         *
794eb541aff092a057b27b917f09d33aba226dffedChris Craik         * This method may be called by FrameSequenceDrawable on any thread.
803105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         */
813105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        public abstract void releaseBitmap(Bitmap bitmap);
823105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
833105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
843105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    private static BitmapProvider sAllocatingBitmapProvider = new BitmapProvider() {
853105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        @Override
863105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        public Bitmap acquireBitmap(int minWidth, int minHeight) {
873105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            return Bitmap.createBitmap(minWidth, minHeight, Bitmap.Config.ARGB_8888);
883105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
893105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
903105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        @Override
91b19ecfd9cfa2c417034403adf7039edf9fe59327Chris Craik        public void releaseBitmap(Bitmap bitmap) {}
923105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    };
933105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
94e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
95e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Register a callback to be invoked when a FrameSequenceDrawable finishes looping.
96e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     *
973105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     * @see #setLoopBehavior(int)
98e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
99e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
100e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        mOnFinishedListener = onFinishedListener;
101e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
102e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
103e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
104e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Loop only once.
105e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
106e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_ONCE = 1;
107e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
108e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
109e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Loop continuously. The OnFinishedListener will never be called.
110e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
111e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_INF = 2;
112e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
113e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
114e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Use loop count stored in source data, or LOOP_ONCE if not present.
115e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
116e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_DEFAULT = 3;
117e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
118e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
119e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Define looping behavior of frame sequence.
120e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     *
121e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT
122e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
123e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public void setLoopBehavior(int loopBehavior) {
124e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        mLoopBehavior = loopBehavior;
125e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
126e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
127a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final FrameSequence mFrameSequence;
128a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final FrameSequence.State mFrameSequenceState;
129a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
130a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final Paint mPaint;
131537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    private BitmapShader mFrontBitmapShader;
132537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    private BitmapShader mBackBitmapShader;
133537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     private final Rect mSrcRect;
134537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    private boolean mCircleMaskEnabled;
135a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
136a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    //Protects the fields below
137a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final Object mLock = new Object();
138a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
1394eb541aff092a057b27b917f09d33aba226dffedChris Craik    private final BitmapProvider mBitmapProvider;
1404eb541aff092a057b27b917f09d33aba226dffedChris Craik    private boolean mDestroyed = false;
141a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Bitmap mFrontBitmap;
142a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Bitmap mBackBitmap;
143a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
144a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_SCHEDULED = 1;
145a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_DECODING = 2;
146a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_WAITING_TO_SWAP = 3;
147a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_READY_TO_SWAP = 4;
148a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
149a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private int mState;
150e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private int mCurrentLoop;
151e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private int mLoopBehavior = LOOP_DEFAULT;
152a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
153a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private long mLastSwap;
1543105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    private long mNextSwap;
155a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private int mNextFrameToDecode;
156e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private OnFinishedListener mOnFinishedListener;
157a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
158a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    /**
159a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik     * Runs on decoding thread, only modifies mBackBitmap's pixels
160a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik     */
161a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Runnable mDecodeRunnable = new Runnable() {
162a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        @Override
163a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        public void run() {
164a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            int nextFrame;
165a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            Bitmap bitmap;
166a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
1674eb541aff092a057b27b917f09d33aba226dffedChris Craik                if (mDestroyed) return;
1683105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
169a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                nextFrame = mNextFrameToDecode;
170a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                if (nextFrame < 0) {
171a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                    return;
172a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                }
173a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                bitmap = mBackBitmap;
174a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mState = STATE_DECODING;
175a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
176a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            int lastFrame = nextFrame - 2;
177a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
178537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
179e532fb97ba883a488bb751d51732274e03a052afChris Craik            if (invalidateTimeMs < MIN_DELAY_MS) {
180e532fb97ba883a488bb751d51732274e03a052afChris Craik                invalidateTimeMs = DEFAULT_DELAY_MS;
181e532fb97ba883a488bb751d51732274e03a052afChris Craik            }
182a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
18338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            boolean schedule = false;
18438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            Bitmap bitmapToRelease = null;
185a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
18638f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                if (mDestroyed) {
18738f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    bitmapToRelease = mBackBitmap;
18838f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    mBackBitmap = null;
18938f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                } else if (mNextFrameToDecode >= 0 && mState == STATE_DECODING) {
19038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    schedule = true;
19138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    mNextSwap = invalidateTimeMs + mLastSwap;
19238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    mState = STATE_WAITING_TO_SWAP;
19338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                }
19438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
19538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (schedule) {
19638f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                scheduleSelf(FrameSequenceDrawable.this, mNextSwap);
19738f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
19838f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (bitmapToRelease != null) {
19938f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                // destroy the bitmap here, since there's no safe way to get back to
20038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                // drawable thread - drawable is likely detached, so schedule is noop.
20138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                mBitmapProvider.releaseBitmap(bitmapToRelease);
202a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
203a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
204a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    };
205a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
206e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private Runnable mCallbackRunnable = new Runnable() {
207e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        @Override
208e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        public void run() {
209e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik            if (mOnFinishedListener != null) {
210e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                mOnFinishedListener.onFinished(FrameSequenceDrawable.this);
211e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik            }
212e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        }
213e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    };
214a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
2153105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    private static Bitmap acquireAndValidateBitmap(BitmapProvider bitmapProvider,
2163105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            int minWidth, int minHeight) {
2173105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        Bitmap bitmap = bitmapProvider.acquireBitmap(minWidth, minHeight);
2183105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2193105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        if (bitmap.getWidth() < minWidth
2203105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                || bitmap.getHeight() < minHeight
2213105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                || bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
2223105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            throw new IllegalArgumentException("Invalid bitmap provided");
2233105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
2243105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2253105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        return bitmap;
2263105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
2273105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
228a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public FrameSequenceDrawable(FrameSequence frameSequence) {
2293105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        this(frameSequence, sAllocatingBitmapProvider);
2303105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
2313105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2323105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    public FrameSequenceDrawable(FrameSequence frameSequence, BitmapProvider bitmapProvider) {
2334eb541aff092a057b27b917f09d33aba226dffedChris Craik        if (frameSequence == null || bitmapProvider == null) throw new IllegalArgumentException();
234a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
235a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequence = frameSequence;
236a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequenceState = frameSequence.createState();
237a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        final int width = frameSequence.getWidth();
238a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        final int height = frameSequence.getHeight();
239a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
2404eb541aff092a057b27b917f09d33aba226dffedChris Craik        mBitmapProvider = bitmapProvider;
2413105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        mFrontBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
2423105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        mBackBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
243a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mSrcRect = new Rect(0, 0, width, height);
244a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint = new Paint();
245a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setFilterBitmap(true);
246a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
247537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        mFrontBitmapShader
248537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            = new BitmapShader(mFrontBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
249537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        mBackBitmapShader
250537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            = new BitmapShader(mBackBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
251537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
252a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mLastSwap = 0;
253a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
254a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mNextFrameToDecode = -1;
255a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
256a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        initializeDecodingThread();
257a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
258a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
259537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    /**
260537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     * Pass true to mask the shape of the animated drawing content to a circle.
261537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     *
262537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     * <p> The masking circle will be the largest circle contained in the Drawable's bounds.
263537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     * Masking is done with BitmapShader, incurring minimal additional draw cost.
264537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     */
265537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    public final void setCircleMaskEnabled(boolean circleMaskEnabled) {
266537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        mCircleMaskEnabled = circleMaskEnabled;
267537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        // Anti alias only necessary when using circular mask
268537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        mPaint.setAntiAlias(circleMaskEnabled);
269537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    }
270537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
2714eb541aff092a057b27b917f09d33aba226dffedChris Craik    private void checkDestroyedLocked() {
2724eb541aff092a057b27b917f09d33aba226dffedChris Craik        if (mDestroyed) {
2733105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            throw new IllegalStateException("Cannot perform operation on recycled drawable");
2743105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
2753105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
2763105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2774eb541aff092a057b27b917f09d33aba226dffedChris Craik    public boolean isDestroyed() {
2783105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        synchronized (mLock) {
2794eb541aff092a057b27b917f09d33aba226dffedChris Craik            return mDestroyed;
2803105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
2813105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
2823105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2833105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    /**
2843105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     * Marks the drawable as permanently recycled (and thus unusable), and releases any owned
2854eb541aff092a057b27b917f09d33aba226dffedChris Craik     * Bitmaps drawable to its BitmapProvider, if attached.
2863105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     *
2874eb541aff092a057b27b917f09d33aba226dffedChris Craik     * If no BitmapProvider is attached to the drawable, recycle() is called on the Bitmaps.
2883105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     */
2894eb541aff092a057b27b917f09d33aba226dffedChris Craik    public void destroy() {
29038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        if (mBitmapProvider == null) {
2913105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            throw new IllegalStateException("BitmapProvider must be non-null");
2923105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
2933105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2943105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        Bitmap bitmapToReleaseA;
29538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        Bitmap bitmapToReleaseB = null;
2963105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        synchronized (mLock) {
2974eb541aff092a057b27b917f09d33aba226dffedChris Craik            checkDestroyedLocked();
2983105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2993105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            bitmapToReleaseA = mFrontBitmap;
3003105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            mFrontBitmap = null;
30138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik
30238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (mState != STATE_DECODING) {
30338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                bitmapToReleaseB = mBackBitmap;
30438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                mBackBitmap = null;
30538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
30638f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik
3074eb541aff092a057b27b917f09d33aba226dffedChris Craik            mDestroyed = true;
3083105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
3093105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
3104eb541aff092a057b27b917f09d33aba226dffedChris Craik        // For simplicity and safety, we don't destroy the state object here
31138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        mBitmapProvider.releaseBitmap(bitmapToReleaseA);
31238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        if (bitmapToReleaseB != null) {
31338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            mBitmapProvider.releaseBitmap(bitmapToReleaseB);
31438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        }
3153105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
3163105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
317a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
318a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    protected void finalize() throws Throwable {
319a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        try {
3204eb541aff092a057b27b917f09d33aba226dffedChris Craik            mFrameSequenceState.destroy();
321a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        } finally {
322a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            super.finalize();
323a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
324a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
325a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
326a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
327a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void draw(Canvas canvas) {
328a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
3294eb541aff092a057b27b917f09d33aba226dffedChris Craik            checkDestroyedLocked();
3303105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            if (mState == STATE_WAITING_TO_SWAP) {
3313105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                // may have failed to schedule mark ready runnable,
3323105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                // so go ahead and swap if swapping is due
3333105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                if (mNextSwap - SystemClock.uptimeMillis() <= 0) {
3343105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                    mState = STATE_READY_TO_SWAP;
3353105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                }
3363105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            }
3373105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
338a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            if (isRunning() && mState == STATE_READY_TO_SWAP) {
339a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                // Because draw has occurred, the view system is guaranteed to no longer hold a
340a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                // reference to the old mFrontBitmap, so we now use it to produce the next frame
341a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                Bitmap tmp = mBackBitmap;
342a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mBackBitmap = mFrontBitmap;
343a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mFrontBitmap = tmp;
344a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
345537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik                BitmapShader tmpShader = mBackBitmapShader;
346537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik                mBackBitmapShader = mFrontBitmapShader;
347537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik                mFrontBitmapShader = tmpShader;
348537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
349a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mLastSwap = SystemClock.uptimeMillis();
350e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
351e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                boolean continueLooping = true;
352e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
353e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    mCurrentLoop++;
354e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) ||
3553105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                            (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
356e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                        continueLooping = false;
357e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    }
358e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                }
359e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
360e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                if (continueLooping) {
361e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    scheduleDecodeLocked();
362e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                } else {
363e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    scheduleSelf(mCallbackRunnable, 0);
364e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                }
365a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
366a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
367a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
368537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        if (mCircleMaskEnabled) {
369537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            Rect bounds = getBounds();
370537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            mPaint.setShader(mFrontBitmapShader);
371537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            float width = bounds.width();
372537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            float height = bounds.height();
373537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            float circleRadius = (Math.min(width, height)) / 2f;
374537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            canvas.drawCircle(width / 2f, height / 2f, circleRadius, mPaint);
375537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        } else {
376537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            mPaint.setShader(null);
377537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
378537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        }
379a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
380a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
381a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private void scheduleDecodeLocked() {
382a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mState = STATE_SCHEDULED;
383a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
384a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        sDecodingThreadHandler.post(mDecodeRunnable);
385a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
386a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
387a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
388a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void run() {
38938f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        // set ready to swap as necessary
39038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        boolean invalidate = false;
391a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
39238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (mNextFrameToDecode >= 0 && mState == STATE_WAITING_TO_SWAP) {
39338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                mState = STATE_READY_TO_SWAP;
39438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                invalidate = true;
39538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
39638f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        }
39738f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        if (invalidate) {
39838f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            invalidateSelf();
399a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
400a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
401a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
402a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
403a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void start() {
404a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (!isRunning()) {
405a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
4064eb541aff092a057b27b917f09d33aba226dffedChris Craik                checkDestroyedLocked();
407a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                if (mState == STATE_SCHEDULED) return; // already scheduled
408e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                mCurrentLoop = 0;
409a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                scheduleDecodeLocked();
410a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
411a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
412a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
413a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
414a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
415a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void stop() {
416a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (isRunning()) {
417a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            unscheduleSelf(this);
418a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
419a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
420a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
421a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
422a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public boolean isRunning() {
423a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
4244eb541aff092a057b27b917f09d33aba226dffedChris Craik            return mNextFrameToDecode > -1 && !mDestroyed;
425a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
426a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
427a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
428a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
429a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void unscheduleSelf(Runnable what) {
430a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
431a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            mNextFrameToDecode = -1;
432537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            mState = 0;
433a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
434a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        super.unscheduleSelf(what);
435a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
436a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
437a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
438a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public boolean setVisible(boolean visible, boolean restart) {
439a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        boolean changed = super.setVisible(visible, restart);
440a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
441a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (!visible) {
442a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            stop();
443a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        } else if (restart || changed) {
444a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            stop();
445a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            start();
446a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
447a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
448a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return changed;
449a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
450a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
451a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    // drawing properties
452a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
453a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
454a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setFilterBitmap(boolean filter) {
455a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setFilterBitmap(filter);
456a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
457a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
458a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
459a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setAlpha(int alpha) {
460a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setAlpha(alpha);
461a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
462a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
463a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
464a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setColorFilter(ColorFilter colorFilter) {
465a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setColorFilter(colorFilter);
466a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
467a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
468a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
469a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getIntrinsicWidth() {
470a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.getWidth();
471a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
472a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
473a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
474a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getIntrinsicHeight() {
475a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.getHeight();
476a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
477a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
478a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
479a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getOpacity() {
480a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
481a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
482a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik}
483