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 package com.android.launcher3.graphics; 17 18 import static android.graphics.Paint.DITHER_FLAG; 19 import static android.graphics.Paint.FILTER_BITMAP_FLAG; 20 21 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; 22 23 import android.animation.ObjectAnimator; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.LinearGradient; 27 import android.graphics.Paint; 28 import android.graphics.Rect; 29 import android.graphics.RectF; 30 import android.graphics.Shader; 31 import android.util.DisplayMetrics; 32 import android.view.View; 33 34 import androidx.annotation.ColorInt; 35 import androidx.annotation.VisibleForTesting; 36 37 import com.android.launcher3.BaseDraggingActivity; 38 import com.android.launcher3.DeviceProfile; 39 import com.android.launcher3.R; 40 import com.android.launcher3.anim.AnimatedFloat; 41 import com.android.launcher3.testing.shared.ResourceUtils; 42 import com.android.launcher3.util.ScreenOnTracker; 43 import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener; 44 import com.android.launcher3.util.Themes; 45 46 /** 47 * View scrim which draws behind hotseat and workspace 48 */ 49 public class SysUiScrim implements View.OnAttachStateChangeListener { 50 51 /** 52 * Receiver used to get a signal that the user unlocked their device. 53 */ 54 private final ScreenOnListener mScreenOnListener = new ScreenOnListener() { 55 @Override 56 public void onScreenOnChanged(boolean isOn) { 57 if (!isOn) { 58 mAnimateScrimOnNextDraw = true; 59 } 60 } 61 62 @Override 63 public void onUserPresent() { 64 // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where 65 // the user unlocked and the Launcher is not in the foreground. 66 mAnimateScrimOnNextDraw = false; 67 } 68 }; 69 70 private static final int MAX_SYSUI_SCRIM_ALPHA = 255; 71 private static final int ALPHA_MASK_BITMAP_WIDTH_DP = 2; 72 73 private static final int BOTTOM_MASK_HEIGHT_DP = 200; 74 private static final int TOP_MASK_HEIGHT_DP = 70; 75 76 private boolean mDrawTopScrim, mDrawBottomScrim; 77 78 private final RectF mTopMaskRect = new RectF(); 79 private final Paint mTopMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG); 80 private final Bitmap mTopMaskBitmap; 81 private final int mTopMaskHeight; 82 83 private final RectF mBottomMaskRect = new RectF(); 84 private final Paint mBottomMaskPaint = new Paint(FILTER_BITMAP_FLAG | DITHER_FLAG); 85 private final Bitmap mBottomMaskBitmap; 86 private final int mBottomMaskHeight; 87 88 private final View mRoot; 89 private final BaseDraggingActivity mActivity; 90 private final boolean mHideSysUiScrim; 91 private boolean mSkipScrimAnimationForTest = false; 92 93 private boolean mAnimateScrimOnNextDraw = false; 94 private final AnimatedFloat mSysUiAnimMultiplier = new AnimatedFloat(this::reapplySysUiAlpha); 95 private final AnimatedFloat mSysUiProgress = new AnimatedFloat(this::reapplySysUiAlpha); 96 SysUiScrim(View view)97 public SysUiScrim(View view) { 98 mRoot = view; 99 mActivity = BaseDraggingActivity.fromContext(view.getContext()); 100 DisplayMetrics dm = mActivity.getResources().getDisplayMetrics(); 101 102 mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm); 103 mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm); 104 mHideSysUiScrim = Themes.getAttrBoolean(view.getContext(), R.attr.isWorkspaceDarkText); 105 106 mTopMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mTopMaskHeight, 107 new int[]{0x3DFFFFFF, 0x0AFFFFFF, 0x00FFFFFF}, 108 new float[]{0f, 0.7f, 1f}); 109 mTopMaskPaint.setColor(0xFF222222); 110 mBottomMaskBitmap = mHideSysUiScrim ? null : createDitheredAlphaMask(mBottomMaskHeight, 111 new int[]{0x00FFFFFF, 0x2FFFFFFF}, 112 new float[]{0f, 1f}); 113 114 if (!KEYGUARD_ANIMATION.get() && !mHideSysUiScrim) { 115 view.addOnAttachStateChangeListener(this); 116 } 117 } 118 119 /** 120 * Draw the top and bottom scrims 121 */ draw(Canvas canvas)122 public void draw(Canvas canvas) { 123 if (!mHideSysUiScrim) { 124 if (mSysUiProgress.value <= 0) { 125 mAnimateScrimOnNextDraw = false; 126 return; 127 } 128 129 if (mAnimateScrimOnNextDraw) { 130 mSysUiAnimMultiplier.value = 0; 131 reapplySysUiAlphaNoInvalidate(); 132 133 ObjectAnimator oa = mSysUiAnimMultiplier.animateToValue(1); 134 oa.setDuration(600); 135 oa.setStartDelay(mActivity.getWindow().getTransitionBackgroundFadeDuration()); 136 oa.start(); 137 mAnimateScrimOnNextDraw = false; 138 } 139 140 if (mDrawTopScrim) { 141 canvas.drawBitmap(mTopMaskBitmap, null, mTopMaskRect, mTopMaskPaint); 142 } 143 if (mDrawBottomScrim) { 144 canvas.drawBitmap(mBottomMaskBitmap, null, mBottomMaskRect, mBottomMaskPaint); 145 } 146 } 147 } 148 149 /** 150 * Returns the sysui multiplier property for controlling fade in/out of the scrim 151 */ getSysUIMultiplier()152 public AnimatedFloat getSysUIMultiplier() { 153 return mSysUiAnimMultiplier; 154 } 155 156 /** 157 * Returns the sysui progress property for controlling fade in/out of the scrim 158 */ getSysUIProgress()159 public AnimatedFloat getSysUIProgress() { 160 return mSysUiProgress; 161 } 162 163 /** 164 * Determines whether to draw the top and/or bottom scrim based on new insets. 165 * 166 * In order for the bottom scrim to be drawn this 3 condition should be meet at the same time: 167 * the device is in 3 button navigation, the taskbar is not present and the Hotseat is 168 * horizontal 169 */ onInsetsChanged(Rect insets)170 public void onInsetsChanged(Rect insets) { 171 DeviceProfile dp = mActivity.getDeviceProfile(); 172 mDrawTopScrim = insets.top > 0; 173 mDrawBottomScrim = !dp.isVerticalBarLayout() && !dp.isGestureMode && !dp.isTaskbarPresent; 174 } 175 176 @Override onViewAttachedToWindow(View view)177 public void onViewAttachedToWindow(View view) { 178 ScreenOnTracker.INSTANCE.get(mActivity).addListener(mScreenOnListener); 179 } 180 181 @Override onViewDetachedFromWindow(View view)182 public void onViewDetachedFromWindow(View view) { 183 ScreenOnTracker.INSTANCE.get(mActivity).removeListener(mScreenOnListener); 184 } 185 186 /** 187 * Set the width and height of the view being scrimmed 188 */ setSize(int w, int h)189 public void setSize(int w, int h) { 190 mTopMaskRect.set(0, 0, w, mTopMaskHeight); 191 mBottomMaskRect.set(0, h - mBottomMaskHeight, w, h); 192 } 193 194 /** 195 * Sets whether the SysUiScrim should hide for testing. 196 */ 197 @VisibleForTesting skipScrimAnimation()198 public void skipScrimAnimation() { 199 mSkipScrimAnimationForTest = true; 200 reapplySysUiAlpha(); 201 } 202 reapplySysUiAlpha()203 private void reapplySysUiAlpha() { 204 reapplySysUiAlphaNoInvalidate(); 205 if (!mHideSysUiScrim) { 206 mRoot.invalidate(); 207 } 208 } 209 reapplySysUiAlphaNoInvalidate()210 private void reapplySysUiAlphaNoInvalidate() { 211 float factor = mSysUiProgress.value * mSysUiAnimMultiplier.value; 212 if (mSkipScrimAnimationForTest) factor = 1f; 213 mBottomMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor)); 214 mTopMaskPaint.setAlpha(Math.round(MAX_SYSUI_SCRIM_ALPHA * factor)); 215 } 216 createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions)217 private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) { 218 DisplayMetrics dm = mActivity.getResources().getDisplayMetrics(); 219 int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm); 220 Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8); 221 Canvas c = new Canvas(dst); 222 Paint paint = new Paint(DITHER_FLAG); 223 LinearGradient lg = new LinearGradient(0, 0, 0, height, 224 colors, positions, Shader.TileMode.CLAMP); 225 paint.setShader(lg); 226 c.drawPaint(paint); 227 return dst; 228 } 229 } 230