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.systemui.Dependency; 45 import com.android.systemui.statusbar.policy.ConfigurationController; 46 47 /** 48 * A view which can draw a scrim 49 */ 50 public class ScrimView extends View implements ConfigurationController.ConfigurationListener { 51 private static final String TAG = "ScrimView"; 52 private final ColorExtractor.GradientColors mColors; 53 private boolean mDrawAsSrc; 54 private float mViewAlpha = 1.0f; 55 private ValueAnimator mAlphaAnimator; 56 private Rect mExcludedRect = new Rect(); 57 private boolean mHasExcludedArea; 58 private Drawable mDrawable; 59 private PorterDuffColorFilter mColorFilter; 60 private int mTintColor; 61 private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener = animation -> { 62 if (mDrawable == null) { 63 Log.w(TAG, "Trying to animate null drawable"); 64 return; 65 } 66 mDrawable.setAlpha((int) (255 * (float) animation.getAnimatedValue())); 67 }; 68 private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { 69 @Override 70 public void onAnimationEnd(Animator animation) { 71 mAlphaAnimator = null; 72 } 73 }; 74 private Runnable mChangeRunnable; 75 76 public ScrimView(Context context) { 77 this(context, null); 78 } 79 80 public ScrimView(Context context, AttributeSet attrs) { 81 this(context, attrs, 0); 82 } 83 84 public ScrimView(Context context, AttributeSet attrs, int defStyleAttr) { 85 this(context, attrs, defStyleAttr, 0); 86 } 87 88 public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 89 super(context, attrs, defStyleAttr, defStyleRes); 90 91 mDrawable = new GradientDrawable(context); 92 mDrawable.setCallback(this); 93 mColors = new ColorExtractor.GradientColors(); 94 updateScreenSize(); 95 updateColorWithTint(false); 96 } 97 98 @Override 99 protected void onAttachedToWindow() { 100 super.onAttachedToWindow(); 101 102 // We need to know about configuration changes to update the gradient size 103 // since it's independent from view bounds. 104 ConfigurationController config = Dependency.get(ConfigurationController.class); 105 config.addCallback(this); 106 } 107 108 @Override 109 protected void onDetachedFromWindow() { 110 super.onDetachedFromWindow(); 111 112 ConfigurationController config = Dependency.get(ConfigurationController.class); 113 config.removeCallback(this); 114 } 115 116 @Override 117 protected void onDraw(Canvas canvas) { 118 if (mDrawAsSrc || mDrawable.getAlpha() > 0) { 119 if (!mHasExcludedArea) { 120 mDrawable.draw(canvas); 121 } else { 122 if (mExcludedRect.top > 0) { 123 canvas.save(); 124 canvas.clipRect(0, 0, getWidth(), mExcludedRect.top); 125 mDrawable.draw(canvas); 126 canvas.restore(); 127 } 128 if (mExcludedRect.left > 0) { 129 canvas.save(); 130 canvas.clipRect(0, mExcludedRect.top, mExcludedRect.left, 131 mExcludedRect.bottom); 132 mDrawable.draw(canvas); 133 canvas.restore(); 134 } 135 if (mExcludedRect.right < getWidth()) { 136 canvas.save(); 137 canvas.clipRect(mExcludedRect.right, mExcludedRect.top, getWidth(), 138 mExcludedRect.bottom); 139 mDrawable.draw(canvas); 140 canvas.restore(); 141 } 142 if (mExcludedRect.bottom < getHeight()) { 143 canvas.save(); 144 canvas.clipRect(0, mExcludedRect.bottom, getWidth(), getHeight()); 145 mDrawable.draw(canvas); 146 canvas.restore(); 147 } 148 } 149 } 150 } 151 152 public void setDrawable(Drawable drawable) { 153 mDrawable = drawable; 154 mDrawable.setCallback(this); 155 mDrawable.setBounds(getLeft(), getTop(), getRight(), getBottom()); 156 mDrawable.setAlpha((int) (255 * mViewAlpha)); 157 setDrawAsSrc(mDrawAsSrc); 158 updateScreenSize(); 159 invalidate(); 160 } 161 162 @Override 163 public void invalidateDrawable(@NonNull Drawable drawable) { 164 super.invalidateDrawable(drawable); 165 if (drawable == mDrawable) { 166 invalidate(); 167 } 168 } 169 170 public void setDrawAsSrc(boolean asSrc) { 171 mDrawAsSrc = asSrc; 172 PorterDuff.Mode mode = asSrc ? PorterDuff.Mode.SRC : PorterDuff.Mode.SRC_OVER; 173 mDrawable.setXfermode(new PorterDuffXfermode(mode)); 174 } 175 176 @Override 177 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 178 super.onLayout(changed, left, top, right, bottom); 179 if (changed) { 180 mDrawable.setBounds(left, top, right, bottom); 181 invalidate(); 182 } 183 } 184 185 public void setColors(@NonNull ColorExtractor.GradientColors colors) { 186 setColors(colors, false); 187 } 188 189 public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) { 190 if (colors == null) { 191 throw new IllegalArgumentException("Colors cannot be null"); 192 } 193 if (mColors.equals(colors)) { 194 return; 195 } 196 mColors.set(colors); 197 updateColorWithTint(animated); 198 } 199 200 @VisibleForTesting 201 Drawable getDrawable() { 202 return mDrawable; 203 } 204 205 public ColorExtractor.GradientColors getColors() { 206 return mColors; 207 } 208 209 public void setTint(int color) { 210 setTint(color, false); 211 } 212 213 public void setTint(int color, boolean animated) { 214 if (mTintColor == color) { 215 return; 216 } 217 mTintColor = color; 218 updateColorWithTint(animated); 219 } 220 221 private void updateColorWithTint(boolean animated) { 222 if (mDrawable instanceof GradientDrawable) { 223 // Optimization to blend colors and avoid a color filter 224 GradientDrawable drawable = (GradientDrawable) mDrawable; 225 float tintAmount = Color.alpha(mTintColor) / 255f; 226 int mainTinted = ColorUtils.blendARGB(mColors.getMainColor(), mTintColor, 227 tintAmount); 228 int secondaryTinted = ColorUtils.blendARGB(mColors.getSecondaryColor(), mTintColor, 229 tintAmount); 230 drawable.setColors(mainTinted, secondaryTinted, animated); 231 } else { 232 if (mColorFilter == null) { 233 mColorFilter = new PorterDuffColorFilter(mTintColor, PorterDuff.Mode.SRC_OVER); 234 } else { 235 mColorFilter.setColor(mTintColor); 236 } 237 mDrawable.setColorFilter(Color.alpha(mTintColor) == 0 ? null : mColorFilter); 238 mDrawable.invalidateSelf(); 239 } 240 241 if (mChangeRunnable != null) { 242 mChangeRunnable.run(); 243 } 244 } 245 246 public int getTint() { 247 return mTintColor; 248 } 249 250 @Override 251 public boolean hasOverlappingRendering() { 252 return false; 253 } 254 255 public void setViewAlpha(float alpha) { 256 if (alpha != mViewAlpha) { 257 mViewAlpha = alpha; 258 259 if (mAlphaAnimator != null) { 260 mAlphaAnimator.cancel(); 261 } 262 263 mDrawable.setAlpha((int) (255 * alpha)); 264 if (mChangeRunnable != null) { 265 mChangeRunnable.run(); 266 } 267 } 268 } 269 270 public float getViewAlpha() { 271 return mViewAlpha; 272 } 273 274 public void animateViewAlpha(float alpha, long durationOut, Interpolator interpolator) { 275 if (mAlphaAnimator != null) { 276 mAlphaAnimator.cancel(); 277 } 278 mAlphaAnimator = ValueAnimator.ofFloat(getViewAlpha(), alpha); 279 mAlphaAnimator.addUpdateListener(mAlphaUpdateListener); 280 mAlphaAnimator.addListener(mClearAnimatorListener); 281 mAlphaAnimator.setInterpolator(interpolator); 282 mAlphaAnimator.setDuration(durationOut); 283 mAlphaAnimator.start(); 284 } 285 286 public void setExcludedArea(Rect area) { 287 if (area == null) { 288 mHasExcludedArea = false; 289 invalidate(); 290 return; 291 } 292 293 int left = Math.max(area.left, 0); 294 int top = Math.max(area.top, 0); 295 int right = Math.min(area.right, getWidth()); 296 int bottom = Math.min(area.bottom, getHeight()); 297 mExcludedRect.set(left, top, right, bottom); 298 mHasExcludedArea = left < right && top < bottom; 299 invalidate(); 300 } 301 302 public void setChangeRunnable(Runnable changeRunnable) { 303 mChangeRunnable = changeRunnable; 304 } 305 306 @Override 307 public void onConfigChanged(Configuration newConfig) { 308 updateScreenSize(); 309 } 310 311 private void updateScreenSize() { 312 if (mDrawable instanceof GradientDrawable) { 313 WindowManager wm = mContext.getSystemService(WindowManager.class); 314 if (wm == null) { 315 Log.w(TAG, "Can't resize gradient drawable to fit the screen"); 316 return; 317 } 318 Display display = wm.getDefaultDisplay(); 319 if (display != null) { 320 Point size = new Point(); 321 display.getRealSize(size); 322 ((GradientDrawable) mDrawable).setScreenSize(size.x, size.y); 323 } 324 } 325 } 326 } 327