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;
34769d2c1df936a328f324a953f3cdf5901dd99078Chris Craikimport android.util.Log;
35a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
36a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikpublic class FrameSequenceDrawable extends Drawable implements Animatable, Runnable {
37769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik    private static final String TAG = "FrameSequence";
38e532fb97ba883a488bb751d51732274e03a052afChris Craik    /**
39e532fb97ba883a488bb751d51732274e03a052afChris Craik     * These constants are chosen to imitate common browser behavior for WebP/GIF.
40e532fb97ba883a488bb751d51732274e03a052afChris Craik     * If other decoders are added, this behavior should be moved into the WebP/GIF decoders.
41e532fb97ba883a488bb751d51732274e03a052afChris Craik     *
42e532fb97ba883a488bb751d51732274e03a052afChris Craik     * Note that 0 delay is undefined behavior in the GIF standard.
43e532fb97ba883a488bb751d51732274e03a052afChris Craik     */
44e532fb97ba883a488bb751d51732274e03a052afChris Craik    private static final long MIN_DELAY_MS = 20;
45e532fb97ba883a488bb751d51732274e03a052afChris Craik    private static final long DEFAULT_DELAY_MS = 100;
46e532fb97ba883a488bb751d51732274e03a052afChris Craik
47a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final Object sLock = new Object();
48a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static HandlerThread sDecodingThread;
49a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static Handler sDecodingThreadHandler;
50a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static void initializeDecodingThread() {
51a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (sLock) {
52a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            if (sDecodingThread != null) return;
53a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
543105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            sDecodingThread = new HandlerThread("FrameSequence decoding thread",
553105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                    Process.THREAD_PRIORITY_BACKGROUND);
56a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            sDecodingThread.start();
57a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            sDecodingThreadHandler = new Handler(sDecodingThread.getLooper());
58a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
59a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
60a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
61e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static interface OnFinishedListener {
62e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        /**
63e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * Called when a FrameSequenceDrawable has finished looping.
64e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         *
65e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * Note that this is will not be called if the drawable is explicitly
66e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * stopped, or marked invisible.
67e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         */
68e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        public abstract void onFinished(FrameSequenceDrawable drawable);
69e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
70e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
713105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    public static interface BitmapProvider {
723105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        /**
733105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         * Called by FrameSequenceDrawable to aquire an 8888 Bitmap with minimum dimensions.
743105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         */
753105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        public abstract Bitmap acquireBitmap(int minWidth, int minHeight);
763105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
773105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        /**
783105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         * Called by FrameSequenceDrawable to release a Bitmap it no longer needs. The Bitmap
793105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         * will no longer be used at all by the drawable, so it is safe to reuse elsewhere.
804eb541aff092a057b27b917f09d33aba226dffedChris Craik         *
814eb541aff092a057b27b917f09d33aba226dffedChris Craik         * This method may be called by FrameSequenceDrawable on any thread.
823105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik         */
833105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        public abstract void releaseBitmap(Bitmap bitmap);
843105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
853105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
863105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    private static BitmapProvider sAllocatingBitmapProvider = new BitmapProvider() {
873105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        @Override
883105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        public Bitmap acquireBitmap(int minWidth, int minHeight) {
893105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            return Bitmap.createBitmap(minWidth, minHeight, Bitmap.Config.ARGB_8888);
903105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
913105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
923105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        @Override
93b19ecfd9cfa2c417034403adf7039edf9fe59327Chris Craik        public void releaseBitmap(Bitmap bitmap) {}
943105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    };
953105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
96e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
97e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Register a callback to be invoked when a FrameSequenceDrawable finishes looping.
98e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     *
993105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     * @see #setLoopBehavior(int)
100e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
101e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
102e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        mOnFinishedListener = onFinishedListener;
103e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
104e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
105e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
1063a208a6c6340cc636ed22919973264675da5eab0Chris Craik     * Loop a finite number of times, which can be set using setLoopCount. Default to loop once.
107e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
1083a208a6c6340cc636ed22919973264675da5eab0Chris Craik    public static final int LOOP_FINITE = 1;
109e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
110e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
111e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Loop continuously. The OnFinishedListener will never be called.
112e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
113e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_INF = 2;
114e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
115e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
116e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Use loop count stored in source data, or LOOP_ONCE if not present.
117e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
118e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_DEFAULT = 3;
119e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
120e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
1213a208a6c6340cc636ed22919973264675da5eab0Chris Craik     * Loop only once.
1223a208a6c6340cc636ed22919973264675da5eab0Chris Craik     *
1233a208a6c6340cc636ed22919973264675da5eab0Chris Craik     * @deprecated Use LOOP_FINITE instead.
1243a208a6c6340cc636ed22919973264675da5eab0Chris Craik     */
1253a208a6c6340cc636ed22919973264675da5eab0Chris Craik    @Deprecated
1263a208a6c6340cc636ed22919973264675da5eab0Chris Craik    public static final int LOOP_ONCE = LOOP_FINITE;
1273a208a6c6340cc636ed22919973264675da5eab0Chris Craik
1283a208a6c6340cc636ed22919973264675da5eab0Chris Craik    /**
129e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Define looping behavior of frame sequence.
130e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     *
1313a208a6c6340cc636ed22919973264675da5eab0Chris Craik     * Must be one of LOOP_ONCE, LOOP_INF, LOOP_DEFAULT, or LOOP_FINITE.
132e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
133e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public void setLoopBehavior(int loopBehavior) {
134e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        mLoopBehavior = loopBehavior;
135e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
136e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
1373a208a6c6340cc636ed22919973264675da5eab0Chris Craik    /**
1383a208a6c6340cc636ed22919973264675da5eab0Chris Craik     * Set the number of loops in LOOP_FINITE mode. The number must be a postive integer.
1393a208a6c6340cc636ed22919973264675da5eab0Chris Craik     */
1403a208a6c6340cc636ed22919973264675da5eab0Chris Craik    public void setLoopCount(int loopCount) {
1413a208a6c6340cc636ed22919973264675da5eab0Chris Craik        mLoopCount = loopCount;
1423a208a6c6340cc636ed22919973264675da5eab0Chris Craik    }
1433a208a6c6340cc636ed22919973264675da5eab0Chris Craik
144a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final FrameSequence mFrameSequence;
145a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final FrameSequence.State mFrameSequenceState;
146a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
147a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final Paint mPaint;
148537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    private BitmapShader mFrontBitmapShader;
149537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    private BitmapShader mBackBitmapShader;
150958761ceb97982b510d3059d31ba8c45700a1654Chris Craik    private final Rect mSrcRect;
151537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    private boolean mCircleMaskEnabled;
152a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
153a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    //Protects the fields below
154a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final Object mLock = new Object();
155a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
1564eb541aff092a057b27b917f09d33aba226dffedChris Craik    private final BitmapProvider mBitmapProvider;
1574eb541aff092a057b27b917f09d33aba226dffedChris Craik    private boolean mDestroyed = false;
158a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Bitmap mFrontBitmap;
159a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Bitmap mBackBitmap;
160a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
161a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_SCHEDULED = 1;
162a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_DECODING = 2;
163a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_WAITING_TO_SWAP = 3;
164a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_READY_TO_SWAP = 4;
165a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
166a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private int mState;
167e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private int mCurrentLoop;
168e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private int mLoopBehavior = LOOP_DEFAULT;
1693a208a6c6340cc636ed22919973264675da5eab0Chris Craik    private int mLoopCount = 1;
170a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
171a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private long mLastSwap;
1723105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    private long mNextSwap;
173a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private int mNextFrameToDecode;
174e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private OnFinishedListener mOnFinishedListener;
175a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
176958761ceb97982b510d3059d31ba8c45700a1654Chris Craik    private RectF mTempRectF = new RectF();
177958761ceb97982b510d3059d31ba8c45700a1654Chris Craik
178a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    /**
179a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik     * Runs on decoding thread, only modifies mBackBitmap's pixels
180a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik     */
181a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Runnable mDecodeRunnable = new Runnable() {
182a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        @Override
183a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        public void run() {
184a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            int nextFrame;
185a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            Bitmap bitmap;
186a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
1874eb541aff092a057b27b917f09d33aba226dffedChris Craik                if (mDestroyed) return;
1883105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
189a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                nextFrame = mNextFrameToDecode;
190a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                if (nextFrame < 0) {
191a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                    return;
192a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                }
193a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                bitmap = mBackBitmap;
194a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mState = STATE_DECODING;
195a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
196a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            int lastFrame = nextFrame - 2;
197769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik            boolean exceptionDuringDecode = false;
198769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik            long invalidateTimeMs = 0;
199769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik            try {
200769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik                invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
201769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik            } catch(Exception e) {
202769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik                // Exception during decode: continue, but delay next frame indefinitely.
203769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik                Log.e(TAG, "exception during decode: " + e);
204769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik                exceptionDuringDecode = true;
205769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik            }
206537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
207e532fb97ba883a488bb751d51732274e03a052afChris Craik            if (invalidateTimeMs < MIN_DELAY_MS) {
208e532fb97ba883a488bb751d51732274e03a052afChris Craik                invalidateTimeMs = DEFAULT_DELAY_MS;
209e532fb97ba883a488bb751d51732274e03a052afChris Craik            }
210a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
21138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            boolean schedule = false;
21238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            Bitmap bitmapToRelease = null;
213a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
21438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                if (mDestroyed) {
21538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    bitmapToRelease = mBackBitmap;
21638f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    mBackBitmap = null;
21738f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                } else if (mNextFrameToDecode >= 0 && mState == STATE_DECODING) {
21838f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    schedule = true;
219769d2c1df936a328f324a953f3cdf5901dd99078Chris Craik                    mNextSwap = exceptionDuringDecode ? Long.MAX_VALUE : invalidateTimeMs + mLastSwap;
22038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                    mState = STATE_WAITING_TO_SWAP;
22138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                }
22238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
22338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (schedule) {
22438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                scheduleSelf(FrameSequenceDrawable.this, mNextSwap);
22538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
22638f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (bitmapToRelease != null) {
22738f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                // destroy the bitmap here, since there's no safe way to get back to
22838f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                // drawable thread - drawable is likely detached, so schedule is noop.
22938f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                mBitmapProvider.releaseBitmap(bitmapToRelease);
230a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
231a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
232a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    };
233a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
2341527ca54acf175853ba6eb3b1e0a3a674789e209Chris Craik    private Runnable mFinishedCallbackRunnable = new Runnable() {
235e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        @Override
236e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        public void run() {
2371527ca54acf175853ba6eb3b1e0a3a674789e209Chris Craik            synchronized (mLock) {
2381527ca54acf175853ba6eb3b1e0a3a674789e209Chris Craik                mNextFrameToDecode = -1;
2391527ca54acf175853ba6eb3b1e0a3a674789e209Chris Craik                mState = 0;
2401527ca54acf175853ba6eb3b1e0a3a674789e209Chris Craik            }
241e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik            if (mOnFinishedListener != null) {
242e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                mOnFinishedListener.onFinished(FrameSequenceDrawable.this);
243e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik            }
244e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        }
245e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    };
246a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
2473105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    private static Bitmap acquireAndValidateBitmap(BitmapProvider bitmapProvider,
2483105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            int minWidth, int minHeight) {
2493105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        Bitmap bitmap = bitmapProvider.acquireBitmap(minWidth, minHeight);
2503105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2513105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        if (bitmap.getWidth() < minWidth
2523105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                || bitmap.getHeight() < minHeight
2533105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                || bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
2543105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            throw new IllegalArgumentException("Invalid bitmap provided");
2553105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
2563105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2573105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        return bitmap;
2583105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
2593105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
260a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public FrameSequenceDrawable(FrameSequence frameSequence) {
2613105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        this(frameSequence, sAllocatingBitmapProvider);
2623105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
2633105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
2643105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    public FrameSequenceDrawable(FrameSequence frameSequence, BitmapProvider bitmapProvider) {
2654eb541aff092a057b27b917f09d33aba226dffedChris Craik        if (frameSequence == null || bitmapProvider == null) throw new IllegalArgumentException();
266a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
267a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequence = frameSequence;
268a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequenceState = frameSequence.createState();
269a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        final int width = frameSequence.getWidth();
270a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        final int height = frameSequence.getHeight();
271a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
2724eb541aff092a057b27b917f09d33aba226dffedChris Craik        mBitmapProvider = bitmapProvider;
2733105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        mFrontBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
2743105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        mBackBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
275a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mSrcRect = new Rect(0, 0, width, height);
276a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint = new Paint();
277a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setFilterBitmap(true);
278a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
279537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        mFrontBitmapShader
280537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            = new BitmapShader(mFrontBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
281537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        mBackBitmapShader
282537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            = new BitmapShader(mBackBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
283537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
284a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mLastSwap = 0;
285a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
286a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mNextFrameToDecode = -1;
287a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
288a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        initializeDecodingThread();
289a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
290a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
291537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    /**
292537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     * Pass true to mask the shape of the animated drawing content to a circle.
293537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     *
294537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     * <p> The masking circle will be the largest circle contained in the Drawable's bounds.
295537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     * Masking is done with BitmapShader, incurring minimal additional draw cost.
296537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik     */
297537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    public final void setCircleMaskEnabled(boolean circleMaskEnabled) {
298958761ceb97982b510d3059d31ba8c45700a1654Chris Craik        if (mCircleMaskEnabled != circleMaskEnabled) {
299958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            mCircleMaskEnabled = circleMaskEnabled;
300958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            // Anti alias only necessary when using circular mask
301958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            mPaint.setAntiAlias(circleMaskEnabled);
302958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            invalidateSelf();
303958761ceb97982b510d3059d31ba8c45700a1654Chris Craik        }
304958761ceb97982b510d3059d31ba8c45700a1654Chris Craik    }
305958761ceb97982b510d3059d31ba8c45700a1654Chris Craik
306958761ceb97982b510d3059d31ba8c45700a1654Chris Craik    public final boolean getCircleMaskEnabled() {
307958761ceb97982b510d3059d31ba8c45700a1654Chris Craik        return mCircleMaskEnabled;
308537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik    }
309537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
3104eb541aff092a057b27b917f09d33aba226dffedChris Craik    private void checkDestroyedLocked() {
3114eb541aff092a057b27b917f09d33aba226dffedChris Craik        if (mDestroyed) {
3123105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            throw new IllegalStateException("Cannot perform operation on recycled drawable");
3133105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
3143105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
3153105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
3164eb541aff092a057b27b917f09d33aba226dffedChris Craik    public boolean isDestroyed() {
3173105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        synchronized (mLock) {
3184eb541aff092a057b27b917f09d33aba226dffedChris Craik            return mDestroyed;
3193105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
3203105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
3213105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
3223105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    /**
3233105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     * Marks the drawable as permanently recycled (and thus unusable), and releases any owned
3244eb541aff092a057b27b917f09d33aba226dffedChris Craik     * Bitmaps drawable to its BitmapProvider, if attached.
3253105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     *
3264eb541aff092a057b27b917f09d33aba226dffedChris Craik     * If no BitmapProvider is attached to the drawable, recycle() is called on the Bitmaps.
3273105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik     */
3284eb541aff092a057b27b917f09d33aba226dffedChris Craik    public void destroy() {
32938f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        if (mBitmapProvider == null) {
3303105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            throw new IllegalStateException("BitmapProvider must be non-null");
3313105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
3323105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
3333105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        Bitmap bitmapToReleaseA;
33438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        Bitmap bitmapToReleaseB = null;
3353105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        synchronized (mLock) {
3364eb541aff092a057b27b917f09d33aba226dffedChris Craik            checkDestroyedLocked();
3373105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
3383105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            bitmapToReleaseA = mFrontBitmap;
3393105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            mFrontBitmap = null;
34038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik
34138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (mState != STATE_DECODING) {
34238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                bitmapToReleaseB = mBackBitmap;
34338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                mBackBitmap = null;
34438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
34538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik
3464eb541aff092a057b27b917f09d33aba226dffedChris Craik            mDestroyed = true;
3473105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik        }
3483105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
3494eb541aff092a057b27b917f09d33aba226dffedChris Craik        // For simplicity and safety, we don't destroy the state object here
35038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        mBitmapProvider.releaseBitmap(bitmapToReleaseA);
35138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        if (bitmapToReleaseB != null) {
35238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            mBitmapProvider.releaseBitmap(bitmapToReleaseB);
35338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        }
3543105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik    }
3553105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
356a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
357a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    protected void finalize() throws Throwable {
358a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        try {
3594eb541aff092a057b27b917f09d33aba226dffedChris Craik            mFrameSequenceState.destroy();
360a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        } finally {
361a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            super.finalize();
362a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
363a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
364a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
365a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
366a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void draw(Canvas canvas) {
367a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
3684eb541aff092a057b27b917f09d33aba226dffedChris Craik            checkDestroyedLocked();
3693105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            if (mState == STATE_WAITING_TO_SWAP) {
3703105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                // may have failed to schedule mark ready runnable,
3713105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                // so go ahead and swap if swapping is due
3723105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                if (mNextSwap - SystemClock.uptimeMillis() <= 0) {
3733105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                    mState = STATE_READY_TO_SWAP;
3743105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik                }
3753105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik            }
3763105099a73d4fea3408ea0cf6b358fff77dc8b67Chris Craik
377a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            if (isRunning() && mState == STATE_READY_TO_SWAP) {
378a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                // Because draw has occurred, the view system is guaranteed to no longer hold a
379a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                // reference to the old mFrontBitmap, so we now use it to produce the next frame
380a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                Bitmap tmp = mBackBitmap;
381a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mBackBitmap = mFrontBitmap;
382a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mFrontBitmap = tmp;
383a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
384537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik                BitmapShader tmpShader = mBackBitmapShader;
385537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik                mBackBitmapShader = mFrontBitmapShader;
386537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik                mFrontBitmapShader = tmpShader;
387537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik
388a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mLastSwap = SystemClock.uptimeMillis();
389e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
390e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                boolean continueLooping = true;
391e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
392e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    mCurrentLoop++;
393b23d7abde766b0575d6a2592ea828d2d8596ecd4Chris Craik                    if ((mLoopBehavior == LOOP_FINITE && mCurrentLoop == mLoopCount) ||
394b23d7abde766b0575d6a2592ea828d2d8596ecd4Chris Craik                            (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
395e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                        continueLooping = false;
396e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    }
397e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                }
398e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
399e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                if (continueLooping) {
400e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    scheduleDecodeLocked();
401e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                } else {
4021527ca54acf175853ba6eb3b1e0a3a674789e209Chris Craik                    scheduleSelf(mFinishedCallbackRunnable, 0);
403e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                }
404a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
405a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
406a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
407537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        if (mCircleMaskEnabled) {
408958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final Rect bounds = getBounds();
409958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final int bitmapWidth = getIntrinsicWidth();
410958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final int bitmapHeight = getIntrinsicHeight();
411958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final float scaleX = 1.0f * bounds.width() / bitmapWidth;
412958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final float scaleY = 1.0f * bounds.height() / bitmapHeight;
413958761ceb97982b510d3059d31ba8c45700a1654Chris Craik
414958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            canvas.save();
415958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            // scale and translate to account for bounds, so we can operate in intrinsic
416958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            // width/height (so it's valid to use an unscaled bitmap shader)
417958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            canvas.translate(bounds.left, bounds.top);
418958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            canvas.scale(scaleX, scaleY);
419958761ceb97982b510d3059d31ba8c45700a1654Chris Craik
420958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final float unscaledCircleDiameter = Math.min(bounds.width(), bounds.height());
421958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final float scaledDiameterX = unscaledCircleDiameter / scaleX;
422958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            final float scaledDiameterY = unscaledCircleDiameter / scaleY;
423958761ceb97982b510d3059d31ba8c45700a1654Chris Craik
424958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            // Want to draw a circle, but we have to compensate for canvas scale
425958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            mTempRectF.set(
426958761ceb97982b510d3059d31ba8c45700a1654Chris Craik                    (bitmapWidth - scaledDiameterX) / 2.0f,
427958761ceb97982b510d3059d31ba8c45700a1654Chris Craik                    (bitmapHeight - scaledDiameterY) / 2.0f,
428958761ceb97982b510d3059d31ba8c45700a1654Chris Craik                    (bitmapWidth + scaledDiameterX) / 2.0f,
429958761ceb97982b510d3059d31ba8c45700a1654Chris Craik                    (bitmapHeight + scaledDiameterY) / 2.0f);
430537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            mPaint.setShader(mFrontBitmapShader);
431958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            canvas.drawOval(mTempRectF, mPaint);
432958761ceb97982b510d3059d31ba8c45700a1654Chris Craik            canvas.restore();
433537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        } else {
434537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            mPaint.setShader(null);
435537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
436537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik        }
437a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
438a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
439a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private void scheduleDecodeLocked() {
440a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mState = STATE_SCHEDULED;
441a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
442a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        sDecodingThreadHandler.post(mDecodeRunnable);
443a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
444a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
445a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
446a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void run() {
44738f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        // set ready to swap as necessary
44838f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        boolean invalidate = false;
449a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
45038f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            if (mNextFrameToDecode >= 0 && mState == STATE_WAITING_TO_SWAP) {
45138f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                mState = STATE_READY_TO_SWAP;
45238f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik                invalidate = true;
45338f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            }
45438f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        }
45538f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik        if (invalidate) {
45638f3678fe9ee5e050af1587a525dadc40f526a51Chris Craik            invalidateSelf();
457a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
458a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
459a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
460a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
461a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void start() {
462a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (!isRunning()) {
463a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
4644eb541aff092a057b27b917f09d33aba226dffedChris Craik                checkDestroyedLocked();
465a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                if (mState == STATE_SCHEDULED) return; // already scheduled
466e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                mCurrentLoop = 0;
467a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                scheduleDecodeLocked();
468a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
469a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
470a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
471a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
472a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
473a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void stop() {
474a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (isRunning()) {
475a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            unscheduleSelf(this);
476a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
477a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
478a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
479a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
480a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public boolean isRunning() {
481a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
4824eb541aff092a057b27b917f09d33aba226dffedChris Craik            return mNextFrameToDecode > -1 && !mDestroyed;
483a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
484a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
485a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
486a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
487a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void unscheduleSelf(Runnable what) {
488a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
489a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            mNextFrameToDecode = -1;
490537754df0d7348949e2324a91cc9e4df7571d7bfChris Craik            mState = 0;
491a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
492a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        super.unscheduleSelf(what);
493a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
494a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
495a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
496a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public boolean setVisible(boolean visible, boolean restart) {
497a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        boolean changed = super.setVisible(visible, restart);
498a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
499a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (!visible) {
500a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            stop();
501a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        } else if (restart || changed) {
502a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            stop();
503a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            start();
504a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
505a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
506a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return changed;
507a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
508a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
509a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    // drawing properties
510a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
511a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
512a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setFilterBitmap(boolean filter) {
513a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setFilterBitmap(filter);
514a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
515a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
516a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
517a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setAlpha(int alpha) {
518a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setAlpha(alpha);
519a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
520a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
521a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
522a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setColorFilter(ColorFilter colorFilter) {
523a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setColorFilter(colorFilter);
524a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
525a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
526a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
527a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getIntrinsicWidth() {
528a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.getWidth();
529a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
530a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
531a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
532a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getIntrinsicHeight() {
533a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.getHeight();
534a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
535a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
536a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
537a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getOpacity() {
538a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
539a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
540a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik}
541