content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.bitmap.drawable;
  
  import android.content.res.Resources;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.Paint.Style;
  import android.graphics.Path;
  import android.graphics.Rect;
  import android.graphics.RectF;
  import android.util.Log;
  import android.view.View;
  
  import com.android.bitmap.BitmapCache;
  
  /**
   * A custom ExtendedBitmapDrawable that styles the corners in configurable ways.
   *
   * All four corners can be configured as {@link #CORNER_STYLE_SHARP},
   * {@link #CORNER_STYLE_ROUND}, or {@link #CORNER_STYLE_FLAP}.
   * This is accomplished applying a non-rectangular clip applied to the canvas.
   *
   * A border is draw that conforms to the styled corners.
   *
   * {@link #CORNER_STYLE_FLAP} corners have a colored flap drawn within the bounds.
   */
  public class StyledCornersBitmapDrawable extends ExtendedBitmapDrawable {
      private static final String TAG = StyledCornersBitmapDrawable.class.getSimpleName();
  
      public static final int CORNER_STYLE_SHARP = 0;
      public static final int CORNER_STYLE_ROUND = 1;
      public static final int CORNER_STYLE_FLAP = 2;
  
      private static final int START_RIGHT = 0;
      private static final int START_BOTTOM = 90;
      private static final int START_LEFT = 180;
      private static final int START_TOP = 270;
      private static final int QUARTER_CIRCLE = 90;
      private static final RectF sRectF = new RectF();
  
      private final Paint mFlapPaint = new Paint();
      private final Paint mBorderPaint = new Paint();
      private final Paint mCompatibilityModeBackgroundPaint = new Paint();
      private final Path mClipPath = new Path();
      private final Path mCompatibilityModePath = new Path();
      private final float mCornerRoundRadius;
      private final float mCornerFlapSide;
  
      private int mTopLeftCornerStyle = CORNER_STYLE_SHARP;
      private int mTopRightCornerStyle = CORNER_STYLE_SHARP;
      private int mBottomRightCornerStyle = CORNER_STYLE_SHARP;
      private int mBottomLeftCornerStyle = CORNER_STYLE_SHARP;
  
      private int mTopStartCornerStyle = CORNER_STYLE_SHARP;
      private int mTopEndCornerStyle = CORNER_STYLE_SHARP;
      private int mBottomEndCornerStyle = CORNER_STYLE_SHARP;
      private int mBottomStartCornerStyle = CORNER_STYLE_SHARP;
  
      private int mScrimColor;
      private float mBorderWidth;
      private boolean mIsCompatibilityMode;
      private boolean mEatInvalidates;
  
      /**
       * Create a new StyledCornersBitmapDrawable.
       */
      public StyledCornersBitmapDrawable(Resources res, BitmapCache cache,
              boolean limitDensity, ExtendedOptions opts, float cornerRoundRadius,
              float cornerFlapSide) {
          super(res, cache, limitDensity, opts);
  
          mCornerRoundRadius = cornerRoundRadius;
          mCornerFlapSide = cornerFlapSide;
  
          mFlapPaint.setColor(Color.TRANSPARENT);
          mFlapPaint.setStyle(Style.FILL);
          mFlapPaint.setAntiAlias(true);
  
          mBorderPaint.setColor(Color.TRANSPARENT);
          mBorderPaint.setStyle(Style.STROKE);
          mBorderPaint.setStrokeWidth(mBorderWidth);
          mBorderPaint.setAntiAlias(true);
  
          mCompatibilityModeBackgroundPaint.setColor(Color.TRANSPARENT);
          mCompatibilityModeBackgroundPaint.setStyle(Style.FILL);
          mCompatibilityModeBackgroundPaint.setAntiAlias(true);
  
          mScrimColor = Color.TRANSPARENT;
      }
  
      /**
       * Set the border stroke width of this drawable.
       */
      public void setBorderWidth(final float borderWidth) {
          final boolean changed = mBorderPaint.getStrokeWidth() != borderWidth;
          mBorderPaint.setStrokeWidth(borderWidth);
          mBorderWidth = borderWidth;
  
          if (changed) {
              invalidateSelf();
          }
      }
  
      /**
       * Set the border stroke color of this drawable. Set to {@link Color#TRANSPARENT} to disable.
       */
      public void setBorderColor(final int color) {
          final boolean changed = mBorderPaint.getColor() != color;
          mBorderPaint.setColor(color);
  
          if (changed) {
              invalidateSelf();
          }
      }
  
      /** Set the corner styles for all four corners specified in RTL friendly ways */
      public void setCornerStylesRelative(int topStart, int topEnd, int bottomEnd, int bottomStart) {
          mTopStartCornerStyle = topStart;
          mTopEndCornerStyle = topEnd;
          mBottomEndCornerStyle = bottomEnd;
          mBottomStartCornerStyle = bottomStart;
          resolveCornerStyles();
      }
  
      @Override
      public void onLayoutDirectionChangeLocal(int layoutDirection) {
          resolveCornerStyles();
      }
  
      /**
       * Get the flap color for all corners that have style {@link #CORNER_STYLE_SHARP}.
       */
      public int getFlapColor() {
          return mFlapPaint.getColor();
      }
  
      /**
       * Set the flap color for all corners that have style {@link #CORNER_STYLE_SHARP}.
       *
       * Use {@link android.graphics.Color#TRANSPARENT} to disable flap colors.
       */
      public void setFlapColor(int flapColor) {
          boolean changed = mFlapPaint.getColor() != flapColor;
          mFlapPaint.setColor(flapColor);
  
          if (changed) {
              invalidateSelf();
          }
      }
  
      /**
       * Get the color of the scrim that is drawn over the contents, but under the flaps and borders.
       */
      public int getScrimColor() {
          return mScrimColor;
      }
  
      /**
       * Set the color of the scrim that is drawn over the contents, but under the flaps and borders.
       *
       * Use {@link android.graphics.Color#TRANSPARENT} to disable the scrim.
       */
      public void setScrimColor(int color) {
          boolean changed = mScrimColor != color;
          mScrimColor = color;
  
          if (changed) {
              invalidateSelf();
          }
      }
  
      /**
       * Sets whether we should work around an issue introduced in Android 4.4.3,
       * where a WebView can corrupt the stencil buffer of the canvas when the canvas is clipped
       * using a non-rectangular Path.
       */
      public void setCompatibilityMode(boolean isCompatibilityMode) {
          boolean changed = mIsCompatibilityMode != isCompatibilityMode;
          mIsCompatibilityMode = isCompatibilityMode;
  
          if (changed) {
              invalidateSelf();
          }
      }
  
      /**
       * Sets the color of the container that this drawable is in. The given color will be used in
       * {@link #setCompatibilityMode compatibility mode} to draw fake corners to emulate clipped
       * corners.
       */
      public void setCompatibilityModeBackgroundColor(int color) {
          boolean changed = mCompatibilityModeBackgroundPaint.getColor() != color;
          mCompatibilityModeBackgroundPaint.setColor(color);
  
          if (changed) {
              invalidateSelf();
          }
      }
  
      @Override
      protected void onBoundsChange(Rect bounds) {
          super.onBoundsChange(bounds);
  
          recalculatePath();
      }
  
      /**
       * Override draw(android.graphics.Canvas) instead of
       * {@link #onDraw(android.graphics.Canvas)} to clip all the drawable layers.
       */
      @Override
      public void draw(Canvas canvas) {
          final Rect bounds = getBounds();
          if (bounds.isEmpty()) {
              return;
          }
  
          pauseInvalidate();
  
          // Clip to path.
          if (!mIsCompatibilityMode) {
              canvas.save();
              canvas.clipPath(mClipPath);
          }
  
          // Draw parent within path.
          super.draw(canvas);
  
          // Draw scrim on top of parent.
          canvas.drawColor(mScrimColor);
  
          // Draw flaps.
          float left = bounds.left + mBorderWidth / 2;
          float top = bounds.top + mBorderWidth / 2;
          float right = bounds.right - mBorderWidth / 2;
          float bottom = bounds.bottom - mBorderWidth / 2;
          RectF flapCornerRectF = sRectF;
          flapCornerRectF.set(0, 0, mCornerFlapSide + mCornerRoundRadius,
                  mCornerFlapSide + mCornerRoundRadius);
  
          if (mTopLeftCornerStyle == CORNER_STYLE_FLAP) {
              flapCornerRectF.offsetTo(left, top);
              canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
                      mCornerRoundRadius, mFlapPaint);
          }
          if (mTopRightCornerStyle == CORNER_STYLE_FLAP) {
              flapCornerRectF.offsetTo(right - mCornerFlapSide, top);
              canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
                      mCornerRoundRadius, mFlapPaint);
          }
          if (mBottomRightCornerStyle == CORNER_STYLE_FLAP) {
              flapCornerRectF.offsetTo(right - mCornerFlapSide, bottom - mCornerFlapSide);
              canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
                      mCornerRoundRadius, mFlapPaint);
          }
          if (mBottomLeftCornerStyle == CORNER_STYLE_FLAP) {
              flapCornerRectF.offsetTo(left, bottom - mCornerFlapSide);
              canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
                      mCornerRoundRadius, mFlapPaint);
          }
  
          if (!mIsCompatibilityMode) {
              canvas.restore();
          }
  
          if (mIsCompatibilityMode) {
              drawFakeCornersForCompatibilityMode(canvas);
          }
  
          // Draw border around path.
          canvas.drawPath(mClipPath, mBorderPaint);
  
          resumeInvalidate();
      }
  
      @Override
      public void invalidateSelf() {
          if (!mEatInvalidates) {
              super.invalidateSelf();
          } else {
              Log.d(TAG, "Skipping invalidate.");
          }
      }
  
      protected void drawFakeCornersForCompatibilityMode(final Canvas canvas) {
          final Rect bounds = getBounds();
  
          float left = bounds.left;
          float top = bounds.top;
          float right = bounds.right;
          float bottom = bounds.bottom;
  
          // Draw fake round corners.
          RectF fakeCornerRectF = sRectF;
          fakeCornerRectF.set(0, 0, mCornerRoundRadius * 2, mCornerRoundRadius * 2);
          if (mTopLeftCornerStyle == CORNER_STYLE_ROUND) {
              fakeCornerRectF.offsetTo(left, top);
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(left, top);
              mCompatibilityModePath.lineTo(left + mCornerRoundRadius, top);
              mCompatibilityModePath.arcTo(fakeCornerRectF, START_TOP, -QUARTER_CIRCLE);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
          if (mTopRightCornerStyle == CORNER_STYLE_ROUND) {
              fakeCornerRectF.offsetTo(right - fakeCornerRectF.width(), top);
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(right, top);
              mCompatibilityModePath.lineTo(right, top + mCornerRoundRadius);
              mCompatibilityModePath.arcTo(fakeCornerRectF, START_RIGHT, -QUARTER_CIRCLE);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
          if (mBottomRightCornerStyle == CORNER_STYLE_ROUND) {
              fakeCornerRectF
                      .offsetTo(right - fakeCornerRectF.width(), bottom - fakeCornerRectF.height());
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(right, bottom);
              mCompatibilityModePath.lineTo(right - mCornerRoundRadius, bottom);
              mCompatibilityModePath.arcTo(fakeCornerRectF, START_BOTTOM, -QUARTER_CIRCLE);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
          if (mBottomLeftCornerStyle == CORNER_STYLE_ROUND) {
              fakeCornerRectF.offsetTo(left, bottom - fakeCornerRectF.height());
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(left, bottom);
              mCompatibilityModePath.lineTo(left, bottom - mCornerRoundRadius);
              mCompatibilityModePath.arcTo(fakeCornerRectF, START_LEFT, -QUARTER_CIRCLE);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
  
          // Draw fake flap corners.
          if (mTopLeftCornerStyle == CORNER_STYLE_FLAP) {
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(left, top);
              mCompatibilityModePath.lineTo(left + mCornerFlapSide, top);
              mCompatibilityModePath.lineTo(left, top + mCornerFlapSide);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
          if (mTopRightCornerStyle == CORNER_STYLE_FLAP) {
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(right, top);
              mCompatibilityModePath.lineTo(right, top + mCornerFlapSide);
              mCompatibilityModePath.lineTo(right - mCornerFlapSide, top);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
          if (mBottomRightCornerStyle == CORNER_STYLE_FLAP) {
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(right, bottom);
              mCompatibilityModePath.lineTo(right - mCornerFlapSide, bottom);
              mCompatibilityModePath.lineTo(right, bottom - mCornerFlapSide);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
          if (mBottomLeftCornerStyle == CORNER_STYLE_FLAP) {
              mCompatibilityModePath.rewind();
              mCompatibilityModePath.moveTo(left, bottom);
              mCompatibilityModePath.lineTo(left, bottom - mCornerFlapSide);
              mCompatibilityModePath.lineTo(left + mCornerFlapSide, bottom);
              mCompatibilityModePath.close();
              canvas.drawPath(mCompatibilityModePath, mCompatibilityModeBackgroundPaint);
          }
      }
  
      private void pauseInvalidate() {
          mEatInvalidates = true;
      }
  
      private void resumeInvalidate() {
          mEatInvalidates = false;
      }
  
      private void recalculatePath() {
          Rect bounds = getBounds();
  
          if (bounds.isEmpty()) {
              return;
          }
  
          // Setup.
          float left = bounds.left + mBorderWidth / 2;
          float top = bounds.top + mBorderWidth / 2;
          float right = bounds.right - mBorderWidth / 2;
          float bottom = bounds.bottom - mBorderWidth / 2;
          RectF roundedCornerRectF = sRectF;
          roundedCornerRectF.set(0, 0, 2 * mCornerRoundRadius, 2 * mCornerRoundRadius);
          mClipPath.rewind();
  
          switch (mTopLeftCornerStyle) {
              case CORNER_STYLE_SHARP:
                  mClipPath.moveTo(left, top);
                  break;
              case CORNER_STYLE_ROUND:
                  roundedCornerRectF.offsetTo(left, top);
                  mClipPath.arcTo(roundedCornerRectF, START_LEFT, QUARTER_CIRCLE);
                  break;
              case CORNER_STYLE_FLAP:
                  mClipPath.moveTo(left, top - mCornerFlapSide);
                  mClipPath.lineTo(left + mCornerFlapSide, top);
                  break;
          }
  
          switch (mTopRightCornerStyle) {
              case CORNER_STYLE_SHARP:
                  mClipPath.lineTo(right, top);
                  break;
              case CORNER_STYLE_ROUND:
                  roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(), top);
                  mClipPath.arcTo(roundedCornerRectF, START_TOP, QUARTER_CIRCLE);
                  break;
              case CORNER_STYLE_FLAP:
                  mClipPath.lineTo(right - mCornerFlapSide, top);
                  mClipPath.lineTo(right, top + mCornerFlapSide);
                  break;
          }
  
          switch (mBottomRightCornerStyle) {
              case CORNER_STYLE_SHARP:
                  mClipPath.lineTo(right, bottom);
                  break;
              case CORNER_STYLE_ROUND:
                  roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(),
                          bottom - roundedCornerRectF.height());
                  mClipPath.arcTo(roundedCornerRectF, START_RIGHT, QUARTER_CIRCLE);
                  break;
              case CORNER_STYLE_FLAP:
                  mClipPath.lineTo(right, bottom - mCornerFlapSide);
                  mClipPath.lineTo(right - mCornerFlapSide, bottom);
                  break;
          }
  
          switch (mBottomLeftCornerStyle) {
              case CORNER_STYLE_SHARP:
                  mClipPath.lineTo(left, bottom);
                  break;
              case CORNER_STYLE_ROUND:
                  roundedCornerRectF.offsetTo(left, bottom - roundedCornerRectF.height());
                  mClipPath.arcTo(roundedCornerRectF, START_BOTTOM, QUARTER_CIRCLE);
                  break;
              case CORNER_STYLE_FLAP:
                  mClipPath.lineTo(left + mCornerFlapSide, bottom);
                  mClipPath.lineTo(left, bottom - mCornerFlapSide);
                  break;
          }
  
          // Finish.
          mClipPath.close();
      }
  
      private void resolveCornerStyles() {
          boolean isLtr = getLayoutDirectionLocal() == View.LAYOUT_DIRECTION_LTR;
          setCornerStyles(
              isLtr ? mTopStartCornerStyle : mTopEndCornerStyle,
              isLtr ? mTopEndCornerStyle : mTopStartCornerStyle,
              isLtr ? mBottomEndCornerStyle : mBottomStartCornerStyle,
              isLtr ? mBottomStartCornerStyle : mBottomEndCornerStyle);
      }
  
      /** Set the corner styles for all four corners */
      private void setCornerStyles(int topLeft, int topRight, int bottomRight, int bottomLeft) {
          boolean changed = mTopLeftCornerStyle != topLeft
              || mTopRightCornerStyle != topRight
              || mBottomRightCornerStyle != bottomRight
              || mBottomLeftCornerStyle != bottomLeft;
  
          mTopLeftCornerStyle = topLeft;
          mTopRightCornerStyle = topRight;
          mBottomRightCornerStyle = bottomRight;
          mBottomLeftCornerStyle = bottomLeft;
  
          if (changed) {
              recalculatePath();
          }
      }
  }
  