• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.scrim;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.graphics.Canvas;
25 import android.graphics.ColorFilter;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.PixelFormat;
29 import android.graphics.Rect;
30 import android.graphics.RectF;
31 import android.graphics.Xfermode;
32 import android.graphics.drawable.Drawable;
33 import android.view.animation.DecelerateInterpolator;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.graphics.ColorUtils;
37 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
38 
39 /**
40  * Drawable used on SysUI scrims.
41  */
42 public class ScrimDrawable extends Drawable {
43     private static final String TAG = "ScrimDrawable";
44 
45     private boolean mShouldUseLargeScreenSize;
46     private final Paint mPaint;
47     private final Path mPath = new Path();
48     private final RectF mBoundsRectF = new RectF();
49 
50     private int mAlpha = 255;
51     private int mMainColor;
52     private ValueAnimator mColorAnimation;
53     private int mMainColorTo;
54     private float mCornerRadius;
55     private ConcaveInfo mConcaveInfo;
56     private int mBottomEdgePosition;
57     private float mBottomEdgeRadius = -1;
58     private boolean mCornerRadiusEnabled;
59 
ScrimDrawable()60     public ScrimDrawable() {
61         mPaint = new Paint();
62         mPaint.setStyle(Paint.Style.FILL);
63         mShouldUseLargeScreenSize = false;
64     }
65 
66     /**
67      * Sets the background color.
68      * @param mainColor the color.
69      * @param animated if transition should be interpolated.
70      */
setColor(int mainColor, boolean animated)71     public void setColor(int mainColor, boolean animated) {
72         if (mainColor == mMainColorTo) {
73             return;
74         }
75 
76         if (mColorAnimation != null && mColorAnimation.isRunning()) {
77             mColorAnimation.cancel();
78         }
79 
80         mMainColorTo = mainColor;
81 
82         if (animated) {
83             final int mainFrom = mMainColor;
84 
85             ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
86             anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
87             anim.addUpdateListener(animation -> {
88                 float ratio = (float) animation.getAnimatedValue();
89                 mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
90                 invalidateSelf();
91             });
92             anim.addListener(new AnimatorListenerAdapter() {
93                 @Override
94                 public void onAnimationEnd(Animator animation, boolean isReverse) {
95                     if (mColorAnimation == animation) {
96                         mColorAnimation = null;
97                     }
98                 }
99             });
100             anim.setInterpolator(new DecelerateInterpolator());
101             anim.start();
102             mColorAnimation = anim;
103         } else {
104             mMainColor = mainColor;
105             invalidateSelf();
106         }
107     }
108 
109     @Override
setAlpha(int alpha)110     public void setAlpha(int alpha) {
111         if (alpha != mAlpha) {
112             mAlpha = alpha;
113             invalidateSelf();
114         }
115     }
116 
117     @Override
getAlpha()118     public int getAlpha() {
119         return mAlpha;
120     }
121 
122     @Override
setXfermode(@ullable Xfermode mode)123     public void setXfermode(@Nullable Xfermode mode) {
124         mPaint.setXfermode(mode);
125         invalidateSelf();
126     }
127 
128     @Override
setColorFilter(ColorFilter colorFilter)129     public void setColorFilter(ColorFilter colorFilter) {
130         mPaint.setColorFilter(colorFilter);
131     }
132 
133     @Override
getColorFilter()134     public ColorFilter getColorFilter() {
135         return mPaint.getColorFilter();
136     }
137 
138     @Override
getOpacity()139     public int getOpacity() {
140         return PixelFormat.TRANSLUCENT;
141     }
142 
setShouldUseLargeScreenSize(boolean v)143     public void setShouldUseLargeScreenSize(boolean v) {
144         mShouldUseLargeScreenSize = v;
145     }
146 
147     /**
148      * Corner radius used by either concave or convex corners.
149      */
setRoundedCorners(float radius)150     public void setRoundedCorners(float radius) {
151         if (radius == mCornerRadius) {
152             return;
153         }
154         mCornerRadius = radius;
155         if (mConcaveInfo != null) {
156             mConcaveInfo.setCornerRadius(radius);
157             updatePath();
158         }
159         invalidateSelf();
160     }
161 
162     /**
163      * If we should draw a rounded rect instead of a rect.
164      */
setRoundedCornersEnabled(boolean enabled)165     public void setRoundedCornersEnabled(boolean enabled) {
166         if (mCornerRadiusEnabled == enabled) {
167             return;
168         }
169         mCornerRadiusEnabled = enabled;
170         invalidateSelf();
171     }
172 
173     /**
174      * If we should draw a concave rounded rect instead of a rect.
175      */
setBottomEdgeConcave(boolean enabled)176     public void setBottomEdgeConcave(boolean enabled) {
177         if (enabled && mConcaveInfo != null) {
178             return;
179         }
180         if (!enabled) {
181             mConcaveInfo = null;
182         } else {
183             mConcaveInfo = new ConcaveInfo();
184             mConcaveInfo.setCornerRadius(mCornerRadius);
185         }
186         invalidateSelf();
187     }
188 
189     /**
190      * Location of concave edge.
191      * @see #setBottomEdgeConcave(boolean)
192      */
setBottomEdgePosition(int y)193     public void setBottomEdgePosition(int y) {
194         if (mBottomEdgePosition == y) {
195             return;
196         }
197         mBottomEdgePosition = y;
198         if (mConcaveInfo == null) {
199             return;
200         }
201         updatePath();
202         invalidateSelf();
203     }
204 
setBottomEdgeRadius(float radius)205     public void setBottomEdgeRadius(float radius) {
206         if (mBottomEdgeRadius != radius) {
207             mBottomEdgeRadius = radius;
208             invalidateSelf();
209         }
210     }
211 
212     @Override
draw(@onNull Canvas canvas)213     public void draw(@NonNull Canvas canvas) {
214         mPaint.setColor(mMainColor);
215         mPaint.setAlpha(mAlpha);
216         if (mConcaveInfo != null) {
217             drawConcave(canvas);
218         } else if (mCornerRadiusEnabled && mCornerRadius > 0) {
219             float topEdgeRadius = mCornerRadius;
220             float bottomEdgeRadius = mBottomEdgeRadius == -1.0 ? mCornerRadius : mBottomEdgeRadius;
221 
222             mBoundsRectF.set(getBounds());
223 
224             // When the back gesture causes the notification scrim to be scaled down,
225             // this offset "reveals" the rounded bottom edge as it "pulls away".
226             // We must *not* make this adjustment on largescreen shades (where the corner is sharp).
227             if (!mShouldUseLargeScreenSize && mBottomEdgeRadius != -1) {
228                 mBoundsRectF.bottom -= bottomEdgeRadius;
229             }
230 
231             // We need a box with rounded corners but its lower corners are not rounded on large
232             // screen devices in "portrait" orientation.
233             // Thus, we cannot draw a symmetric rounded rectangle via canvas.drawRoundRect()
234             // and must build a box with different corner radii at the top and at the bottom.
235             // Additionally, when the scrim is pushed to the very bottom of the screen, do not draw
236             // anything (drawing a rounded box with these specifications is not possible).
237             // TODO(b/271030611) perhaps this could be accomplished via Path.addRoundRect instead?
238             if (mBoundsRectF.bottom - mBoundsRectF.top > bottomEdgeRadius) {
239                 mPath.reset();
240                 mPath.moveTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius);
241                 mPath.cubicTo(mBoundsRectF.right, mBoundsRectF.top + topEdgeRadius,
242                         mBoundsRectF.right, mBoundsRectF.top,
243                         mBoundsRectF.right - topEdgeRadius, mBoundsRectF.top);
244                 mPath.lineTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top);
245                 mPath.cubicTo(mBoundsRectF.left + topEdgeRadius, mBoundsRectF.top,
246                         mBoundsRectF.left, mBoundsRectF.top,
247                         mBoundsRectF.left, mBoundsRectF.top + topEdgeRadius);
248                 mPath.lineTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius);
249                 mPath.cubicTo(mBoundsRectF.left, mBoundsRectF.bottom - bottomEdgeRadius,
250                         mBoundsRectF.left, mBoundsRectF.bottom,
251                         mBoundsRectF.left + bottomEdgeRadius, mBoundsRectF.bottom);
252                 mPath.lineTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom);
253                 mPath.cubicTo(mBoundsRectF.right - bottomEdgeRadius, mBoundsRectF.bottom,
254                         mBoundsRectF.right, mBoundsRectF.bottom,
255                         mBoundsRectF.right, mBoundsRectF.bottom - bottomEdgeRadius);
256                 mPath.close();
257                 canvas.drawPath(mPath, mPaint);
258             }
259         } else {
260             canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
261                     getBounds().bottom, mPaint);
262         }
263     }
264 
265     @Override
onBoundsChange(Rect bounds)266     protected void onBoundsChange(Rect bounds) {
267         updatePath();
268     }
269 
drawConcave(Canvas canvas)270     private void drawConcave(Canvas canvas) {
271         canvas.clipOutPath(mConcaveInfo.mPath);
272         canvas.drawRect(getBounds().left, getBounds().top, getBounds().right,
273                 mBottomEdgePosition + mConcaveInfo.mPathOverlap, mPaint);
274     }
275 
updatePath()276     private void updatePath() {
277         if (mConcaveInfo == null) {
278             return;
279         }
280         mConcaveInfo.mPath.reset();
281         float top = mBottomEdgePosition;
282         float bottom = mBottomEdgePosition + mConcaveInfo.mPathOverlap;
283         mConcaveInfo.mPath.addRoundRect(getBounds().left, top, getBounds().right, bottom,
284                 mConcaveInfo.mCornerRadii, Path.Direction.CW);
285     }
286 
287     @VisibleForTesting
getMainColor()288     public int getMainColor() {
289         return mMainColor;
290     }
291 
292     private static class ConcaveInfo {
293         private float mPathOverlap;
294         private final float[] mCornerRadii;
295         private final Path mPath = new Path();
296 
ConcaveInfo()297         ConcaveInfo() {
298             mCornerRadii = new float[] {0, 0, 0, 0, 0, 0, 0, 0};
299         }
300 
setCornerRadius(float radius)301         public void setCornerRadius(float radius) {
302             mPathOverlap = radius;
303             mCornerRadii[0] = radius;
304             mCornerRadii[1] = radius;
305             mCornerRadii[2] = radius;
306             mCornerRadii[3] = radius;
307         }
308     }
309 }
310