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