• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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