content: /*
   * Copyright (C) 2017 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.launcher3.graphics;
  
  import android.annotation.TargetApi;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.Bitmap;
  import android.graphics.BlurMaskFilter;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.ColorFilter;
  import android.graphics.Paint;
  import android.graphics.PixelFormat;
  import android.graphics.Rect;
  import android.graphics.drawable.Drawable;
  import android.os.Build;
  import android.util.AttributeSet;
  
  import com.android.launcher3.R;
  import com.android.launcher3.Utilities;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import java.io.IOException;
  
  /**
   * A drawable which adds shadow around a child drawable.
   */
  @TargetApi(Build.VERSION_CODES.O)
  public class ShadowDrawable extends Drawable {
  
      private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
  
      private final ShadowDrawableState mState;
  
      @SuppressWarnings("unused")
      public ShadowDrawable() {
          this(new ShadowDrawableState());
      }
  
      private ShadowDrawable(ShadowDrawableState state) {
          mState = state;
      }
  
      @Override
      public void draw(Canvas canvas) {
          Rect bounds = getBounds();
          if (bounds.isEmpty()) {
              return;
          }
          if (mState.mLastDrawnBitmap == null) {
              regenerateBitmapCache();
          }
          canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint);
      }
  
      @Override
      public void setAlpha(int alpha) {
          mPaint.setAlpha(alpha);
          invalidateSelf();
      }
  
      @Override
      public void setColorFilter(ColorFilter colorFilter) {
          mPaint.setColorFilter(colorFilter);
          invalidateSelf();
      }
  
      @Override
      public ConstantState getConstantState() {
          return mState;
      }
  
      @Override
      public int getOpacity() {
          return PixelFormat.TRANSLUCENT;
      }
  
      @Override
      public int getIntrinsicHeight() {
          return mState.mIntrinsicHeight;
      }
  
      @Override
      public int getIntrinsicWidth() {
          return mState.mIntrinsicWidth;
      }
  
      @Override
      public boolean canApplyTheme() {
          return mState.canApplyTheme();
      }
  
      @Override
      public void applyTheme(Resources.Theme t) {
          TypedArray ta = t.obtainStyledAttributes(new int[] {R.attr.isWorkspaceDarkText});
          boolean isDark = ta.getBoolean(0, false);
          ta.recycle();
          if (mState.mIsDark != isDark) {
              mState.mIsDark = isDark;
              mState.mLastDrawnBitmap = null;
              invalidateSelf();
          }
      }
  
      private void regenerateBitmapCache() {
          Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight,
                  Bitmap.Config.ARGB_8888);
          Canvas canvas = new Canvas(bitmap);
  
          // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
          Drawable d = mState.mChildState.newDrawable().mutate();
          d.setBounds(mState.mShadowSize, mState.mShadowSize,
                  mState.mIntrinsicWidth - mState.mShadowSize,
                  mState.mIntrinsicHeight - mState.mShadowSize);
          d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE);
          d.draw(canvas);
  
          // Do not draw shadow on dark theme
          if (!mState.mIsDark) {
              Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
              paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL));
              int[] offset = new int[2];
              Bitmap shadow = bitmap.extractAlpha(paint, offset);
  
              paint.setMaskFilter(null);
              paint.setColor(mState.mShadowColor);
              bitmap.eraseColor(Color.TRANSPARENT);
              canvas.drawBitmap(shadow, offset[0], offset[1], paint);
              d.draw(canvas);
          }
  
          if (Utilities.ATLEAST_OREO) {
              bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
          }
          mState.mLastDrawnBitmap = bitmap;
      }
  
      @Override
      public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs,
              Resources.Theme theme) throws XmlPullParserException, IOException {
          super.inflate(r, parser, attrs, theme);
  
          final TypedArray a = theme == null
                  ? r.obtainAttributes(attrs, R.styleable.ShadowDrawable)
                  : theme.obtainStyledAttributes(attrs, R.styleable.ShadowDrawable, 0, 0);
          try {
              Drawable d = a.getDrawable(R.styleable.ShadowDrawable_android_src);
              if (d == null) {
                  throw new XmlPullParserException("missing src attribute");
              }
              mState.mShadowColor = a.getColor(
                      R.styleable.ShadowDrawable_android_shadowColor, Color.BLACK);
              mState.mShadowSize = a.getDimensionPixelSize(
                      R.styleable.ShadowDrawable_android_elevation, 0);
              mState.mDarkTintColor = a.getColor(
                      R.styleable.ShadowDrawable_darkTintColor, Color.BLACK);
  
              mState.mIntrinsicHeight = d.getIntrinsicHeight() + 2 * mState.mShadowSize;
              mState.mIntrinsicWidth = d.getIntrinsicWidth() + 2 * mState.mShadowSize;
              mState.mChangingConfigurations = d.getChangingConfigurations();
  
              mState.mChildState = d.getConstantState();
          } finally {
              a.recycle();
          }
      }
  
      private static class ShadowDrawableState extends ConstantState {
  
          int mChangingConfigurations;
          int mIntrinsicWidth;
          int mIntrinsicHeight;
  
          int mShadowColor;
          int mShadowSize;
          int mDarkTintColor;
  
          boolean mIsDark;
          Bitmap mLastDrawnBitmap;
          ConstantState mChildState;
  
          @Override
          public Drawable newDrawable() {
              return new ShadowDrawable(this);
          }
  
          @Override
          public int getChangingConfigurations() {
              return mChangingConfigurations;
          }
  
          @Override
          public boolean canApplyTheme() {
              return true;
          }
      }
  }
  