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