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