FrameSequenceDrawable.java revision e36c5d675c8c2f900ef186a55edf71ce36ca9fa0
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;
20a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.Canvas;
21a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.ColorFilter;
22a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.Paint;
23a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.PixelFormat;
24a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.Rect;
25a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.drawable.Animatable;
26a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.graphics.drawable.Drawable;
27a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.os.Handler;
28a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.os.HandlerThread;
29a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikimport android.os.SystemClock;
30a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
31a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craikpublic class FrameSequenceDrawable extends Drawable implements Animatable, Runnable {
32a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final Object sLock = new Object();
33a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static HandlerThread sDecodingThread;
34a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static Handler sDecodingThreadHandler;
35a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static void initializeDecodingThread() {
36a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (sLock) {
37a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            if (sDecodingThread != null) return;
38a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
39a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            sDecodingThread = new HandlerThread("FrameSequence decoding thread");
40a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            sDecodingThread.start();
41a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            sDecodingThreadHandler = new Handler(sDecodingThread.getLooper());
42a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
43a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
44a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
45e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static interface OnFinishedListener {
46e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        /**
47e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * Called when a FrameSequenceDrawable has finished looping.
48e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         *
49e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * Note that this is will not be called if the drawable is explicitly
50e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         * stopped, or marked invisible.
51e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik         */
52e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        public abstract void onFinished(FrameSequenceDrawable drawable);
53e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
54e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
55e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
56e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Register a callback to be invoked when a FrameSequenceDrawable finishes looping.
57e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     *
58e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * @see setLoopBehavior()
59e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
60e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
61e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        mOnFinishedListener = onFinishedListener;
62e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
63e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
64e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
65e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Loop only once.
66e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
67e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_ONCE = 1;
68e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
69e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
70e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Loop continuously. The OnFinishedListener will never be called.
71e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
72e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_INF = 2;
73e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
74e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
75e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Use loop count stored in source data, or LOOP_ONCE if not present.
76e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
77e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public static final int LOOP_DEFAULT = 3;
78e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
79e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    /**
80e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Define looping behavior of frame sequence.
81e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     *
82e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     * Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT
83e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik     */
84e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    public void setLoopBehavior(int loopBehavior) {
85e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        mLoopBehavior = loopBehavior;
86e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    }
87e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
88a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final FrameSequence mFrameSequence;
89a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final FrameSequence.State mFrameSequenceState;
90a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
91a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final Paint mPaint;
92a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final Rect mSrcRect;
93a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
94a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    //Protects the fields below
95a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private final Object mLock = new Object();
96a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
97a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Bitmap mFrontBitmap;
98a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Bitmap mBackBitmap;
99a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
100a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_SCHEDULED = 1;
101a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_DECODING = 2;
102a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_WAITING_TO_SWAP = 3;
103a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private static final int STATE_READY_TO_SWAP = 4;
104a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
105a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private int mState;
106e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private int mCurrentLoop;
107e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private int mLoopBehavior = LOOP_DEFAULT;
108a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
109a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private long mLastSwap;
110a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private int mNextFrameToDecode;
111e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private OnFinishedListener mOnFinishedListener;
112a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
113a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    /**
114a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik     * Runs on decoding thread, only modifies mBackBitmap's pixels
115a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik     */
116a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private Runnable mDecodeRunnable = new Runnable() {
117a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        @Override
118a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        public void run() {
119a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            int nextFrame;
120a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            Bitmap bitmap;
121a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
122a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                nextFrame = mNextFrameToDecode;
123a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                if (nextFrame < 0) {
124a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                    return;
125a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                }
126a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                bitmap = mBackBitmap;
127a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mState = STATE_DECODING;
128a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
129a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            int lastFrame = nextFrame - 2;
130a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
131a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
132a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
133a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return;
134a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                invalidateTimeMs += mLastSwap;
135a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
136a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mState = STATE_WAITING_TO_SWAP;
137a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
138a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            scheduleSelf(FrameSequenceDrawable.this, invalidateTimeMs);
139a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
140a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    };
141a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
142e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    private Runnable mCallbackRunnable = new Runnable() {
143e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        @Override
144e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        public void run() {
145e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik            if (mOnFinishedListener != null) {
146e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                mOnFinishedListener.onFinished(FrameSequenceDrawable.this);
147e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik            }
148e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik        }
149e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik    };
150a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
151a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public FrameSequenceDrawable(FrameSequence frameSequence) {
152a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (frameSequence == null) throw new IllegalArgumentException();
153a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
154a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequence = frameSequence;
155a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequenceState = frameSequence.createState();
156a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        // TODO: add callback for requesting bitmaps, to allow for reuse
157a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        final int width = frameSequence.getWidth();
158a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        final int height = frameSequence.getHeight();
159a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
160a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrontBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
161a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mBackBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
162a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mSrcRect = new Rect(0, 0, width, height);
163a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint = new Paint();
164a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setFilterBitmap(true);
165a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
166a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mLastSwap = 0;
167a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
168a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mNextFrameToDecode = -1;
169a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
170a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        initializeDecodingThread();
171a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
172a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
173a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
174a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    protected void finalize() throws Throwable {
175a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        try {
176a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            mFrontBitmap.recycle();
177a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            mBackBitmap.recycle();
178a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            mFrameSequenceState.recycle();
179a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        } finally {
180a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            super.finalize();
181a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
182a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
183a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
184a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
185a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void draw(Canvas canvas) {
186a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
187a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            if (isRunning() && mState == STATE_READY_TO_SWAP) {
188a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                // Because draw has occurred, the view system is guaranteed to no longer hold a
189a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                // reference to the old mFrontBitmap, so we now use it to produce the next frame
190a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                Bitmap tmp = mBackBitmap;
191a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mBackBitmap = mFrontBitmap;
192a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mFrontBitmap = tmp;
193a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
194a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                mLastSwap = SystemClock.uptimeMillis();
195e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
196e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                boolean continueLooping = true;
197e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
198e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    mCurrentLoop++;
199e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) ||
200e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                        (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
201e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                        continueLooping = false;
202e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    }
203e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                }
204e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik
205e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                if (continueLooping) {
206e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    scheduleDecodeLocked();
207e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                } else {
208e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                    scheduleSelf(mCallbackRunnable, 0);
209e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                }
210a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
211a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
212a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
213a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
214a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
215a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
216a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    private void scheduleDecodeLocked() {
217a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mState = STATE_SCHEDULED;
218a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
219a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        sDecodingThreadHandler.post(mDecodeRunnable);
220a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
221a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
222a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
223a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void run() {
224a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        // set ready to swap
225a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
226a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return;
227a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            mState = STATE_READY_TO_SWAP;
228a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
229a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        invalidateSelf();
230a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
231a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
232a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
233a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void start() {
234a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (!isRunning()) {
235a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            synchronized (mLock) {
236a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                if (mState == STATE_SCHEDULED) return; // already scheduled
237e36c5d675c8c2f900ef186a55edf71ce36ca9fa0Chris Craik                mCurrentLoop = 0;
238a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik                scheduleDecodeLocked();
239a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            }
240a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
241a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
242a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
243a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
244a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void stop() {
245a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (isRunning()) {
246a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            unscheduleSelf(this);
247a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
248a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
249a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
250a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
251a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public boolean isRunning() {
252a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
253a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            return mNextFrameToDecode > -1;
254a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
255a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
256a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
257a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
258a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void scheduleSelf(Runnable what, long when) {
259a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        super.scheduleSelf(what, when);
260a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
261a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
262a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
263a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void unscheduleSelf(Runnable what) {
264a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        synchronized (mLock) {
265a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            mNextFrameToDecode = -1;
266a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
267a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        super.unscheduleSelf(what);
268a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
269a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
270a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
271a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public boolean setVisible(boolean visible, boolean restart) {
272a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        boolean changed = super.setVisible(visible, restart);
273a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
274a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        if (!visible) {
275a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            stop();
276a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        } else if (restart || changed) {
277a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            stop();
278a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik            start();
279a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        }
280a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
281a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return changed;
282a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
283a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
284a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    // drawing properties
285a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
286a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
287a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setFilterBitmap(boolean filter) {
288a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setFilterBitmap(filter);
289a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
290a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
291a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
292a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setAlpha(int alpha) {
293a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setAlpha(alpha);
294a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
295a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
296a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
297a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public void setColorFilter(ColorFilter colorFilter) {
298a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        mPaint.setColorFilter(colorFilter);
299a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
300a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
301a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
302a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getIntrinsicWidth() {
303a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.getWidth();
304a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
305a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
306a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
307a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getIntrinsicHeight() {
308a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.getHeight();
309a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
310a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik
311a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    @Override
312a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    public int getOpacity() {
313a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik        return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
314a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik    }
315a3ac0a2df64dcfb8b0b01f1cf05e9afd1439e1f4Chris Craik}
316