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 
17 package com.android.launcher3.graphics;
18 
19 import static android.content.Intent.ACTION_SCREEN_OFF;
20 import static android.content.Intent.ACTION_USER_PRESENT;
21 
22 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
23 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
24 
25 import android.animation.ObjectAnimator;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.Color;
33 import android.graphics.LinearGradient;
34 import android.graphics.Paint;
35 import android.graphics.Rect;
36 import android.graphics.RectF;
37 import android.graphics.Region;
38 import android.graphics.Shader;
39 import android.graphics.drawable.Drawable;
40 import android.util.DisplayMetrics;
41 import android.util.FloatProperty;
42 import android.view.View;
43 import android.view.WindowInsets;
44 
45 import androidx.core.graphics.ColorUtils;
46 
47 import com.android.launcher3.CellLayout;
48 import com.android.launcher3.R;
49 import com.android.launcher3.ResourceUtils;
50 import com.android.launcher3.Utilities;
51 import com.android.launcher3.Workspace;
52 import com.android.launcher3.uioverrides.WallpaperColorInfo;
53 import com.android.launcher3.util.Themes;
54 
55 /**
56  * View scrim which draws behind hotseat and workspace
57  */
58 public class WorkspaceAndHotseatScrim extends Scrim {
59 
60     public static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_PROGRESS =
61             new FloatProperty<WorkspaceAndHotseatScrim>("sysUiProgress") {
62                 @Override
63                 public Float get(WorkspaceAndHotseatScrim scrim) {
64                     return scrim.mSysUiProgress;
65                 }
66 
67                 @Override
68                 public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
69                     scrim.setSysUiProgress(value);
70                 }
71             };
72 
73     private static final FloatProperty<WorkspaceAndHotseatScrim> SYSUI_ANIM_MULTIPLIER =
74             new FloatProperty<WorkspaceAndHotseatScrim>("sysUiAnimMultiplier") {
75                 @Override
76                 public Float get(WorkspaceAndHotseatScrim scrim) {
77                     return scrim.mSysUiAnimMultiplier;
78                 }
79 
80                 @Override
81                 public void setValue(WorkspaceAndHotseatScrim scrim, float value) {
82                     scrim.mSysUiAnimMultiplier = value;
83                     scrim.reapplySysUiAlpha();
84                 }
85             };
86 
87     /**
88      * Receiver used to get a signal that the user unlocked their device.
89      * @see KEYGUARD_ANIMATION For proper signal.
90      */
91     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
92         @Override
93         public void onReceive(Context context, Intent intent) {
94             final String action = intent.getAction();
95             if (ACTION_SCREEN_OFF.equals(action)) {
96                 mAnimateScrimOnNextDraw = true;
97             } else if (ACTION_USER_PRESENT.equals(action)) {
98                 // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where
99                 // the user unlocked and the Launcher is not in the foreground.
100                 mAnimateScrimOnNextDraw = false;
101             }
102         }
103     };
104 
105     private static final int DARK_SCRIM_COLOR = 0x55000000;
106     private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100;
107     private static final int ALPHA_MASK_HEIGHT_DP = 500;
108     private static final int ALPHA_MASK_BITMAP_DP = 200;
109     private static final int ALPHA_MASK_WIDTH_DP = 2;
110 
111     private final Rect mHighlightRect = new Rect();
112 
113     private Workspace mWorkspace;
114 
115     private boolean mDrawTopScrim, mDrawBottomScrim;
116 
117     private final RectF mFinalMaskRect = new RectF();
118     private final Paint mBottomMaskPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
119     private final Bitmap mBottomMask;
120     private final int mMaskHeight;
121 
122     private final Drawable mTopScrim;
123 
124     private float mSysUiProgress = 1;
125     private boolean mHideSysUiScrim;
126 
127     private boolean mAnimateScrimOnNextDraw = false;
128     private float mSysUiAnimMultiplier = 1;
129 
WorkspaceAndHotseatScrim(View view)130     public WorkspaceAndHotseatScrim(View view) {
131         super(view);
132 
133         mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP,
134                 view.getResources().getDisplayMetrics());
135         mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim);
136         mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask();
137         mHideSysUiScrim = mTopScrim == null;
138 
139         onExtractedColorsChanged(mWallpaperColorInfo);
140     }
141 
setWorkspace(Workspace workspace)142     public void setWorkspace(Workspace workspace)  {
143         mWorkspace = workspace;
144     }
145 
draw(Canvas canvas)146     public void draw(Canvas canvas) {
147         // Draw the background below children.
148         if (mScrimAlpha > 0) {
149             // Update the scroll position first to ensure scrim cutout is in the right place.
150             mWorkspace.computeScrollWithoutInvalidation();
151             CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout();
152             canvas.save();
153             if (currCellLayout != null && currCellLayout != mLauncher.getHotseat()) {
154                 // Cut a hole in the darkening scrim on the page that should be highlighted, if any.
155                 mLauncher.getDragLayer()
156                         .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect);
157                 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE);
158             }
159 
160             super.draw(canvas);
161             canvas.restore();
162         }
163 
164         if (!mHideSysUiScrim) {
165             if (mSysUiProgress <= 0) {
166                 mAnimateScrimOnNextDraw = false;
167                 return;
168             }
169 
170             if (mAnimateScrimOnNextDraw) {
171                 mSysUiAnimMultiplier = 0;
172                 reapplySysUiAlphaNoInvalidate();
173 
174                 ObjectAnimator oa = createSysuiMultiplierAnim(1);
175                 oa.setDuration(600);
176                 oa.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration());
177                 oa.start();
178                 mAnimateScrimOnNextDraw = false;
179             }
180 
181             if (mDrawTopScrim) {
182                 mTopScrim.draw(canvas);
183             }
184             if (mDrawBottomScrim) {
185                 canvas.drawBitmap(mBottomMask, null, mFinalMaskRect, mBottomMaskPaint);
186             }
187         }
188     }
189 
190     /**
191      * @return an ObjectAnimator that controls the fade in/out of the sys ui scrim.
192      */
createSysuiMultiplierAnim(float... values)193     public ObjectAnimator createSysuiMultiplierAnim(float... values) {
194         ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, values);
195         anim.setAutoCancel(true);
196         return anim;
197     }
198 
199     /**
200      * Determines whether to draw the top and/or bottom scrim based on new insets.
201      */
onInsetsChanged(Rect insets, boolean allowSysuiScrims)202     public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) {
203         mDrawTopScrim = allowSysuiScrims
204                 && mTopScrim != null
205                 && insets.top > 0;
206         mDrawBottomScrim = allowSysuiScrims
207                 && mBottomMask != null
208                 && !mLauncher.getDeviceProfile().isVerticalBarLayout()
209                 && hasBottomNavButtons();
210     }
211 
hasBottomNavButtons()212     private boolean hasBottomNavButtons() {
213         if (Utilities.ATLEAST_Q && mLauncher.getRootView() != null
214                 && mLauncher.getRootView().getRootWindowInsets() != null) {
215             WindowInsets windowInsets = mLauncher.getRootView().getRootWindowInsets();
216             return windowInsets.getTappableElementInsets().bottom > 0;
217         }
218         return true;
219     }
220 
221     @Override
onViewAttachedToWindow(View view)222     public void onViewAttachedToWindow(View view) {
223         super.onViewAttachedToWindow(view);
224 
225         if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
226             IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
227             filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
228             mRoot.getContext().registerReceiver(mReceiver, filter);
229         }
230     }
231 
232     @Override
onViewDetachedFromWindow(View view)233     public void onViewDetachedFromWindow(View view) {
234         super.onViewDetachedFromWindow(view);
235         if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
236             mRoot.getContext().unregisterReceiver(mReceiver);
237         }
238     }
239 
240     @Override
onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo)241     public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) {
242         // for super light wallpaper it needs to be darken for contrast to workspace
243         // for dark wallpapers the text is white so darkening works as well
244         mBottomMaskPaint.setColor(ColorUtils.compositeColors(DARK_SCRIM_COLOR,
245                 wallpaperColorInfo.getMainColor()));
246         reapplySysUiAlpha();
247         super.onExtractedColorsChanged(wallpaperColorInfo);
248     }
249 
setSize(int w, int h)250     public void setSize(int w, int h) {
251         if (mTopScrim != null) {
252             mTopScrim.setBounds(0, 0, w, h);
253             mFinalMaskRect.set(0, h - mMaskHeight, w, h);
254         }
255     }
256 
setSysUiProgress(float progress)257     private void setSysUiProgress(float progress) {
258         if (progress != mSysUiProgress) {
259             mSysUiProgress = progress;
260             reapplySysUiAlpha();
261         }
262     }
263 
reapplySysUiAlpha()264     private void reapplySysUiAlpha() {
265         reapplySysUiAlphaNoInvalidate();
266         if (!mHideSysUiScrim) {
267             invalidate();
268         }
269     }
270 
reapplySysUiAlphaNoInvalidate()271     private void reapplySysUiAlphaNoInvalidate() {
272         float factor = mSysUiProgress * mSysUiAnimMultiplier;
273         mBottomMaskPaint.setAlpha(Math.round(MAX_HOTSEAT_SCRIM_ALPHA * factor));
274         if (mTopScrim != null) {
275             mTopScrim.setAlpha(Math.round(255 * factor));
276         }
277     }
278 
createDitheredAlphaMask()279     private Bitmap createDitheredAlphaMask() {
280         DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
281         int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
282         int gradientHeight = ResourceUtils.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
283         Bitmap dst = Bitmap.createBitmap(width, mMaskHeight, Bitmap.Config.ALPHA_8);
284         Canvas c = new Canvas(dst);
285         Paint paint = new Paint(Paint.DITHER_FLAG);
286         LinearGradient lg = new LinearGradient(0, 0, 0, gradientHeight,
287                 new int[]{
288                         0x00FFFFFF,
289                         setColorAlphaBound(Color.WHITE, (int) (0xFF * 0.95)),
290                         0xFFFFFFFF},
291                 new float[]{0f, 0.8f, 1f},
292                 Shader.TileMode.CLAMP);
293         paint.setShader(lg);
294         c.drawRect(0, 0, width, gradientHeight, paint);
295         return dst;
296     }
297 }
298