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