Add setCircularMaskEnabled

Also fixes a bug where unscheduling the drawable doesn't reset the
state, potentially leading to situations where it has been unscheduled
but its state is still SCHEDULED

Change-Id: Iec872eda5ccc5c60fb8215c406cad430513168e4
diff --git a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
index 5158bc0..c4ce0ce 100644
--- a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
+++ b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
@@ -17,11 +17,14 @@
 package android.support.rastermill;
 
 import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Shader;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -125,7 +128,10 @@
     private final FrameSequence.State mFrameSequenceState;
 
     private final Paint mPaint;
-    private final Rect mSrcRect;
+    private BitmapShader mFrontBitmapShader;
+    private BitmapShader mBackBitmapShader;
+     private final Rect mSrcRect;
+    private boolean mCircleMaskEnabled;
 
     //Protects the fields below
     private final Object mLock = new Object();
@@ -169,6 +175,7 @@
             }
             int lastFrame = nextFrame - 2;
             long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
+
             if (invalidateTimeMs < MIN_DELAY_MS) {
                 invalidateTimeMs = DEFAULT_DELAY_MS;
             }
@@ -237,6 +244,11 @@
         mPaint = new Paint();
         mPaint.setFilterBitmap(true);
 
+        mFrontBitmapShader
+            = new BitmapShader(mFrontBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+        mBackBitmapShader
+            = new BitmapShader(mBackBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+
         mLastSwap = 0;
 
         mNextFrameToDecode = -1;
@@ -244,6 +256,18 @@
         initializeDecodingThread();
     }
 
+    /**
+     * Pass true to mask the shape of the animated drawing content to a circle.
+     *
+     * <p> The masking circle will be the largest circle contained in the Drawable's bounds.
+     * Masking is done with BitmapShader, incurring minimal additional draw cost.
+     */
+    public final void setCircleMaskEnabled(boolean circleMaskEnabled) {
+        mCircleMaskEnabled = circleMaskEnabled;
+        // Anti alias only necessary when using circular mask
+        mPaint.setAntiAlias(circleMaskEnabled);
+    }
+
     private void checkDestroyedLocked() {
         if (mDestroyed) {
             throw new IllegalStateException("Cannot perform operation on recycled drawable");
@@ -318,6 +342,10 @@
                 mBackBitmap = mFrontBitmap;
                 mFrontBitmap = tmp;
 
+                BitmapShader tmpShader = mBackBitmapShader;
+                mBackBitmapShader = mFrontBitmapShader;
+                mFrontBitmapShader = tmpShader;
+
                 mLastSwap = SystemClock.uptimeMillis();
 
                 boolean continueLooping = true;
@@ -337,7 +365,17 @@
             }
         }
 
-        canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
+        if (mCircleMaskEnabled) {
+            Rect bounds = getBounds();
+            mPaint.setShader(mFrontBitmapShader);
+            float width = bounds.width();
+            float height = bounds.height();
+            float circleRadius = (Math.min(width, height)) / 2f;
+            canvas.drawCircle(width / 2f, height / 2f, circleRadius, mPaint);
+        } else {
+            mPaint.setShader(null);
+            canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
+        }
     }
 
     private void scheduleDecodeLocked() {
@@ -391,6 +429,7 @@
     public void unscheduleSelf(Runnable what) {
         synchronized (mLock) {
             mNextFrameToDecode = -1;
+            mState = 0;
         }
         super.unscheduleSelf(what);
     }