Home | History | Annotate | Download | only in statusbar
      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