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.Dumpable; 32 import com.android.systemui.R; 33 34 import java.io.PrintWriter; 35 import java.util.Arrays; 36 37 /** 38 * A view that can be used for both the dimmed and normal background of an notification. 39 */ 40 public class NotificationBackgroundView extends View implements Dumpable { 41 42 private final boolean mDontModifyCorners; 43 private Drawable mBackground; 44 private int mClipTopAmount; 45 private int mClipBottomAmount; 46 private int mTintColor; 47 private final float[] mCornerRadii = new float[8]; 48 private boolean mBottomIsRounded; 49 private boolean mBottomAmountClips = true; 50 private int mActualHeight = -1; 51 private int mActualWidth = -1; 52 private boolean mExpandAnimationRunning; 53 private int mExpandAnimationWidth = -1; 54 private int mExpandAnimationHeight = -1; 55 private int mDrawableAlpha = 255; 56 private boolean mIsPressedAllowed; 57 NotificationBackgroundView(Context context, AttributeSet attrs)58 public NotificationBackgroundView(Context context, AttributeSet attrs) { 59 super(context, attrs); 60 mDontModifyCorners = getResources().getBoolean( 61 R.bool.config_clipNotificationsToOutline); 62 } 63 64 @Override onDraw(Canvas canvas)65 protected void onDraw(Canvas canvas) { 66 if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) { 67 canvas.save(); 68 if (!mExpandAnimationRunning) { 69 canvas.clipRect(0, mClipTopAmount, getWidth(), 70 getActualHeight() - mClipBottomAmount); 71 } 72 draw(canvas, mBackground); 73 canvas.restore(); 74 } 75 } 76 draw(Canvas canvas, Drawable drawable)77 private void draw(Canvas canvas, Drawable drawable) { 78 if (drawable != null) { 79 int top = 0; 80 int bottom = getActualHeight(); 81 if (mBottomIsRounded 82 && mBottomAmountClips 83 && !mExpandAnimationRunning) { 84 bottom -= mClipBottomAmount; 85 } 86 final boolean isRtl = isLayoutRtl(); 87 final int width = getWidth(); 88 final int actualWidth = getActualWidth(); 89 90 int left = isRtl ? width - actualWidth : 0; 91 int right = isRtl ? width : actualWidth; 92 93 if (mExpandAnimationRunning) { 94 // Horizontally center this background view inside of the container 95 left = (int) ((width - actualWidth) / 2.0f); 96 right = (int) (left + actualWidth); 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 private int getActualHeight() { 166 if (mExpandAnimationRunning && mExpandAnimationHeight > -1) { 167 return mExpandAnimationHeight; 168 } else if (mActualHeight > -1) { 169 return mActualHeight; 170 } 171 return getHeight(); 172 } 173 setActualWidth(int actualWidth)174 public void setActualWidth(int actualWidth) { 175 mActualWidth = actualWidth; 176 } 177 getActualWidth()178 private int getActualWidth() { 179 if (mExpandAnimationRunning && mExpandAnimationWidth > -1) { 180 return mExpandAnimationWidth; 181 } else if (mActualWidth > -1) { 182 return mActualWidth; 183 } 184 return getWidth(); 185 } 186 setClipTopAmount(int clipTopAmount)187 public void setClipTopAmount(int clipTopAmount) { 188 mClipTopAmount = clipTopAmount; 189 invalidate(); 190 } 191 setClipBottomAmount(int clipBottomAmount)192 public void setClipBottomAmount(int clipBottomAmount) { 193 mClipBottomAmount = clipBottomAmount; 194 invalidate(); 195 } 196 197 @Override hasOverlappingRendering()198 public boolean hasOverlappingRendering() { 199 200 // Prevents this view from creating a layer when alpha is animating. 201 return false; 202 } 203 setState(int[] drawableState)204 public void setState(int[] drawableState) { 205 if (mBackground != null && mBackground.isStateful()) { 206 if (!mIsPressedAllowed) { 207 drawableState = ArrayUtils.removeInt(drawableState, 208 com.android.internal.R.attr.state_pressed); 209 } 210 mBackground.setState(drawableState); 211 } 212 } 213 setRippleColor(int color)214 public void setRippleColor(int color) { 215 if (mBackground instanceof RippleDrawable) { 216 RippleDrawable ripple = (RippleDrawable) mBackground; 217 ripple.setColor(ColorStateList.valueOf(color)); 218 } 219 } 220 setDrawableAlpha(int drawableAlpha)221 public void setDrawableAlpha(int drawableAlpha) { 222 mDrawableAlpha = drawableAlpha; 223 if (mExpandAnimationRunning) { 224 return; 225 } 226 mBackground.setAlpha(drawableAlpha); 227 } 228 229 /** 230 * Sets the current top and bottom radius for this background. 231 */ setRadius(float topRoundness, float bottomRoundness)232 public void setRadius(float topRoundness, float bottomRoundness) { 233 if (topRoundness == mCornerRadii[0] && bottomRoundness == mCornerRadii[4]) { 234 return; 235 } 236 mBottomIsRounded = bottomRoundness != 0.0f; 237 mCornerRadii[0] = topRoundness; 238 mCornerRadii[1] = topRoundness; 239 mCornerRadii[2] = topRoundness; 240 mCornerRadii[3] = topRoundness; 241 mCornerRadii[4] = bottomRoundness; 242 mCornerRadii[5] = bottomRoundness; 243 mCornerRadii[6] = bottomRoundness; 244 mCornerRadii[7] = bottomRoundness; 245 updateBackgroundRadii(); 246 } 247 setBottomAmountClips(boolean clips)248 public void setBottomAmountClips(boolean clips) { 249 if (clips != mBottomAmountClips) { 250 mBottomAmountClips = clips; 251 invalidate(); 252 } 253 } 254 updateBackgroundRadii()255 private void updateBackgroundRadii() { 256 if (mDontModifyCorners) { 257 return; 258 } 259 if (mBackground instanceof LayerDrawable) { 260 GradientDrawable gradientDrawable = 261 (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); 262 gradientDrawable.setCornerRadii(mCornerRadii); 263 } 264 } 265 266 /** Set the current expand animation size. */ setExpandAnimationSize(int width, int height)267 public void setExpandAnimationSize(int width, int height) { 268 mExpandAnimationHeight = height; 269 mExpandAnimationWidth = width; 270 invalidate(); 271 } 272 setExpandAnimationRunning(boolean running)273 public void setExpandAnimationRunning(boolean running) { 274 mExpandAnimationRunning = running; 275 if (mBackground instanceof LayerDrawable) { 276 GradientDrawable gradientDrawable = 277 (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0); 278 // Speed optimization: disable AA if transfer mode is not SRC_OVER. AA is not easy to 279 // spot during animation anyways. 280 gradientDrawable.setAntiAlias(!running); 281 } 282 if (!mExpandAnimationRunning) { 283 setDrawableAlpha(mDrawableAlpha); 284 } 285 invalidate(); 286 } 287 setPressedAllowed(boolean allowed)288 public void setPressedAllowed(boolean allowed) { 289 mIsPressedAllowed = allowed; 290 } 291 292 @Override dump(PrintWriter pw, String[] args)293 public void dump(PrintWriter pw, String[] args) { 294 pw.println("mDontModifyCorners: " + mDontModifyCorners); 295 pw.println("mClipTopAmount: " + mClipTopAmount); 296 pw.println("mClipBottomAmount: " + mClipBottomAmount); 297 pw.println("mCornerRadii: " + Arrays.toString(mCornerRadii)); 298 pw.println("mBottomIsRounded: " + mBottomIsRounded); 299 pw.println("mBottomAmountClips: " + mBottomAmountClips); 300 pw.println("mActualWidth: " + mActualWidth); 301 pw.println("mActualHeight: " + mActualHeight); 302 } 303 } 304