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.Shader; 38 import android.graphics.drawable.Drawable; 39 import android.util.DisplayMetrics; 40 import android.util.FloatProperty; 41 import android.view.View; 42 import android.view.WindowInsets; 43 44 import com.android.launcher3.BaseDraggingActivity; 45 import com.android.launcher3.R; 46 import com.android.launcher3.ResourceUtils; 47 import com.android.launcher3.Utilities; 48 import com.android.launcher3.config.FeatureFlags; 49 import com.android.launcher3.util.DynamicResource; 50 import com.android.launcher3.util.Themes; 51 import com.android.systemui.plugins.ResourceProvider; 52 53 /** 54 * View scrim which draws behind hotseat and workspace 55 */ 56 public class SysUiScrim implements View.OnAttachStateChangeListener { 57 58 public static final FloatProperty<SysUiScrim> SYSUI_PROGRESS = 59 new FloatProperty<SysUiScrim>("sysUiProgress") { 60 @Override 61 public Float get(SysUiScrim scrim) { 62 return scrim.mSysUiProgress; 63 } 64 65 @Override 66 public void setValue(SysUiScrim scrim, float value) { 67 scrim.setSysUiProgress(value); 68 } 69 }; 70 71 private static final FloatProperty<SysUiScrim> SYSUI_ANIM_MULTIPLIER = 72 new FloatProperty<SysUiScrim>("sysUiAnimMultiplier") { 73 @Override 74 public Float get(SysUiScrim scrim) { 75 return scrim.mSysUiAnimMultiplier; 76 } 77 78 @Override 79 public void setValue(SysUiScrim scrim, float value) { 80 scrim.mSysUiAnimMultiplier = value; 81 scrim.reapplySysUiAlpha(); 82 } 83 }; 84 85 /** 86 * Receiver used to get a signal that the user unlocked their device. 87 */ 88 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 89 @Override 90 public void onReceive(Context context, Intent intent) { 91 final String action = intent.getAction(); 92 if (ACTION_SCREEN_OFF.equals(action)) { 93 mAnimateScrimOnNextDraw = true; 94 } else if (ACTION_USER_PRESENT.equals(action)) { 95 // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where 96 // the user unlocked and the Launcher is not in the foreground. 97 mAnimateScrimOnNextDraw = false; 98 } 99 } 100 }; 101 102 private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100; 103 private static final int ALPHA_MASK_HEIGHT_DP = 500; 104 private static final int ALPHA_MASK_BITMAP_DP = 200; 105 private static final int ALPHA_MASK_WIDTH_DP = 2; 106 107 private boolean mDrawTopScrim, mDrawBottomScrim, mDrawWallpaperScrim; 108 109 private final RectF mWallpaperScrimRect = new RectF(); 110 private final Paint mWallpaperScrimPaint = new Paint(); 111 private final RectF mFinalMaskRect = new RectF(); 112 private final Paint mBottomMaskPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 113 private final Bitmap mBottomMask; 114 private final int mMaskHeight; 115 116 private final View mRoot; 117 private final BaseDraggingActivity mActivity; 118 private final Drawable mTopScrim; 119 120 private float mSysUiProgress = 1; 121 private boolean mHideSysUiScrim; 122 123 private boolean mAnimateScrimOnNextDraw = false; 124 private float mSysUiAnimMultiplier = 1; 125 private int mWallpaperScrimMaxAlpha; 126 SysUiScrim(View view)127 public SysUiScrim(View view) { 128 mRoot = view; 129 mActivity = BaseDraggingActivity.fromContext(view.getContext()); 130 mMaskHeight = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_DP, 131 view.getResources().getDisplayMetrics()); 132 mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim); 133 mBottomMask = mTopScrim == null ? null : createDitheredAlphaMask(); 134 mHideSysUiScrim = mTopScrim == null; 135 136 mDrawWallpaperScrim = FeatureFlags.ENABLE_WALLPAPER_SCRIM.get() 137 && !Themes.getAttrBoolean(view.getContext(), R.attr.isMainColorDark) 138 && !Themes.getAttrBoolean(view.getContext(), R.attr.isWorkspaceDarkText); 139 ResourceProvider rp = DynamicResource.provider(view.getContext()); 140 int wallpaperScrimColor = rp.getColor(R.color.wallpaper_scrim_color); 141 mWallpaperScrimMaxAlpha = Color.alpha(wallpaperScrimColor); 142 mWallpaperScrimPaint.setColor(wallpaperScrimColor); 143 144 view.addOnAttachStateChangeListener(this); 145 } 146 147 /** 148 * Draw the top and bottom scrims 149 */ draw(Canvas canvas)150 public void draw(Canvas canvas) { 151 if (!mHideSysUiScrim) { 152 if (mSysUiProgress <= 0) { 153 mAnimateScrimOnNextDraw = false; 154 return; 155 } 156 157 if (mAnimateScrimOnNextDraw) { 158 mSysUiAnimMultiplier = 0; 159 reapplySysUiAlphaNoInvalidate(); 160 161 ObjectAnimator oa = createSysuiMultiplierAnim(1); 162 oa.setDuration(600); 163 oa.setStartDelay(mActivity.getWindow().getTransitionBackgroundFadeDuration()); 164 oa.start(); 165 mAnimateScrimOnNextDraw = false; 166 } 167 168 if (mDrawWallpaperScrim) { 169 canvas.drawRect(mWallpaperScrimRect, mWallpaperScrimPaint); 170 } 171 if (mDrawTopScrim) { 172 mTopScrim.draw(canvas); 173 } 174 if (mDrawBottomScrim) { 175 canvas.drawBitmap(mBottomMask, null, mFinalMaskRect, mBottomMaskPaint); 176 } 177 } 178 } 179 180 /** 181 * @return an ObjectAnimator that controls the fade in/out of the sys ui scrim. 182 */ createSysuiMultiplierAnim(float... values)183 public ObjectAnimator createSysuiMultiplierAnim(float... values) { 184 ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, values); 185 anim.setAutoCancel(true); 186 return anim; 187 } 188 189 /** 190 * Determines whether to draw the top and/or bottom scrim based on new insets. 191 */ onInsetsChanged(Rect insets)192 public void onInsetsChanged(Rect insets) { 193 mDrawTopScrim = mTopScrim != null && insets.top > 0; 194 mDrawBottomScrim = mBottomMask != null 195 && !mActivity.getDeviceProfile().isVerticalBarLayout() 196 && hasBottomNavButtons(); 197 } 198 hasBottomNavButtons()199 private boolean hasBottomNavButtons() { 200 if (Utilities.ATLEAST_Q && mActivity.getRootView() != null 201 && mActivity.getRootView().getRootWindowInsets() != null) { 202 WindowInsets windowInsets = mActivity.getRootView().getRootWindowInsets(); 203 return windowInsets.getTappableElementInsets().bottom > 0; 204 } 205 return true; 206 } 207 208 @Override onViewAttachedToWindow(View view)209 public void onViewAttachedToWindow(View view) { 210 if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) { 211 IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF); 212 filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone 213 mRoot.getContext().registerReceiver(mReceiver, filter); 214 } 215 } 216 217 @Override onViewDetachedFromWindow(View view)218 public void onViewDetachedFromWindow(View view) { 219 if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) { 220 mRoot.getContext().unregisterReceiver(mReceiver); 221 } 222 } 223 224 /** 225 * Set the width and height of the view being scrimmed 226 * @param w 227 * @param h 228 */ setSize(int w, int h)229 public void setSize(int w, int h) { 230 if (mTopScrim != null) { 231 mTopScrim.setBounds(0, 0, w, h); 232 mFinalMaskRect.set(0, h - mMaskHeight, w, h); 233 } 234 mWallpaperScrimRect.set(0, 0, w, h); 235 } 236 setSysUiProgress(float progress)237 private void setSysUiProgress(float progress) { 238 if (progress != mSysUiProgress) { 239 mSysUiProgress = progress; 240 reapplySysUiAlpha(); 241 } 242 } 243 reapplySysUiAlpha()244 private void reapplySysUiAlpha() { 245 reapplySysUiAlphaNoInvalidate(); 246 if (!mHideSysUiScrim) { 247 mRoot.invalidate(); 248 } 249 } 250 reapplySysUiAlphaNoInvalidate()251 private void reapplySysUiAlphaNoInvalidate() { 252 float factor = mSysUiProgress * mSysUiAnimMultiplier; 253 mBottomMaskPaint.setAlpha(Math.round(MAX_HOTSEAT_SCRIM_ALPHA * factor)); 254 if (mTopScrim != null) { 255 mTopScrim.setAlpha(Math.round(255 * factor)); 256 } 257 mWallpaperScrimPaint.setAlpha(Math.round(mWallpaperScrimMaxAlpha * factor)); 258 } 259 createDitheredAlphaMask()260 private Bitmap createDitheredAlphaMask() { 261 DisplayMetrics dm = mActivity.getResources().getDisplayMetrics(); 262 int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm); 263 int gradientHeight = ResourceUtils.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm); 264 Bitmap dst = Bitmap.createBitmap(width, mMaskHeight, Bitmap.Config.ALPHA_8); 265 Canvas c = new Canvas(dst); 266 Paint paint = new Paint(Paint.DITHER_FLAG); 267 LinearGradient lg = new LinearGradient(0, 0, 0, gradientHeight, 268 new int[]{ 269 0x00FFFFFF, 270 setColorAlphaBound(Color.WHITE, (int) (0xFF * 0.95)), 271 0xFFFFFFFF}, 272 new float[]{0f, 0.8f, 1f}, 273 Shader.TileMode.CLAMP); 274 paint.setShader(lg); 275 c.drawRect(0, 0, width, gradientHeight, paint); 276 return dst; 277 } 278 } 279