1 /* 2 * Copyright (C) 2014 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.systemui.statusbar; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.annotation.NonNull; 23 import android.content.Context; 24 import android.content.res.Configuration; 25 import android.graphics.Canvas; 26 import android.graphics.Color; 27 import android.graphics.Point; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffColorFilter; 30 import android.graphics.PorterDuffXfermode; 31 import android.graphics.Rect; 32 import android.graphics.drawable.Drawable; 33 import android.support.v4.graphics.ColorUtils; 34 import android.util.AttributeSet; 35 import android.util.Log; 36 import android.view.Display; 37 import android.view.View; 38 import android.view.WindowManager; 39 import android.view.animation.Interpolator; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.colorextraction.ColorExtractor; 43 import com.android.internal.colorextraction.drawable.GradientDrawable; 44 import com.android.settingslib.Utils; 45 import com.android.systemui.Dependency; 46 import com.android.systemui.statusbar.policy.ConfigurationController; 47 48 /** 49 * A view which can draw a scrim 50 */ 51 public class ScrimView extends View implements ConfigurationController.ConfigurationListener { 52 private static final String TAG = "ScrimView"; 53 private final ColorExtractor.GradientColors mColors; 54 private int mDensity; 55 private boolean mDrawAsSrc; 56 private float mViewAlpha = 1.0f; 57 private ValueAnimator mAlphaAnimator; 58 private Rect mExcludedRect = new Rect(); 59 private boolean mHasExcludedArea; 60 private Drawable mDrawable; 61 private PorterDuffColorFilter mColorFilter; 62 private int mTintColor; 63 private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener = animation -> { 64 if (mDrawable == null) { 65 Log.w(TAG, "Trying to animate null drawable"); 66 return; 67 } 68 mDrawable.setAlpha((int) (255 * (float) animation.getAnimatedValue())); 69 }; 70 private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { 71 @Override 72 public void onAnimationEnd(Animator animation) { 73 mAlphaAnimator = null; 74 } 75 }; 76 private Runnable mChangeRunnable; 77 private int mCornerRadius; 78 ScrimView(Context context)79 public ScrimView(Context context) { 80 this(context, null); 81 } 82 ScrimView(Context context, AttributeSet attrs)83 public ScrimView(Context context, AttributeSet attrs) { 84 this(context, attrs, 0); 85 } 86 ScrimView(Context context, AttributeSet attrs, int defStyleAttr)87 public ScrimView(Context context, AttributeSet attrs, int defStyleAttr) { 88 this(context, attrs, defStyleAttr, 0); 89 } 90 ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)91 public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 92 super(context, attrs, defStyleAttr, defStyleRes); 93 94 mDrawable = new GradientDrawable(context); 95 mDrawable.setCallback(this); 96 mColors = new ColorExtractor.GradientColors(); 97 updateScreenSize(); 98 updateColorWithTint(false); 99 initView(); 100 final Configuration currentConfig = mContext.getResources().getConfiguration(); 101 mDensity = currentConfig.densityDpi; 102 } 103 initView()104 private void initView() { 105 mCornerRadius = getResources().getDimensionPixelSize( 106 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); 107 } 108 109 @Override onConfigurationChanged(Configuration newConfig)110 protected void onConfigurationChanged(Configuration newConfig) { 111 super.onConfigurationChanged(newConfig); 112 int densityDpi = newConfig.densityDpi; 113 if (mDensity != densityDpi) { 114 mDensity = densityDpi; 115 initView(); 116 } 117 } 118 119 @Override onAttachedToWindow()120 protected void onAttachedToWindow() { 121 super.onAttachedToWindow(); 122 123 // We need to know about configuration changes to update the gradient size 124 // since it's independent from view bounds. 125 ConfigurationController config = Dependency.get(ConfigurationController.class); 126 config.addCallback(this); 127 } 128 129 @Override onDetachedFromWindow()130 protected void onDetachedFromWindow() { 131 super.onDetachedFromWindow(); 132 133 ConfigurationController config = Dependency.get(ConfigurationController.class); 134 config.removeCallback(this); 135 } 136 137 @Override onDraw(Canvas canvas)138 protected void onDraw(Canvas canvas) { 139 if (mDrawAsSrc || mDrawable.getAlpha() > 0) { 140 if (!mHasExcludedArea) { 141 mDrawable.draw(canvas); 142 } else { 143 if (mExcludedRect.top > 0) { 144 canvas.save(); 145 canvas.clipRect(0, 0, getWidth(), mExcludedRect.top); 146 mDrawable.draw(canvas); 147 canvas.restore(); 148 } 149 if (mExcludedRect.left > 0) { 150 canvas.save(); 151 canvas.clipRect(0, mExcludedRect.top, mExcludedRect.left, 152 mExcludedRect.bottom); 153 mDrawable.draw(canvas); 154 canvas.restore(); 155 } 156 if (mExcludedRect.right < getWidth()) { 157 canvas.save(); 158 canvas.clipRect(mExcludedRect.right, mExcludedRect.top, getWidth(), 159 mExcludedRect.bottom); 160 mDrawable.draw(canvas); 161 canvas.restore(); 162 } 163 if (mExcludedRect.bottom < getHeight()) { 164 canvas.save(); 165 canvas.clipRect(0, mExcludedRect.bottom, getWidth(), getHeight()); 166 mDrawable.draw(canvas); 167 canvas.restore(); 168 } 169 // We also need to draw the rounded corners of the background 170 canvas.save(); 171 canvas.clipRect(mExcludedRect.left, mExcludedRect.top, 172 mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius); 173 mDrawable.draw(canvas); 174 canvas.restore(); 175 canvas.save(); 176 canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top, 177 mExcludedRect.right, mExcludedRect.top + mCornerRadius); 178 mDrawable.draw(canvas); 179 canvas.restore(); 180 canvas.save(); 181 canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius, 182 mExcludedRect.left + mCornerRadius, mExcludedRect.bottom); 183 mDrawable.draw(canvas); 184 canvas.restore(); 185 canvas.save(); 186 canvas.clipRect(mExcludedRect.right - mCornerRadius, 187 mExcludedRect.bottom - mCornerRadius, 188 mExcludedRect.right, mExcludedRect.bottom); 189 mDrawable.draw(canvas); 190 canvas.restore(); 191 } 192 } 193 } 194 setDrawable(Drawable drawable)195 public void setDrawable(Drawable drawable) { 196 mDrawable = drawable; 197 mDrawable.setCallback(this); 198 mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom()); 199 mDrawable.setAlpha((int) (255 * mViewAlpha)); 200 setDrawAsSrc(mDrawAsSrc); 201 updateScreenSize(); 202 invalidate(); 203 } 204 205 @Override invalidateDrawable(@onNull Drawable drawable)206 public void invalidateDrawable(@NonNull Drawable drawable) { 207 super.invalidateDrawable(drawable); 208 if (drawable == mDrawable) { 209 invalidate(); 210 } 211 } 212 setDrawAsSrc(boolean asSrc)213 public void setDrawAsSrc(boolean asSrc) { 214 mDrawAsSrc = asSrc; 215 PorterDuff.Mode mode = asSrc ? PorterDuff.Mode.SRC : PorterDuff.Mode.SRC_OVER; 216 mDrawable.setXfermode(new PorterDuffXfermode(mode)); 217 } 218 219 @Override onLayout(boolean changed, int left, int top, int right, int bottom)220 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 221 super.onLayout(changed, left, top, right, bottom); 222 if (changed) { 223 mDrawable.setBounds(left, top, right, bottom); 224 invalidate(); 225 } 226 } 227 setColors(@onNull ColorExtractor.GradientColors colors)228 public void setColors(@NonNull ColorExtractor.GradientColors colors) { 229 setColors(colors, false); 230 } 231 setColors(@onNull ColorExtractor.GradientColors colors, boolean animated)232 public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) { 233 if (colors == null) { 234 throw new IllegalArgumentException("Colors cannot be null"); 235 } 236 if (mColors.equals(colors)) { 237 return; 238 } 239 mColors.set(colors); 240 updateColorWithTint(animated); 241 } 242 243 @VisibleForTesting getDrawable()244 Drawable getDrawable() { 245 return mDrawable; 246 } 247 getColors()248 public ColorExtractor.GradientColors getColors() { 249 return mColors; 250 } 251 setTint(int color)252 public void setTint(int color) { 253 setTint(color, false); 254 } 255 setTint(int color, boolean animated)256 public void setTint(int color, boolean animated) { 257 if (mTintColor == color) { 258 return; 259 } 260 mTintColor = color; 261 updateColorWithTint(animated); 262 } 263 updateColorWithTint(boolean animated)264 private void updateColorWithTint(boolean animated) { 265 if (mDrawable instanceof GradientDrawable) { 266 // Optimization to blend colors and avoid a color filter 267 GradientDrawable drawable = (GradientDrawable) mDrawable; 268 float tintAmount = Color.alpha(mTintColor) / 255f; 269 int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, 270 tintAmount); 271 int secondaryTinted = ColorUtils.blendARGB(mColors.getSecondaryColor(), mTintColor, 272 tintAmount); 273 drawable.setColors(mainTinted, secondaryTinted, animated); 274 } else { 275 if (mColorFilter == null) { 276 mColorFilter = new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_OVER); 277 } else { 278 mColorFilter.setColor(mTintColor); 279 } 280 mDrawable.setColorFilter(Color.alpha(mTintColor) == 0 ? null : mColorFilter); 281 mDrawable.invalidateSelf(); 282 } 283 284 if (mChangeRunnable != null) { 285 mChangeRunnable.run(); 286 } 287 } 288 getTint()289 public int getTint() { 290 return mTintColor; 291 } 292 293 @Override hasOverlappingRendering()294 public boolean hasOverlappingRendering() { 295 return false; 296 } 297 298 /** 299 * It might look counterintuitive to have another method to set the alpha instead of 300 * only using {@link #setAlpha(float)}. In this case we're in a hardware layer 301 * optimizing blend modes, so it makes sense. 302 * 303 * @param alpha Gradient alpha from 0 to 1. 304 */ setViewAlpha(float alpha)305 public void setViewAlpha(float alpha) { 306 if (alpha != mViewAlpha) { 307 mViewAlpha = alpha; 308 309 if (mAlphaAnimator != null) { 310 mAlphaAnimator.cancel(); 311 } 312 313 mDrawable.setAlpha((int) (255 * alpha)); 314 if (mChangeRunnable != null) { 315 mChangeRunnable.run(); 316 } 317 } 318 } 319 getViewAlpha()320 public float getViewAlpha() { 321 return mViewAlpha; 322 } 323 setExcludedArea(Rect area)324 public void setExcludedArea(Rect area) { 325 if (area == null) { 326 mHasExcludedArea = false; 327 invalidate(); 328 return; 329 } 330 331 int left = Math.max(area.left, 0); 332 int top = Math.max(area.top, 0); 333 int right = Math.min(area.right, getWidth()); 334 int bottom = Math.min(area.bottom, getHeight()); 335 mExcludedRect.set(left, top, right, bottom); 336 mHasExcludedArea = left < right && top < bottom; 337 invalidate(); 338 } 339 340 public void setChangeRunnable(Runnable changeRunnable) { 341 mChangeRunnable = changeRunnable; 342 } 343 344 @Override 345 public void onConfigChanged(Configuration newConfig) { 346 updateScreenSize(); 347 } 348 349 private void updateScreenSize() { 350 if (mDrawable instanceof GradientDrawable) { 351 WindowManager wm = mContext.getSystemService(WindowManager.class); 352 if (wm == null) { 353 Log.w(TAG, "Can't resize gradient drawable to fit the screen"); 354 return; 355 } 356 Display display = wm.getDefaultDisplay(); 357 if (display != null) { 358 Point size = new Point(); 359 display.getRealSize(size); 360 ((GradientDrawable) mDrawable).setScreenSize(size.x, size.y); 361 } 362 } 363 } 364 } 365