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