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