1 /* 2 * Copyright (C) 2018 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 com.android.systemui.statusbar.phone; 18 19 20 import android.graphics.Bitmap; 21 import android.graphics.BlurMaskFilter; 22 import android.graphics.BlurMaskFilter.Blur; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.ColorFilter; 26 import android.graphics.Paint; 27 import android.graphics.PixelFormat; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 31 import com.android.systemui.R; 32 33 /** 34 * A drawable which adds shadow around a child drawable. 35 */ 36 public class ShadowKeyDrawable extends Drawable { 37 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 38 39 private final ShadowDrawableState mState; 40 41 public ShadowKeyDrawable(Drawable d) { 42 this(d, new ShadowDrawableState()); 43 } 44 45 private ShadowKeyDrawable(Drawable d, ShadowDrawableState state) { 46 mState = state; 47 if (d != null) { 48 mState.mBaseHeight = d.getIntrinsicHeight(); 49 mState.mBaseWidth = d.getIntrinsicWidth(); 50 mState.mChangingConfigurations = d.getChangingConfigurations(); 51 mState.mChildState = d.getConstantState(); 52 } 53 } 54 55 public void setRotation(float degrees) { 56 if (mState.mRotateDegrees != degrees) { 57 mState.mRotateDegrees = degrees; 58 mState.mLastDrawnBitmap = null; 59 invalidateSelf(); 60 } 61 } 62 63 public void setShadowProperties(int x, int y, int size, int color) { 64 if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y 65 || mState.mShadowSize != size || mState.mShadowColor != color) { 66 mState.mShadowOffsetX = x; 67 mState.mShadowOffsetY = y; 68 mState.mShadowSize = size; 69 mState.mShadowColor = color; 70 mState.mLastDrawnBitmap = null; 71 invalidateSelf(); 72 } 73 } 74 75 public float getRotation() { 76 return mState.mRotateDegrees; 77 } 78 79 @Override 80 public void draw(Canvas canvas) { 81 Rect bounds = getBounds(); 82 if (bounds.isEmpty()) { 83 return; 84 } 85 if (mState.mLastDrawnBitmap == null) { 86 regenerateBitmapCache(); 87 } 88 canvas.drawBitmap(mState.mLastDrawnBitmap, null, bounds, mPaint); 89 } 90 91 @Override 92 public void setTint(int tintColor) { 93 super.setTint(tintColor); 94 } 95 96 @Override 97 public void setAlpha(int alpha) { 98 mPaint.setAlpha(alpha); 99 invalidateSelf(); 100 } 101 102 @Override 103 public void setColorFilter(ColorFilter colorFilter) { 104 mPaint.setColorFilter(colorFilter); 105 invalidateSelf(); 106 } 107 108 @Override 109 public ConstantState getConstantState() { 110 return mState; 111 } 112 113 @Override 114 public int getOpacity() { 115 return PixelFormat.TRANSLUCENT; 116 } 117 118 @Override 119 public int getIntrinsicHeight() { 120 return mState.mBaseHeight; 121 } 122 123 @Override 124 public int getIntrinsicWidth() { 125 return mState.mBaseWidth; 126 } 127 128 @Override 129 public boolean canApplyTheme() { 130 return mState.canApplyTheme(); 131 } 132 133 private void regenerateBitmapCache() { 134 final int width = getIntrinsicWidth(); 135 final int height = getIntrinsicHeight(); 136 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 137 Canvas canvas = new Canvas(bitmap); 138 canvas.save(); 139 final float radians = (float) (mState.mRotateDegrees * Math.PI / 180); 140 141 // Rotate canvas before drawing original drawable if no shadow 142 if (mState.mShadowSize == 0) { 143 canvas.rotate(mState.mRotateDegrees, width / 2, height / 2); 144 } 145 146 // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared. 147 final Drawable d = mState.mChildState.newDrawable().mutate(); 148 d.setBounds(0, 0, mState.mBaseWidth, mState.mBaseHeight); 149 d.draw(canvas); 150 151 if (mState.mShadowSize > 0) { 152 // Draws the shadow 153 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 154 paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL)); 155 int[] offset = new int[2]; 156 157 final Bitmap shadow = bitmap.extractAlpha(paint, offset); 158 159 paint.setMaskFilter(null); 160 paint.setColor(mState.mShadowColor); 161 bitmap.eraseColor(Color.TRANSPARENT); 162 163 canvas.rotate(mState.mRotateDegrees, width / 2, height / 2); 164 165 final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY 166 + Math.cos(radians) * mState.mShadowOffsetX); 167 final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY 168 - Math.sin(radians) * mState.mShadowOffsetX); 169 170 canvas.drawBitmap(shadow, offset[0] + shadowOffsetX, offset[1] + shadowOffsetY, paint); 171 d.draw(canvas); 172 } 173 174 bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false); 175 mState.mLastDrawnBitmap = bitmap; 176 canvas.restore(); 177 } 178 179 private static class ShadowDrawableState extends ConstantState { 180 int mChangingConfigurations; 181 int mBaseWidth; 182 int mBaseHeight; 183 float mRotateDegrees; 184 int mShadowOffsetX; 185 int mShadowOffsetY; 186 int mShadowSize; 187 int mShadowColor; 188 189 Bitmap mLastDrawnBitmap; 190 ConstantState mChildState; 191 192 @Override 193 public Drawable newDrawable() { 194 return new ShadowKeyDrawable(null, this); 195 } 196 197 @Override 198 public int getChangingConfigurations() { 199 return mChangingConfigurations; 200 } 201 202 @Override 203 public boolean canApplyTheme() { 204 return true; 205 } 206 } 207 } 208