1 /* 2 * Copyright (C) 2014 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.notification.row; 18 19 import android.content.Context; 20 import android.content.res.ColorStateList; 21 import android.graphics.Canvas; 22 import android.graphics.PorterDuff; 23 import android.graphics.drawable.Drawable; 24 import android.graphics.drawable.GradientDrawable; 25 import android.graphics.drawable.LayerDrawable; 26 import android.graphics.drawable.RippleDrawable; 27 import android.util.AttributeSet; 28 import android.view.View; 29 30 import com.android.internal.util.ArrayUtils; 31 import com.android.systemui.R; 32 import com.android.systemui.statusbar.notification.ExpandAnimationParameters; 33 34 /** 35 * A view that can be used for both the dimmed and normal background of an notification. 36 */ 37 public class NotificationBackgroundView extends View { 38 39 private final boolean mDontModifyCorners; 40 private Drawable mBackground; 41 private int mClipTopAmount; 42 private int mActualHeight; 43 private int mClipBottomAmount; 44 private int mTintColor; 45 private final float[] mCornerRadii = new float[8]; 46 private boolean mBottomIsRounded; 47 private int mBackgroundTop; 48 private boolean mBottomAmountClips = true; 49 private boolean mExpandAnimationRunning; 50 private float mActualWidth; 51 private int mDrawableAlpha = 255; 52 private boolean mIsPressedAllowed; 53 NotificationBackgroundView(Context context, AttributeSet attrs)54 public NotificationBackgroundView(Context context, AttributeSet attrs) { 55 super(context, attrs); 56 mDontModifyCorners = getResources().getBoolean( 57 R.bool.config_clipNotificationsToOutline); 58 } 59 60 @Override onDraw(Canvas canvas)61 protected void onDraw(Canvas canvas) { 62 if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop 63 || mExpandAnimationRunning) { 64 canvas.save(); 65 if (!mExpandAnimationRunning) { 66 canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount); 67 } 68 draw(canvas, mBackground); 69 canvas.restore(); 70 } 71 } 72 draw(Canvas canvas, Drawable drawable)73 private void draw(Canvas canvas, Drawable drawable) { 74 if (drawable != null) { 75 int top = mBackgroundTop; 76 int bottom = mActualHeight; 77 if (mBottomIsRounded 78 && mBottomAmountClips 79 && !mExpandAnimationRunning) { 80 bottom -= mClipBottomAmount; 81 } 82 int left = 0; 83 int right = getWidth(); 84 if (mExpandAnimationRunning) { 85 left = (int) ((getWidth() - mActualWidth) / 2.0f); 86 right = (int) (left + mActualWidth); 87 } 88 drawable.setBounds(left, top, right, bottom); 89 drawable.draw(canvas); 90 } 91 } 92 93 @Override verifyDrawable(Drawable who)94 protected boolean verifyDrawable(Drawable who) { 95 return super.verifyDrawable(who) || who == mBackground; 96 } 97 98 @Override drawableStateChanged()99 protected void drawableStateChanged() { 100 setState(getDrawableState()); 101 } 102 103 @Override drawableHotspotChanged(float x, float y)104 public void drawableHotspotChanged(float x, float y) { 105 if (mBackground != null) { 106 mBackground.setHotspot(x, y); 107 } 108 } 109 110 /** 111 * Sets a background drawable. As we need to change our bounds independently of layout, we need 112 * the notion of a background independently of the regular View background.. 113 */ setCustomBackground(Drawable background)114 public void setCustomBackground(Drawable background) { 115 if (mBackground != null) { 116 mBackground.setCallback(null); 117 unscheduleDrawable(mBackground); 118 } 119 mBackground = background; 120 mBackground.mutate(); 121 if (mBackground != null) { 122 mBackground.setCallback(this); 123 setTint(mTintColor); 124 } 125 if (mBackground instanceof RippleDrawable) { 126 ((RippleDrawable) mBackground).setForceSoftware(true); 127 } 128 updateBackgroundRadii(); 129 invalidate(); 130 } 131 setCustomBackground(int drawableResId)132 public void setCustomBackground(int drawableResId) { 133 final Drawable d = mContext.getDrawable(drawableResId); 134 setCustomBackground(d); 135 } 136 setTint(int tintColor)137 public void setTint(int tintColor) { 138 if (tintColor != 0) { 139 mBackground.setColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP); 140 } else { 141 mBackground.clearColorFilter(); 142 } 143 mTintColor = tintColor; 144 invalidate(); 145 } 146 setActualHeight(int actualHeight)147 public void setActualHeight(int actualHeight) { 148 if (mExpandAnimationRunning) { 149 return; 150 } 151 mActualHeight = actualHeight; 152 invalidate(); 153 } 154 getActualHeight()155 public int getActualHeight() { 156 return mActualHeight; 157 } 158 setClipTopAmount(int clipTopAmount)159 public void setClipTopAmount(int clipTopAmount) { 160 mClipTopAmount = clipTopAmount; 161 invalidate(); 162 } 163 setClipBottomAmount(int clipBottomAmount)164 public void setClipBottomAmount(int clipBottomAmount) { 165 mClipBottomAmount = clipBottomAmount; 166 invalidate(); 167 } 168 169 @Override hasOverlappingRendering()170 public boolean hasOverlappingRendering() { 171 172 // Prevents this view from creating a layer when alpha is animating. 173 return false; 174 } 175 setState(int[] drawableState)176 public void setState(int[] drawableState) { 177 if (mBackground != null && mBackground.isStateful()) { 178 if (!mIsPressedAllowed) { 179 drawableState = ArrayUtils.removeInt(drawableState, 180 com.android.internal.R.attr.state_pressed); 181 } 182 mBackground.setState(drawableState); 183 } 184 } 185 setRippleColor(int color)186 public void setRippleColor(int color) { 187 if (mBackground instanceof RippleDrawable) { 188 RippleDrawable ripple = (RippleDrawable) mBackground; 189 ripple.setColor(ColorStateList.valueOf(color)); 190 } 191 } 192 setDrawableAlpha(int drawableAlpha)193 public void setDrawableAlpha(int drawableAlpha) { 194 mDrawableAlpha = drawableAlpha; 195 if (mExpandAnimationRunning) { 196 return; 197 } 198 mBackground.setAlpha(drawableAlpha); 199 } 200 201 /** 202 * Sets the current top and bottom radius for this background. 203 */ setRadius(float topRoundness, float bottomRoundness)204 public void setRadius(float topRoundness, float bottomRoundness) { 205 if (topRoundness == mCornerRadii[0] && bottomRoundness == mCornerRadii[4]) { 206 return; 207 } 208 mBottomIsRounded = bottomRoundness != 0.0f; 209 mCornerRadii[0] = topRoundness; 210 mCornerRadii[1] = topRoundness; 211 mCornerRadii[2] = topRoundness; 212 mCornerRadii[3] = topRoundness; 213 mCornerRadii[4] = bottomRoundness; 214 mCornerRadii[5] = bottomRoundness; 215 mCornerRadii[6] = bottomRoundness; 216 mCornerRadii[7] = bottomRoundness; 217 updateBackgroundRadii(); 218 } 219 setBottomAmountClips(boolean clips)220 public void setBottomAmountClips(boolean clips) { 221 if (clips != mBottomAmountClips) { 222 mBottomAmountClips = clips; 223 invalidate(); 224 } 225 } 226 updateBackgroundRadii()227 private void updateBackgroundRadii() { 228 if (mDontModifyCorners) { 229 return; 230 } 231 if (mBackground instanceof LayerDrawable) { 232 GradientDrawable gradientDrawable = 233 (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); 234 gradientDrawable.setCornerRadii(mCornerRadii); 235 } 236 } 237 setBackgroundTop(int backgroundTop)238 public void setBackgroundTop(int backgroundTop) { 239 mBackgroundTop = backgroundTop; 240 invalidate(); 241 } 242 243 /** Set the current expand animation size. */ setExpandAnimationSize(int actualWidth, int actualHeight)244 public void setExpandAnimationSize(int actualWidth, int actualHeight) { 245 mActualHeight = actualHeight; 246 mActualWidth = actualWidth; 247 invalidate(); 248 } 249 setExpandAnimationRunning(boolean running)250 public void setExpandAnimationRunning(boolean running) { 251 mExpandAnimationRunning = running; 252 if (mBackground instanceof LayerDrawable) { 253 GradientDrawable gradientDrawable = 254 (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); 255 // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to 256 // spot during animation anyways. 257 gradientDrawable.setAntiAlias(!running); 258 } 259 if (!mExpandAnimationRunning) { 260 setDrawableAlpha(mDrawableAlpha); 261 } 262 invalidate(); 263 } 264 setPressedAllowed(boolean allowed)265 public void setPressedAllowed(boolean allowed) { 266 mIsPressedAllowed = allowed; 267 } 268 } 269