Home | History | Annotate | Download | only in rastermill
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.support.rastermill;
     18 
     19 import android.graphics.Bitmap;
     20 import android.graphics.Canvas;
     21 import android.graphics.ColorFilter;
     22 import android.graphics.Paint;
     23 import android.graphics.PixelFormat;
     24 import android.graphics.Rect;
     25 import android.graphics.drawable.Animatable;
     26 import android.graphics.drawable.Drawable;
     27 import android.os.Handler;
     28 import android.os.HandlerThread;
     29 import android.os.Process;
     30 import android.os.SystemClock;
     31 
     32 public class FrameSequenceDrawable extends Drawable implements Animatable, Runnable {
     33     private static final Object sLock = new Object();
     34     private static HandlerThread sDecodingThread;
     35     private static Handler sDecodingThreadHandler;
     36     private static void initializeDecodingThread() {
     37         synchronized (sLock) {
     38             if (sDecodingThread != null) return;
     39 
     40             sDecodingThread = new HandlerThread("FrameSequence decoding thread",
     41                     Process.THREAD_PRIORITY_BACKGROUND);
     42             sDecodingThread.start();
     43             sDecodingThreadHandler = new Handler(sDecodingThread.getLooper());
     44         }
     45     }
     46 
     47     public static interface OnFinishedListener {
     48         /**
     49          * Called when a FrameSequenceDrawable has finished looping.
     50          *
     51          * Note that this is will not be called if the drawable is explicitly
     52          * stopped, or marked invisible.
     53          */
     54         public abstract void onFinished(FrameSequenceDrawable drawable);
     55     }
     56 
     57     public static interface BitmapProvider {
     58         /**
     59          * Called by FrameSequenceDrawable to aquire an 8888 Bitmap with minimum dimensions.
     60          */
     61         public abstract Bitmap acquireBitmap(int minWidth, int minHeight);
     62 
     63         /**
     64          * Called by FrameSequenceDrawable to release a Bitmap it no longer needs. The Bitmap
     65          * will no longer be used at all by the drawable, so it is safe to reuse elsewhere.
     66          *
     67          * This method may be called by FrameSequenceDrawable on any thread.
     68          */
     69         public abstract void releaseBitmap(Bitmap bitmap);
     70     }
     71 
     72     private static BitmapProvider sAllocatingBitmapProvider = new BitmapProvider() {
     73         @Override
     74         public Bitmap acquireBitmap(int minWidth, int minHeight) {
     75             return Bitmap.createBitmap(minWidth, minHeight, Bitmap.Config.ARGB_8888);
     76         }
     77 
     78         @Override
     79         public void releaseBitmap(Bitmap bitmap) {
     80             bitmap.recycle();
     81         }
     82     };
     83 
     84     /**
     85      * Register a callback to be invoked when a FrameSequenceDrawable finishes looping.
     86      *
     87      * @see #setLoopBehavior(int)
     88      */
     89     public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
     90         mOnFinishedListener = onFinishedListener;
     91     }
     92 
     93     /**
     94      * Loop only once.
     95      */
     96     public static final int LOOP_ONCE = 1;
     97 
     98     /**
     99      * Loop continuously. The OnFinishedListener will never be called.
    100      */
    101     public static final int LOOP_INF = 2;
    102 
    103     /**
    104      * Use loop count stored in source data, or LOOP_ONCE if not present.
    105      */
    106     public static final int LOOP_DEFAULT = 3;
    107 
    108     /**
    109      * Define looping behavior of frame sequence.
    110      *
    111      * Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT
    112      */
    113     public void setLoopBehavior(int loopBehavior) {
    114         mLoopBehavior = loopBehavior;
    115     }
    116 
    117     private final FrameSequence mFrameSequence;
    118     private final FrameSequence.State mFrameSequenceState;
    119 
    120     private final Paint mPaint;
    121     private final Rect mSrcRect;
    122 
    123     //Protects the fields below
    124     private final Object mLock = new Object();
    125 
    126     private final BitmapProvider mBitmapProvider;
    127     private boolean mDestroyed = false;
    128     private Bitmap mFrontBitmap;
    129     private Bitmap mBackBitmap;
    130 
    131     private static final int STATE_SCHEDULED = 1;
    132     private static final int STATE_DECODING = 2;
    133     private static final int STATE_WAITING_TO_SWAP = 3;
    134     private static final int STATE_READY_TO_SWAP = 4;
    135 
    136     private int mState;
    137     private int mCurrentLoop;
    138     private int mLoopBehavior = LOOP_DEFAULT;
    139 
    140     private long mLastSwap;
    141     private long mNextSwap;
    142     private int mNextFrameToDecode;
    143     private OnFinishedListener mOnFinishedListener;
    144 
    145     /**
    146      * Runs on decoding thread, only modifies mBackBitmap's pixels
    147      */
    148     private Runnable mDecodeRunnable = new Runnable() {
    149         @Override
    150         public void run() {
    151             int nextFrame;
    152             Bitmap bitmap;
    153             synchronized (mLock) {
    154                 if (mDestroyed) return;
    155 
    156                 nextFrame = mNextFrameToDecode;
    157                 if (nextFrame < 0) {
    158                     return;
    159                 }
    160                 bitmap = mBackBitmap;
    161                 mState = STATE_DECODING;
    162             }
    163             int lastFrame = nextFrame - 2;
    164             long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
    165 
    166             synchronized (mLock) {
    167                 if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return;
    168                 mNextSwap = invalidateTimeMs + mLastSwap;
    169 
    170                 mState = STATE_WAITING_TO_SWAP;
    171             }
    172             scheduleSelf(FrameSequenceDrawable.this, mNextSwap);
    173         }
    174     };
    175 
    176     private Runnable mCallbackRunnable = new Runnable() {
    177         @Override
    178         public void run() {
    179             if (mOnFinishedListener != null) {
    180                 mOnFinishedListener.onFinished(FrameSequenceDrawable.this);
    181             }
    182         }
    183     };
    184 
    185     private static Bitmap acquireAndValidateBitmap(BitmapProvider bitmapProvider,
    186             int minWidth, int minHeight) {
    187         Bitmap bitmap = bitmapProvider.acquireBitmap(minWidth, minHeight);
    188 
    189         if (bitmap.getWidth() < minWidth
    190                 || bitmap.getHeight() < minHeight
    191                 || bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
    192             throw new IllegalArgumentException("Invalid bitmap provided");
    193         }
    194 
    195         return bitmap;
    196     }
    197 
    198     public FrameSequenceDrawable(FrameSequence frameSequence) {
    199         this(frameSequence, sAllocatingBitmapProvider);
    200     }
    201 
    202     public FrameSequenceDrawable(FrameSequence frameSequence, BitmapProvider bitmapProvider) {
    203         if (frameSequence == null || bitmapProvider == null) throw new IllegalArgumentException();
    204 
    205         mFrameSequence = frameSequence;
    206         mFrameSequenceState = frameSequence.createState();
    207         final int width = frameSequence.getWidth();
    208         final int height = frameSequence.getHeight();
    209 
    210         mBitmapProvider = bitmapProvider;
    211         mFrontBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
    212         mBackBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
    213         mSrcRect = new Rect(0, 0, width, height);
    214         mPaint = new Paint();
    215         mPaint.setFilterBitmap(true);
    216 
    217         mLastSwap = 0;
    218 
    219         mNextFrameToDecode = -1;
    220         mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
    221         initializeDecodingThread();
    222     }
    223 
    224     private void checkDestroyedLocked() {
    225         if (mDestroyed) {
    226             throw new IllegalStateException("Cannot perform operation on recycled drawable");
    227         }
    228     }
    229 
    230     public boolean isDestroyed() {
    231         synchronized (mLock) {
    232             return mDestroyed;
    233         }
    234     }
    235 
    236     /**
    237      * Marks the drawable as permanently recycled (and thus unusable), and releases any owned
    238      * Bitmaps drawable to its BitmapProvider, if attached.
    239      *
    240      * If no BitmapProvider is attached to the drawable, recycle() is called on the Bitmaps.
    241      */
    242     public void destroy() {
    243         destroy(mBitmapProvider);
    244     }
    245 
    246     private void destroy(BitmapProvider bitmapProvider) {
    247         if (bitmapProvider == null) {
    248             throw new IllegalStateException("BitmapProvider must be non-null");
    249         }
    250 
    251         Bitmap bitmapToReleaseA;
    252         Bitmap bitmapToReleaseB;
    253         synchronized (mLock) {
    254             checkDestroyedLocked();
    255 
    256             bitmapToReleaseA = mFrontBitmap;
    257             bitmapToReleaseB = mBackBitmap;
    258 
    259             mFrontBitmap = null;
    260             mBackBitmap = null;
    261             mDestroyed = true;
    262         }
    263 
    264         // For simplicity and safety, we don't destroy the state object here
    265         bitmapProvider.releaseBitmap(bitmapToReleaseA);
    266         bitmapProvider.releaseBitmap(bitmapToReleaseB);
    267     }
    268 
    269     @Override
    270     protected void finalize() throws Throwable {
    271         try {
    272             mFrameSequenceState.destroy();
    273             if (!mDestroyed) {
    274                 destroy();
    275             }
    276         } finally {
    277             super.finalize();
    278         }
    279     }
    280 
    281     @Override
    282     public void draw(Canvas canvas) {
    283         synchronized (mLock) {
    284             checkDestroyedLocked();
    285             if (mState == STATE_WAITING_TO_SWAP) {
    286                 // may have failed to schedule mark ready runnable,
    287                 // so go ahead and swap if swapping is due
    288                 if (mNextSwap - SystemClock.uptimeMillis() <= 0) {
    289                     mState = STATE_READY_TO_SWAP;
    290                 }
    291             }
    292 
    293             if (isRunning() && mState == STATE_READY_TO_SWAP) {
    294                 // Because draw has occurred, the view system is guaranteed to no longer hold a
    295                 // reference to the old mFrontBitmap, so we now use it to produce the next frame
    296                 Bitmap tmp = mBackBitmap;
    297                 mBackBitmap = mFrontBitmap;
    298                 mFrontBitmap = tmp;
    299 
    300                 mLastSwap = SystemClock.uptimeMillis();
    301 
    302                 boolean continueLooping = true;
    303                 if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
    304                     mCurrentLoop++;
    305                     if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) ||
    306                             (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
    307                         continueLooping = false;
    308                     }
    309                 }
    310 
    311                 if (continueLooping) {
    312                     scheduleDecodeLocked();
    313                 } else {
    314                     scheduleSelf(mCallbackRunnable, 0);
    315                 }
    316             }
    317         }
    318 
    319         canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
    320     }
    321 
    322     private void scheduleDecodeLocked() {
    323         mState = STATE_SCHEDULED;
    324         mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
    325         sDecodingThreadHandler.post(mDecodeRunnable);
    326     }
    327 
    328     @Override
    329     public void run() {
    330         // set ready to swap
    331         synchronized (mLock) {
    332             if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return;
    333             mState = STATE_READY_TO_SWAP;
    334         }
    335         invalidateSelf();
    336     }
    337 
    338     @Override
    339     public void start() {
    340         if (!isRunning()) {
    341             synchronized (mLock) {
    342                 checkDestroyedLocked();
    343                 if (mState == STATE_SCHEDULED) return; // already scheduled
    344                 mCurrentLoop = 0;
    345                 scheduleDecodeLocked();
    346             }
    347         }
    348     }
    349 
    350     @Override
    351     public void stop() {
    352         if (isRunning()) {
    353             unscheduleSelf(this);
    354         }
    355     }
    356 
    357     @Override
    358     public boolean isRunning() {
    359         synchronized (mLock) {
    360             return mNextFrameToDecode > -1 && !mDestroyed;
    361         }
    362     }
    363 
    364     @Override
    365     public void unscheduleSelf(Runnable what) {
    366         synchronized (mLock) {
    367             mNextFrameToDecode = -1;
    368         }
    369         super.unscheduleSelf(what);
    370     }
    371 
    372     @Override
    373     public boolean setVisible(boolean visible, boolean restart) {
    374         boolean changed = super.setVisible(visible, restart);
    375 
    376         if (!visible) {
    377             stop();
    378         } else if (restart || changed) {
    379             stop();
    380             start();
    381         }
    382 
    383         return changed;
    384     }
    385 
    386     // drawing properties
    387 
    388     @Override
    389     public void setFilterBitmap(boolean filter) {
    390         mPaint.setFilterBitmap(filter);
    391     }
    392 
    393     @Override
    394     public void setAlpha(int alpha) {
    395         mPaint.setAlpha(alpha);
    396     }
    397 
    398     @Override
    399     public void setColorFilter(ColorFilter colorFilter) {
    400         mPaint.setColorFilter(colorFilter);
    401     }
    402 
    403     @Override
    404     public int getIntrinsicWidth() {
    405         return mFrameSequence.getWidth();
    406     }
    407 
    408     @Override
    409     public int getIntrinsicHeight() {
    410         return mFrameSequence.getHeight();
    411     }
    412 
    413     @Override
    414     public int getOpacity() {
    415         return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
    416     }
    417 }
    418