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 static java.lang.Float.isNaN;
20 
21 import android.annotation.NonNull;
22 import android.content.Context;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.PorterDuff;
26 import android.graphics.PorterDuff.Mode;
27 import android.graphics.PorterDuffColorFilter;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.view.View;
31 
32 import androidx.core.graphics.ColorUtils;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.internal.colorextraction.ColorExtractor;
36 import com.android.internal.colorextraction.drawable.ScrimDrawable;
37 
38 /**
39  * A view which can draw a scrim
40  */
41 public class ScrimView extends View {
42     private final ColorExtractor.GradientColors mColors;
43     private float mViewAlpha = 1.0f;
44     private Drawable mDrawable;
45     private PorterDuffColorFilter mColorFilter;
46     private int mTintColor;
47     private Runnable mChangeRunnable;
48 
ScrimView(Context context)49     public ScrimView(Context context) {
50         this(context, null);
51     }
52 
ScrimView(Context context, AttributeSet attrs)53     public ScrimView(Context context, AttributeSet attrs) {
54         this(context, attrs, 0);
55     }
56 
ScrimView(Context context, AttributeSet attrs, int defStyleAttr)57     public ScrimView(Context context, AttributeSet attrs, int defStyleAttr) {
58         this(context, attrs, defStyleAttr, 0);
59     }
60 
ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)61     public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
62         super(context, attrs, defStyleAttr, defStyleRes);
63 
64         mDrawable = new ScrimDrawable();
65         mDrawable.setCallback(this);
66         mColors = new ColorExtractor.GradientColors();
67         updateColorWithTint(false);
68     }
69 
70     @Override
onDraw(Canvas canvas)71     protected void onDraw(Canvas canvas) {
72         if (mDrawable.getAlpha() > 0) {
73             mDrawable.draw(canvas);
74         }
75     }
76 
setDrawable(Drawable drawable)77     public void setDrawable(Drawable drawable) {
78         mDrawable = drawable;
79         mDrawable.setCallback(this);
80         mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom());
81         mDrawable.setAlpha((int) (255 * mViewAlpha));
82         invalidate();
83     }
84 
85     @Override
invalidateDrawable(@onNull Drawable drawable)86     public void invalidateDrawable(@NonNull Drawable drawable) {
87         super.invalidateDrawable(drawable);
88         if (drawable == mDrawable) {
89             invalidate();
90         }
91     }
92 
93     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)94     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
95         super.onLayout(changed, left, top, right, bottom);
96         if (changed) {
97             mDrawable.setBounds(left, top, right, bottom);
98             invalidate();
99         }
100     }
101 
setColors(@onNull ColorExtractor.GradientColors colors)102     public void setColors(@NonNull ColorExtractor.GradientColors colors) {
103         setColors(colors, false);
104     }
105 
setColors(@onNull ColorExtractor.GradientColors colors, boolean animated)106     public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) {
107         if (colors == null) {
108             throw new IllegalArgumentException("Colors cannot be null");
109         }
110         if (mColors.equals(colors)) {
111             return;
112         }
113         mColors.set(colors);
114         updateColorWithTint(animated);
115     }
116 
117     @VisibleForTesting
getDrawable()118     Drawable getDrawable() {
119         return mDrawable;
120     }
121 
getColors()122     public ColorExtractor.GradientColors getColors() {
123         return mColors;
124     }
125 
setTint(int color)126     public void setTint(int color) {
127         setTint(color, false);
128     }
129 
setTint(int color, boolean animated)130     public void setTint(int color, boolean animated) {
131         if (mTintColor == color) {
132             return;
133         }
134         mTintColor = color;
135         updateColorWithTint(animated);
136     }
137 
updateColorWithTint(boolean animated)138     private void updateColorWithTint(boolean animated) {
139         if (mDrawable instanceof ScrimDrawable) {
140             // Optimization to blend colors and avoid a color filter
141             ScrimDrawable drawable = (ScrimDrawable) mDrawable;
142             float tintAmount = Color.alpha(mTintColor) / 255f;
143             int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor,
144                     tintAmount);
145             drawable.setColor(mainTinted, animated);
146         } else {
147             boolean hasAlpha = Color.alpha(mTintColor) != 0;
148             if (hasAlpha) {
149                 PorterDuff.Mode targetMode = mColorFilter == null ? Mode.SRC_OVER :
150                     mColorFilter.getMode();
151                 if (mColorFilter == null || mColorFilter.getColor() != mTintColor) {
152                     mColorFilter = new PorterDuffColorFilter(mTintColor, targetMode);
153                 }
154             } else {
155                 mColorFilter = null;
156             }
157 
158             mDrawable.setColorFilter(mColorFilter);
159             mDrawable.invalidateSelf();
160         }
161 
162         if (mChangeRunnable != null) {
163             mChangeRunnable.run();
164         }
165     }
166 
getTint()167     public int getTint() {
168         return mTintColor;
169     }
170 
171     @Override
hasOverlappingRendering()172     public boolean hasOverlappingRendering() {
173         return false;
174     }
175 
176     /**
177      * It might look counterintuitive to have another method to set the alpha instead of
178      * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
179      * optimizing blend modes, so it makes sense.
180      *
181      * @param alpha Gradient alpha from 0 to 1.
182      */
setViewAlpha(float alpha)183     public void setViewAlpha(float alpha) {
184         if (isNaN(alpha)) {
185             throw new IllegalArgumentException("alpha cannot be NaN");
186         }
187         if (alpha != mViewAlpha) {
188             mViewAlpha = alpha;
189 
190             mDrawable.setAlpha((int) (255 * alpha));
191             if (mChangeRunnable != null) {
192                 mChangeRunnable.run();
193             }
194         }
195     }
196 
getViewAlpha()197     public float getViewAlpha() {
198         return mViewAlpha;
199     }
200 
setChangeRunnable(Runnable changeRunnable)201     public void setChangeRunnable(Runnable changeRunnable) {
202         mChangeRunnable = changeRunnable;
203     }
204 
205     @Override
canReceivePointerEvents()206     protected boolean canReceivePointerEvents() {
207         return false;
208     }
209 }
210