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