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 android.animation.ObjectAnimator; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.graphics.Bitmap; 28 import android.graphics.Canvas; 29 import android.graphics.Color; 30 import android.graphics.LinearGradient; 31 import android.graphics.Paint; 32 import android.graphics.Rect; 33 import android.graphics.RectF; 34 import android.graphics.Region; 35 import android.graphics.Shader; 36 import android.graphics.drawable.Drawable; 37 import android.support.v4.graphics.ColorUtils; 38 import android.util.DisplayMetrics; 39 import android.util.Property; 40 import android.view.View; 41 42 import com.android.launcher3.CellLayout; 43 import com.android.launcher3.Launcher; 44 import com.android.launcher3.R; 45 import com.android.launcher3.Utilities; 46 import com.android.launcher3.Workspace; 47 import com.android.launcher3.uioverrides.WallpaperColorInfo; 48 import com.android.launcher3.util.Themes; 49 50 /** 51 * View scrim which draws behind hotseat and workspace 52 */ 53 public class WorkspaceAndHotseatScrim implements 54 View.OnAttachStateChangeListener, WallpaperColorInfo.OnChangeListener { 55 56 public static Property<WorkspaceAndHotseatScrim, Float> SCRIM_PROGRESS = 57 new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "scrimProgress") { 58 @Override 59 public Float get(WorkspaceAndHotseatScrim scrim) { 60 return scrim.mScrimProgress; 61 } 62 63 @Override 64 public void set(WorkspaceAndHotseatScrim scrim, Float value) { 65 scrim.setScrimProgress(value); 66 } 67 }; 68 69 public static Property<WorkspaceAndHotseatScrim, Float> SYSUI_PROGRESS = 70 new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiProgress") { 71 @Override 72 public Float get(WorkspaceAndHotseatScrim scrim) { 73 return scrim.mSysUiProgress; 74 } 75 76 @Override 77 public void set(WorkspaceAndHotseatScrim scrim, Float value) { 78 scrim.setSysUiProgress(value); 79 } 80 }; 81 82 private static Property<WorkspaceAndHotseatScrim, Float> SYSUI_ANIM_MULTIPLIER = 83 new Property<WorkspaceAndHotseatScrim, Float>(Float.TYPE, "sysUiAnimMultiplier") { 84 @Override 85 public Float get(WorkspaceAndHotseatScrim scrim) { 86 return scrim.mSysUiAnimMultiplier; 87 } 88 89 @Override 90 public void set(WorkspaceAndHotseatScrim scrim, Float value) { 91 scrim.mSysUiAnimMultiplier = value; 92 scrim.reapplySysUiAlpha(); 93 } 94 }; 95 96 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 97 @Override 98 public void onReceive(Context context, Intent intent) { 99 final String action = intent.getAction(); 100 if (ACTION_SCREEN_OFF.equals(action)) { 101 mAnimateScrimOnNextDraw = true; 102 } else if (ACTION_USER_PRESENT.equals(action)) { 103 // ACTION_USER_PRESENT is sent after onStart/onResume. This covers the case where 104 // the user unlocked and the Launcher is not in the foreground. 105 mAnimateScrimOnNextDraw = false; 106 } 107 } 108 }; 109 110 private static final int DARK_SCRIM_COLOR = 0x55000000; 111 private static final int MAX_HOTSEAT_SCRIM_ALPHA = 100; 112 private static final int ALPHA_MASK_HEIGHT_DP = 500; 113 private static final int ALPHA_MASK_BITMAP_DP = 200; 114 private static final int ALPHA_MASK_WIDTH_DP = 2; 115 116 private final Rect mHighlightRect = new Rect(); 117 private final Launcher mLauncher; 118 private final WallpaperColorInfo mWallpaperColorInfo; 119 private final View mRoot; 120 121 private Workspace mWorkspace; 122 123 private final boolean mHasSysUiScrim; 124 private boolean mDrawTopScrim, mDrawBottomScrim; 125 126 private final RectF mFinalMaskRect = new RectF(); 127 private final Paint mBottomMaskPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 128 private final Bitmap mBottomMask; 129 private final int mMaskHeight; 130 131 private final Drawable mTopScrim; 132 133 private int mFullScrimColor; 134 135 private float mScrimProgress; 136 private int mScrimAlpha = 0; 137 138 private float mSysUiProgress = 1; 139 private boolean mHideSysUiScrim; 140 141 private boolean mAnimateScrimOnNextDraw = false; 142 private float mSysUiAnimMultiplier = 1; 143 144 public WorkspaceAndHotseatScrim(View view) { 145 mRoot = view; 146 mLauncher = Launcher.getLauncher(view.getContext()); 147 mWallpaperColorInfo = WallpaperColorInfo.getInstance(mLauncher); 148 149 mMaskHeight = Utilities.pxFromDp(ALPHA_MASK_BITMAP_DP, 150 view.getResources().getDisplayMetrics()); 151 152 mHasSysUiScrim = !mWallpaperColorInfo.supportsDarkText(); 153 if (mHasSysUiScrim) { 154 mTopScrim = Themes.getAttrDrawable(view.getContext(), R.attr.workspaceStatusBarScrim); 155 mBottomMask = createDitheredAlphaMask(); 156 } else { 157 mTopScrim = null; 158 mBottomMask = null; 159 } 160 161 view.addOnAttachStateChangeListener(this); 162 onExtractedColorsChanged(mWallpaperColorInfo); 163 } 164 165 public void setWorkspace(Workspace workspace) { 166 mWorkspace = workspace; 167 } 168 169 public void draw(Canvas canvas) { 170 // Draw the background below children. 171 if (mScrimAlpha > 0) { 172 // Update the scroll position first to ensure scrim cutout is in the right place. 173 mWorkspace.computeScrollWithoutInvalidation(); 174 CellLayout currCellLayout = mWorkspace.getCurrentDragOverlappingLayout(); 175 canvas.save(); 176 if (currCellLayout != null && currCellLayout != mLauncher.getHotseat().getLayout()) { 177 // Cut a hole in the darkening scrim on the page that should be highlighted, if any. 178 mLauncher.getDragLayer() 179 .getDescendantRectRelativeToSelf(currCellLayout, mHighlightRect); 180 canvas.clipRect(mHighlightRect, Region.Op.DIFFERENCE); 181 } 182 183 canvas.drawColor(ColorUtils.setAlphaComponent(mFullScrimColor, mScrimAlpha)); 184 canvas.restore(); 185 } 186 187 if (!mHideSysUiScrim && mHasSysUiScrim) { 188 if (mSysUiProgress <= 0) { 189 mAnimateScrimOnNextDraw = false; 190 return; 191 } 192 193 if (mAnimateScrimOnNextDraw) { 194 mSysUiAnimMultiplier = 0; 195 reapplySysUiAlphaNoInvalidate(); 196 197 ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, 1); 198 anim.setAutoCancel(true); 199 anim.setDuration(600); 200 anim.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration()); 201 anim.start(); 202 mAnimateScrimOnNextDraw = false; 203 } 204 205 if (mDrawTopScrim) { 206 mTopScrim.draw(canvas); 207 } 208 if (mDrawBottomScrim) { 209 canvas.drawBitmap(mBottomMask, null, mFinalMaskRect, mBottomMaskPaint); 210 } 211 } 212 } 213 214 public void onInsetsChanged(Rect insets) { 215 mDrawTopScrim = insets.top > 0; 216 mDrawBottomScrim = !mLauncher.getDeviceProfile().isVerticalBarLayout(); 217 } 218 219 private void setScrimProgress(float progress) { 220 if (mScrimProgress != progress) { 221 mScrimProgress = progress; 222 mScrimAlpha = Math.round(255 * mScrimProgress); 223 invalidate(); 224 } 225 } 226 227 @Override 228 public void onViewAttachedToWindow(View view) { 229 mWallpaperColorInfo.addOnChangeListener(this); 230 onExtractedColorsChanged(mWallpaperColorInfo); 231 232 if (mHasSysUiScrim) { 233 IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF); 234 filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone 235 mRoot.getContext().registerReceiver(mReceiver, filter); 236 } 237 } 238 239 @Override 240 public void onViewDetachedFromWindow(View view) { 241 mWallpaperColorInfo.removeOnChangeListener(this); 242 if (mHasSysUiScrim) { 243 mRoot.getContext().unregisterReceiver(mReceiver); 244 } 245 } 246 247 @Override 248 public void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo) { 249 // for super light wallpaper it needs to be darken for contrast to workspace 250 // for dark wallpapers the text is white so darkening works as well 251 mBottomMaskPaint.setColor(ColorUtils.compositeColors(DARK_SCRIM_COLOR, 252 wallpaperColorInfo.getMainColor())); 253 reapplySysUiAlpha(); 254 mFullScrimColor = wallpaperColorInfo.getMainColor(); 255 if (mScrimAlpha > 0) { 256 invalidate(); 257 } 258 } 259 260 public void setSize(int w, int h) { 261 if (mHasSysUiScrim) { 262 mTopScrim.setBounds(0, 0, w, h); 263 mFinalMaskRect.set(0, h - mMaskHeight, w, h); 264 } 265 } 266 267 public void hideSysUiScrim(boolean hideSysUiScrim) { 268 mHideSysUiScrim = hideSysUiScrim; 269 if (!hideSysUiScrim) { 270 mAnimateScrimOnNextDraw = true; 271 } 272 invalidate(); 273 } 274 275 private void setSysUiProgress(float progress) { 276 if (progress != mSysUiProgress) { 277 mSysUiProgress = progress; 278 reapplySysUiAlpha(); 279 } 280 } 281 282 private void reapplySysUiAlpha() { 283 if (mHasSysUiScrim) { 284 reapplySysUiAlphaNoInvalidate(); 285 if (!mHideSysUiScrim) { 286 invalidate(); 287 } 288 } 289 } 290 291 private void reapplySysUiAlphaNoInvalidate() { 292 float factor = mSysUiProgress * mSysUiAnimMultiplier; 293 mBottomMaskPaint.setAlpha(Math.round(MAX_HOTSEAT_SCRIM_ALPHA * factor)); 294 mTopScrim.setAlpha(Math.round(255 * factor)); 295 } 296 297 public void invalidate() { 298 mRoot.invalidate(); 299 } 300 301 public Bitmap createDitheredAlphaMask() { 302 DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics(); 303 int width = Utilities.pxFromDp(ALPHA_MASK_WIDTH_DP, dm); 304 int gradientHeight = Utilities.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm); 305 Bitmap dst = Bitmap.createBitmap(width, mMaskHeight, Bitmap.Config.ALPHA_8); 306 Canvas c = new Canvas(dst); 307 Paint paint = new Paint(Paint.DITHER_FLAG); 308 LinearGradient lg = new LinearGradient(0, 0, 0, gradientHeight, 309 new int[]{ 310 0x00FFFFFF, 311 ColorUtils.setAlphaComponent(Color.WHITE, (int) (0xFF * 0.95)), 312 0xFFFFFFFF}, 313 new float[]{0f, 0.8f, 1f}, 314 Shader.TileMode.CLAMP); 315 paint.setShader(lg); 316 c.drawRect(0, 0, width, gradientHeight, paint); 317 return dst; 318 } 319 } 320