1 /*
2  * Copyright (C) 2018 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 package com.android.launcher3.views;
17 
18 import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
19 
20 import android.content.Context;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Rect;
24 import android.graphics.drawable.ColorDrawable;
25 import android.util.AttributeSet;
26 import android.view.View;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Px;
30 import androidx.core.graphics.ColorUtils;
31 
32 import com.android.launcher3.BaseActivity;
33 import com.android.launcher3.Insettable;
34 import com.android.launcher3.util.SystemUiController;
35 
36 import java.util.ArrayList;
37 
38 /**
39  * Simple scrim which draws a flat color
40  */
41 public class ScrimView extends View implements Insettable {
42     private static final float STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD = 0.9f;
43 
44     private final ArrayList<Runnable> mOpaquenessListeners = new ArrayList<>(1);
45     private SystemUiController mSystemUiController;
46     private ScrimDrawingController mDrawingController;
47     private int mBackgroundColor;
48     private boolean mIsVisible = true;
49     private boolean mLastDispatchedOpaqueness;
50     private float mHeaderScale = 1f;
51 
ScrimView(Context context, AttributeSet attrs)52     public ScrimView(Context context, AttributeSet attrs) {
53         super(context, attrs);
54         setFocusable(false);
55     }
56 
57     @Override
setInsets(Rect insets)58     public void setInsets(Rect insets) {
59     }
60 
61     @Override
hasOverlappingRendering()62     public boolean hasOverlappingRendering() {
63         return false;
64     }
65 
66     @Override
onSetAlpha(int alpha)67     protected boolean onSetAlpha(int alpha) {
68         updateSysUiColors();
69         dispatchVisibilityListenersIfNeeded();
70         return super.onSetAlpha(alpha);
71     }
72 
73     @Override
setBackgroundColor(int color)74     public void setBackgroundColor(int color) {
75         mBackgroundColor = color;
76         updateSysUiColors();
77         dispatchVisibilityListenersIfNeeded();
78         super.setBackgroundColor(color);
79     }
80 
getBackgroundColor()81     public int getBackgroundColor() {
82         return mBackgroundColor;
83     }
84 
85     @Override
onVisibilityAggregated(boolean isVisible)86     public void onVisibilityAggregated(boolean isVisible) {
87         super.onVisibilityAggregated(isVisible);
88         mIsVisible = isVisible;
89         dispatchVisibilityListenersIfNeeded();
90     }
91 
isFullyOpaque()92     public boolean isFullyOpaque() {
93         return mIsVisible && getAlpha() == 1 && Color.alpha(mBackgroundColor) == 255;
94     }
95 
96     @Override
onDraw(Canvas canvas)97     protected void onDraw(Canvas canvas) {
98         super.onDraw(canvas);
99         if (mDrawingController != null) {
100             mDrawingController.drawOnScrimWithScale(canvas, mHeaderScale);
101         }
102     }
103 
104     /** Set scrim header's scale and bottom offset. */
setScrimHeaderScale(float scale)105     public void setScrimHeaderScale(float scale) {
106         boolean hasChanged = mHeaderScale != scale;
107         mHeaderScale = scale;
108         if (hasChanged) {
109             invalidate();
110         }
111     }
112 
113     @Override
onVisibilityChanged(View changedView, int visibility)114     protected void onVisibilityChanged(View changedView, int visibility) {
115         super.onVisibilityChanged(changedView, visibility);
116         updateSysUiColors();
117     }
118 
updateSysUiColors()119     private void updateSysUiColors() {
120         // Use a light system UI (dark icons) if all apps is behind at least half of the
121         // status bar.
122         final float threshold = STATUS_BAR_COLOR_FORCE_UPDATE_THRESHOLD;
123         boolean forceChange = getVisibility() == VISIBLE
124                 && getAlpha() > threshold
125                 && (Color.alpha(mBackgroundColor) / 255f) > threshold;
126         if (forceChange) {
127             getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !isScrimDark());
128         } else {
129             getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0);
130         }
131     }
132 
dispatchVisibilityListenersIfNeeded()133     private void dispatchVisibilityListenersIfNeeded() {
134         boolean fullyOpaque = isFullyOpaque();
135         if (mLastDispatchedOpaqueness == fullyOpaque) {
136             return;
137         }
138         mLastDispatchedOpaqueness = fullyOpaque;
139         for (int i = 0; i < mOpaquenessListeners.size(); i++) {
140             mOpaquenessListeners.get(i).run();
141         }
142     }
143 
getSystemUiController()144     private SystemUiController getSystemUiController() {
145         if (mSystemUiController == null) {
146             mSystemUiController = BaseActivity.fromContext(getContext()).getSystemUiController();
147         }
148         return mSystemUiController;
149     }
150 
isScrimDark()151     private boolean isScrimDark() {
152         if (!(getBackground() instanceof ColorDrawable)) {
153             throw new IllegalStateException(
154                     "ScrimView must have a ColorDrawable background, this one has: "
155                             + getBackground());
156         }
157         return ColorUtils.calculateLuminance(
158                 ((ColorDrawable) getBackground()).getColor()) < 0.5f;
159     }
160 
161     /**
162      * Sets drawing controller. Invalidates ScrimView if drawerController has changed.
163      */
setDrawingController(ScrimDrawingController drawingController)164     public void setDrawingController(ScrimDrawingController drawingController) {
165         if (mDrawingController != drawingController) {
166             mDrawingController = drawingController;
167             invalidate();
168         }
169     }
170 
171     /**
172      * Registers a listener to be notified of whether the scrim is occluding other UI elements.
173      * @see #isFullyOpaque()
174      */
addOpaquenessListener(@onNull Runnable listener)175     public void addOpaquenessListener(@NonNull Runnable listener) {
176         mOpaquenessListeners.add(listener);
177     }
178 
179     /**
180      * Removes previously registered listener.
181      * @see #addOpaquenessListener(Runnable)
182      */
removeOpaquenessListener(@onNull Runnable listener)183     public void removeOpaquenessListener(@NonNull Runnable listener) {
184         mOpaquenessListeners.remove(listener);
185     }
186 
187     /**
188      * A Utility interface allowing for other surfaces to draw on ScrimView
189      */
190     public interface ScrimDrawingController {
191 
192         /** Draw scrim view on canvas with scale. */
drawOnScrimWithScale(Canvas canvas, float scale)193         default void drawOnScrimWithScale(Canvas canvas, float scale) {
194             drawOnScrimWithScaleAndBottomOffset(canvas, scale, 0);
195         }
196 
197         /** Draw scrim view on canvas with bottomOffset. */
drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx)198         default void drawOnScrimWithBottomOffset(Canvas canvas, @Px int bottomOffsetPx) {
199             drawOnScrimWithScaleAndBottomOffset(canvas, 1f, bottomOffsetPx);
200         }
201 
202         /** Draw scrim view on canvas with scale and bottomOffset. */
drawOnScrimWithScaleAndBottomOffset( Canvas canvas, float scale, @Px int bottomOffsetPx)203         void drawOnScrimWithScaleAndBottomOffset(
204                 Canvas canvas, float scale, @Px int bottomOffsetPx);
205     }
206 }
207