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