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