• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.graphics;
17 
18 import static android.graphics.Paint.DITHER_FLAG;
19 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
20 
21 import android.animation.ObjectAnimator;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.LinearGradient;
25 import android.graphics.Paint;
26 import android.graphics.Rect;
27 import android.graphics.RectF;
28 import android.graphics.Shader;
29 import android.util.DisplayMetrics;
30 import android.view.View;
31 
32 import androidx.annotation.ColorInt;
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.launcher3.DeviceProfile;
36 import com.android.launcher3.R;
37 import com.android.launcher3.anim.AnimatedFloat;
38 import com.android.launcher3.statemanager.StatefulContainer;
39 import com.android.launcher3.testing.shared.ResourceUtils;
40 import com.android.launcher3.util.ScreenOnTracker;
41 import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
42 import com.android.launcher3.util.Themes;
43 import com.android.launcher3.views.ActivityContext;
44 
45 /**
46  * View scrim which draws behind hotseat and workspace
47  */
48 public class SysUiScrim implements View.OnAttachStateChangeListener {
49 
50     /**
51      * Receiver used to get a signal that the user unlocked their device.
52      */
53     private final ScreenOnListener mScreenOnListener = new ScreenOnListener() {
54         @Override
55         public void onScreenOnChanged(boolean isOn) {
56             if (!isOn) {
57                 mAnimateScrimOnNextDraw = true;
58             }
59         }
60 
61         @Override
62         public void onUserPresent() {
63             // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
64             // the user unlocked and the Launcher is not in the foreground.
65             mAnimateScrimOnNextDraw = false;
66         }
67     };
68 
69     private static final int MAX_SYSUI_SCRIM_ALPHA = 255;
70     private static final int ALPHA_MASK_BITMAP_WIDTH_DP = 2;
71 
72     private static final int BOTTOM_MASK_HEIGHT_DP = 200;
73     private static final int TOP_MASK_HEIGHT_DP = 70;
74 
75     private boolean mDrawTopScrim, mDrawBottomScrim;
76 
77     private final RectF mTopMaskRect = new RectF();
78     private final Paint mTopMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG);
79     private final Bitmap mTopMaskBitmap;
80     private final int mTopMaskHeight;
81 
82     private final RectF mBottomMaskRect = new RectF();
83     private final Paint mBottomMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG);
84     private final Bitmap mBottomMaskBitmap;
85     private final int mBottomMaskHeight;
86 
87     private final View mRoot;
88     private final StatefulContainer mContainer;
89     private final boolean mHideSysUiScrim;
90     private boolean mSkipScrimAnimationForTest = false;
91 
92     private boolean mAnimateScrimOnNextDraw = false;
93     private final AnimatedFloat mSysUiAnimMultiplier = new AnimatedFloat(this::reapplySysUiAlpha);
94     private final AnimatedFloat mSysUiProgress = new AnimatedFloat(this::reapplySysUiAlpha);
95 
SysUiScrim(View view)96     public SysUiScrim(View view) {
97         mRoot = view;
98         mContainer = ActivityContext.lookupContext(view.getContext());
99         DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
100 
101         mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
102         mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
103         mHideSysUiScrim = Themes.getAttrBoolean(view.getContext(), R.attr.isWorkspaceDarkText);
104 
105         mTopMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mTopMaskHeight,
106                 new int[]{0x3DFFFFFF, 0x0AFFFFFF, 0x00FFFFFF},
107                 new float[]{0f, 0.7f, 1f});
108         mTopMaskPaint.setColor(0xFF222222);
109         mBottomMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mBottomMaskHeight,
110                 new int[]{0x00FFFFFF, 0x2FFFFFFF},
111                 new float[]{0f, 1f});
112 
113         if (!mHideSysUiScrim) {
114             view.addOnAttachStateChangeListener(this);
115         }
116     }
117 
118     /**
119      * Draw the top and bottom scrims
120      */
draw(Canvas canvas)121     public void draw(Canvas canvas) {
122         if (!mHideSysUiScrim) {
123             if (mSysUiProgress.value <= 0) {
124                 mAnimateScrimOnNextDraw = false;
125                 return;
126             }
127 
128             if (mAnimateScrimOnNextDraw) {
129                 mSysUiAnimMultiplier.value = 0;
130                 reapplySysUiAlphaNoInvalidate();
131 
132                 ObjectAnimator oa = mSysUiAnimMultiplier.animateToValue(1);
133                 oa.setDuration(600);
134                 oa.setStartDelay(mContainer.getWindow().getTransitionBackgroundFadeDuration());
135                 oa.start();
136                 mAnimateScrimOnNextDraw = false;
137             }
138 
139             if (mDrawTopScrim) {
140                 canvas.drawBitmap(mTopMaskBitmap, null, mTopMaskRect, mTopMaskPaint);
141             }
142             if (mDrawBottomScrim) {
143                 canvas.drawBitmap(mBottomMaskBitmap, null, mBottomMaskRect, mBottomMaskPaint);
144             }
145         }
146     }
147 
148     /**
149      * Returns the sysui multiplier property for controlling fade in/out of the scrim
150      */
getSysUIMultiplier()151     public AnimatedFloat getSysUIMultiplier() {
152         return mSysUiAnimMultiplier;
153     }
154 
155     /**
156      * Returns the sysui progress property for controlling fade in/out of the scrim
157      */
getSysUIProgress()158     public AnimatedFloat getSysUIProgress() {
159         return mSysUiProgress;
160     }
161 
162     /**
163      * Determines whether to draw the top and/or bottom scrim based on new insets.
164      *
165      * In order for the bottom scrim to be drawn this 3 condition should be meet at the same time:
166      * the device is in 3 button navigation, the taskbar is not present and the Hotseat is
167      * horizontal
168      */
onInsetsChanged(Rect insets)169     public void onInsetsChanged(Rect insets) {
170         DeviceProfile dp = mContainer.getDeviceProfile();
171         mDrawTopScrim = insets.top > 0;
172         mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.isGestureMode && !dp.isTaskbarPresent;
173     }
174 
175     @Override
onViewAttachedToWindow(View view)176     public void onViewAttachedToWindow(View view) {
177         ScreenOnTracker.INSTANCE.get(mContainer.asContext()).addListener(mScreenOnListener);
178     }
179 
180     @Override
onViewDetachedFromWindow(View view)181     public void onViewDetachedFromWindow(View view) {
182         ScreenOnTracker.INSTANCE.get(mContainer.asContext()).removeListener(mScreenOnListener);
183     }
184 
185     /**
186      * Set the width and height of the view being scrimmed
187      */
setSize(int w, int h)188     public void setSize(int w, int h) {
189         mTopMaskRect.set(0, 0, w, mTopMaskHeight);
190         mBottomMaskRect.set(0, h - mBottomMaskHeight, w, h);
191     }
192 
193     /**
194      * Sets whether the SysUiScrim should hide for testing.
195      */
196     @VisibleForTesting
skipScrimAnimation()197     public void skipScrimAnimation() {
198         mSkipScrimAnimationForTest = true;
199         reapplySysUiAlpha();
200     }
201 
reapplySysUiAlpha()202     private void reapplySysUiAlpha() {
203         reapplySysUiAlphaNoInvalidate();
204         if (!mHideSysUiScrim) {
205             mRoot.invalidate();
206         }
207     }
208 
reapplySysUiAlphaNoInvalidate()209     private void reapplySysUiAlphaNoInvalidate() {
210         float factor = mSysUiProgress.value * mSysUiAnimMultiplier.value;
211         if (mSkipScrimAnimationForTest) factor = 1f;
212         mBottomMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
213         mTopMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor));
214     }
215 
createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions)216     private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) {
217         DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
218         int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm);
219         Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
220         Canvas c = new Canvas(dst);
221         Paint paint = new Paint(DITHER_FLAG);
222         LinearGradient lg = new LinearGradient(0, 0, 0, height,
223                 colors, positions, Shader.TileMode.CLAMP);
224         paint.setShader(lg);
225         c.drawPaint(paint);
226         return dst;
227     }
228 }
229