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