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.ObjectAnimator;
     22 import android.animation.ValueAnimator;
     23 import android.content.Context;
     24 import android.graphics.Bitmap;
     25 import android.graphics.BitmapShader;
     26 import android.graphics.Canvas;
     27 import android.graphics.Color;
     28 import android.graphics.ColorMatrix;
     29 import android.graphics.ColorMatrixColorFilter;
     30 import android.graphics.Paint;
     31 import android.graphics.PorterDuff;
     32 import android.graphics.PorterDuffColorFilter;
     33 import android.graphics.RectF;
     34 import android.graphics.Shader;
     35 import android.util.AttributeSet;
     36 import android.view.MotionEvent;
     37 import android.view.View;
     38 import android.view.ViewAnimationUtils;
     39 import android.view.ViewConfiguration;
     40 import android.view.animation.AnimationUtils;
     41 import android.view.animation.Interpolator;
     42 import android.view.animation.LinearInterpolator;
     43 import android.view.animation.PathInterpolator;
     44 
     45 import com.android.systemui.R;
     46 
     47 /**
     48  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
     49  * to implement dimming/activating on Keyguard for the double-tap gesture
     50  */
     51 public abstract class ActivatableNotificationView extends ExpandableOutlineView {
     52 
     53     private static final long DOUBLETAP_TIMEOUT_MS = 1200;
     54     private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
     55     private static final int ACTIVATE_ANIMATION_LENGTH = 220;
     56 
     57     /**
     58      * The amount of width, which is kept in the end when performing a disappear animation (also
     59      * the amount from which the horizontal appearing begins)
     60      */
     61     private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f;
     62 
     63     /**
     64      * At which point from [0,1] does the horizontal collapse animation end (or start when
     65      * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
     66      */
     67     private static final float HORIZONTAL_ANIMATION_END = 0.2f;
     68 
     69     /**
     70      * At which point from [0,1] does the alpha animation end (or start when
     71      * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
     72      */
     73     private static final float ALPHA_ANIMATION_END = 0.0f;
     74 
     75     /**
     76      * At which point from [0,1] does the horizontal collapse animation start (or start when
     77      * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
     78      */
     79     private static final float HORIZONTAL_ANIMATION_START = 1.0f;
     80 
     81     /**
     82      * At which point from [0,1] does the vertical collapse animation start (or end when
     83      * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
     84      */
     85     private static final float VERTICAL_ANIMATION_START = 1.0f;
     86 
     87     private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR
     88             = new PathInterpolator(0.6f, 0, 0.5f, 1);
     89     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
     90             = new PathInterpolator(0, 0, 0.5f, 1);
     91     private final int mTintedRippleColor;
     92     private final int mLowPriorityRippleColor;
     93     private final int mNormalRippleColor;
     94 
     95     private boolean mDimmed;
     96     private boolean mDark;
     97     private final Paint mDarkPaint = createDarkPaint();
     98 
     99     private int mBgTint = 0;
    100     private final int mRoundedRectCornerRadius;
    101 
    102     /**
    103      * Flag to indicate that the notification has been touched once and the second touch will
    104      * click it.
    105      */
    106     private boolean mActivated;
    107 
    108     private float mDownX;
    109     private float mDownY;
    110     private final float mTouchSlop;
    111 
    112     private OnActivatedListener mOnActivatedListener;
    113 
    114     private final Interpolator mLinearOutSlowInInterpolator;
    115     private final Interpolator mFastOutSlowInInterpolator;
    116     private final Interpolator mSlowOutFastInInterpolator;
    117     private final Interpolator mSlowOutLinearInInterpolator;
    118     private final Interpolator mLinearInterpolator;
    119     private Interpolator mCurrentAppearInterpolator;
    120     private Interpolator mCurrentAlphaInterpolator;
    121 
    122     private NotificationBackgroundView mBackgroundNormal;
    123     private NotificationBackgroundView mBackgroundDimmed;
    124     private ObjectAnimator mBackgroundAnimator;
    125     private RectF mAppearAnimationRect = new RectF();
    126     private PorterDuffColorFilter mAppearAnimationFilter;
    127     private float mAnimationTranslationY;
    128     private boolean mDrawingAppearAnimation;
    129     private Paint mAppearPaint = new Paint();
    130     private ValueAnimator mAppearAnimator;
    131     private float mAppearAnimationFraction = -1.0f;
    132     private float mAppearAnimationTranslation;
    133     private boolean mShowingLegacyBackground;
    134     private final int mLegacyColor;
    135     private final int mNormalColor;
    136     private final int mLowPriorityColor;
    137     private boolean mIsBelowSpeedBump;
    138 
    139     public ActivatableNotificationView(Context context, AttributeSet attrs) {
    140         super(context, attrs);
    141         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    142         mFastOutSlowInInterpolator =
    143                 AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
    144         mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
    145         mLinearOutSlowInInterpolator =
    146                 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
    147         mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
    148         mLinearInterpolator = new LinearInterpolator();
    149         setClipChildren(false);
    150         setClipToPadding(false);
    151         mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
    152         mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
    153                 R.dimen.notification_material_rounded_rect_radius);
    154         mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color);
    155         mNormalColor = getResources().getColor(R.color.notification_material_background_color);
    156         mLowPriorityColor = getResources().getColor(
    157                 R.color.notification_material_background_low_priority_color);
    158         mTintedRippleColor = context.getResources().getColor(
    159                 R.color.notification_ripple_tinted_color);
    160         mLowPriorityRippleColor = context.getResources().getColor(
    161                 R.color.notification_ripple_color_low_priority);
    162         mNormalRippleColor = context.getResources().getColor(
    163                 R.color.notification_ripple_untinted_color);
    164     }
    165 
    166     @Override
    167     protected void onFinishInflate() {
    168         super.onFinishInflate();
    169         mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal);
    170         mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
    171         mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
    172         mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
    173         updateBackground();
    174         updateBackgroundTint();
    175     }
    176 
    177     private final Runnable mTapTimeoutRunnable = new Runnable() {
    178         @Override
    179         public void run() {
    180             makeInactive(true /* animate */);
    181         }
    182     };
    183 
    184     @Override
    185     public boolean onTouchEvent(MotionEvent event) {
    186         if (mDimmed) {
    187             return handleTouchEventDimmed(event);
    188         } else {
    189             return super.onTouchEvent(event);
    190         }
    191     }
    192 
    193     @Override
    194     public void drawableHotspotChanged(float x, float y) {
    195         if (!mDimmed){
    196             mBackgroundNormal.drawableHotspotChanged(x, y);
    197         }
    198     }
    199 
    200     @Override
    201     protected void drawableStateChanged() {
    202         super.drawableStateChanged();
    203         if (mDimmed) {
    204             mBackgroundDimmed.setState(getDrawableState());
    205         } else {
    206             mBackgroundNormal.setState(getDrawableState());
    207         }
    208     }
    209 
    210     private boolean handleTouchEventDimmed(MotionEvent event) {
    211         int action = event.getActionMasked();
    212         switch (action) {
    213             case MotionEvent.ACTION_DOWN:
    214                 mDownX = event.getX();
    215                 mDownY = event.getY();
    216                 if (mDownY > getActualHeight()) {
    217                     return false;
    218                 }
    219                 break;
    220             case MotionEvent.ACTION_MOVE:
    221                 if (!isWithinTouchSlop(event)) {
    222                     makeInactive(true /* animate */);
    223                     return false;
    224                 }
    225                 break;
    226             case MotionEvent.ACTION_UP:
    227                 if (isWithinTouchSlop(event)) {
    228                     if (!mActivated) {
    229                         makeActive();
    230                         postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
    231                     } else {
    232                         boolean performed = performClick();
    233                         if (performed) {
    234                             removeCallbacks(mTapTimeoutRunnable);
    235                         }
    236                     }
    237                 } else {
    238                     makeInactive(true /* animate */);
    239                 }
    240                 break;
    241             case MotionEvent.ACTION_CANCEL:
    242                 makeInactive(true /* animate */);
    243                 break;
    244             default:
    245                 break;
    246         }
    247         return true;
    248     }
    249 
    250     private void makeActive() {
    251         startActivateAnimation(false /* reverse */);
    252         mActivated = true;
    253         if (mOnActivatedListener != null) {
    254             mOnActivatedListener.onActivated(this);
    255         }
    256     }
    257 
    258     private void startActivateAnimation(boolean reverse) {
    259         if (!isAttachedToWindow()) {
    260             return;
    261         }
    262         int widthHalf = mBackgroundNormal.getWidth()/2;
    263         int heightHalf = mBackgroundNormal.getActualHeight()/2;
    264         float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf);
    265         Animator animator;
    266         if (reverse) {
    267             animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
    268                     widthHalf, heightHalf, radius, 0);
    269         } else {
    270             animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
    271                     widthHalf, heightHalf, 0, radius);
    272         }
    273         mBackgroundNormal.setVisibility(View.VISIBLE);
    274         Interpolator interpolator;
    275         Interpolator alphaInterpolator;
    276         if (!reverse) {
    277             interpolator = mLinearOutSlowInInterpolator;
    278             alphaInterpolator = mLinearOutSlowInInterpolator;
    279         } else {
    280             interpolator = ACTIVATE_INVERSE_INTERPOLATOR;
    281             alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR;
    282         }
    283         animator.setInterpolator(interpolator);
    284         animator.setDuration(ACTIVATE_ANIMATION_LENGTH);
    285         if (reverse) {
    286             mBackgroundNormal.setAlpha(1f);
    287             animator.addListener(new AnimatorListenerAdapter() {
    288                 @Override
    289                 public void onAnimationEnd(Animator animation) {
    290                     if (mDimmed) {
    291                         mBackgroundNormal.setVisibility(View.INVISIBLE);
    292                     }
    293                 }
    294             });
    295             animator.start();
    296         } else {
    297             mBackgroundNormal.setAlpha(0.4f);
    298             animator.start();
    299         }
    300         mBackgroundNormal.animate()
    301                 .alpha(reverse ? 0f : 1f)
    302                 .setInterpolator(alphaInterpolator)
    303                 .setDuration(ACTIVATE_ANIMATION_LENGTH);
    304     }
    305 
    306     /**
    307      * Cancels the hotspot and makes the notification inactive.
    308      */
    309     public void makeInactive(boolean animate) {
    310         if (mActivated) {
    311             if (mDimmed) {
    312                 if (animate) {
    313                     startActivateAnimation(true /* reverse */);
    314                 } else {
    315                     mBackgroundNormal.setVisibility(View.INVISIBLE);
    316                 }
    317             }
    318             mActivated = false;
    319         }
    320         if (mOnActivatedListener != null) {
    321             mOnActivatedListener.onActivationReset(this);
    322         }
    323         removeCallbacks(mTapTimeoutRunnable);
    324     }
    325 
    326     private boolean isWithinTouchSlop(MotionEvent event) {
    327         return Math.abs(event.getX() - mDownX) < mTouchSlop
    328                 && Math.abs(event.getY() - mDownY) < mTouchSlop;
    329     }
    330 
    331     public void setDimmed(boolean dimmed, boolean fade) {
    332         if (mDimmed != dimmed) {
    333             mDimmed = dimmed;
    334             if (fade) {
    335                 fadeBackground();
    336             } else {
    337                 updateBackground();
    338             }
    339         }
    340     }
    341 
    342     public void setDark(boolean dark, boolean fade) {
    343         // TODO implement fade
    344         if (mDark != dark) {
    345             mDark = dark;
    346             if (mDark) {
    347                 setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
    348             } else {
    349                 setLayerType(View.LAYER_TYPE_NONE, null);
    350             }
    351         }
    352     }
    353 
    354     private static Paint createDarkPaint() {
    355         final Paint p = new Paint();
    356         final float[] invert = {
    357             -1f,  0f,  0f, 1f, 1f,
    358              0f, -1f,  0f, 1f, 1f,
    359              0f,  0f, -1f, 1f, 1f,
    360              0f,  0f,  0f, 1f, 0f
    361         };
    362         final ColorMatrix m = new ColorMatrix(invert);
    363         final ColorMatrix grayscale = new ColorMatrix();
    364         grayscale.setSaturation(0);
    365         m.preConcat(grayscale);
    366         p.setColorFilter(new ColorMatrixColorFilter(m));
    367         return p;
    368     }
    369 
    370     public void setShowingLegacyBackground(boolean showing) {
    371         mShowingLegacyBackground = showing;
    372         updateBackgroundTint();
    373     }
    374 
    375     @Override
    376     public void setBelowSpeedBump(boolean below) {
    377         super.setBelowSpeedBump(below);
    378         if (below != mIsBelowSpeedBump) {
    379             mIsBelowSpeedBump = below;
    380             updateBackgroundTint();
    381         }
    382     }
    383 
    384     /**
    385      * Sets the tint color of the background
    386      */
    387     public void setTintColor(int color) {
    388         mBgTint = color;
    389         updateBackgroundTint();
    390     }
    391 
    392     private void updateBackgroundTint() {
    393         int color = getBackgroundColor();
    394         int rippleColor = getRippleColor();
    395         if (color == mNormalColor) {
    396             // We don't need to tint a normal notification
    397             color = 0;
    398         }
    399         mBackgroundDimmed.setTint(color);
    400         mBackgroundNormal.setTint(color);
    401         mBackgroundDimmed.setRippleColor(rippleColor);
    402         mBackgroundNormal.setRippleColor(rippleColor);
    403     }
    404 
    405     private void fadeBackground() {
    406         mBackgroundNormal.animate().cancel();
    407         if (mDimmed) {
    408             mBackgroundDimmed.setVisibility(View.VISIBLE);
    409         } else {
    410             mBackgroundNormal.setVisibility(View.VISIBLE);
    411         }
    412         float startAlpha = mDimmed ? 1f : 0;
    413         float endAlpha = mDimmed ? 0 : 1f;
    414         int duration = BACKGROUND_ANIMATION_LENGTH_MS;
    415         // Check whether there is already a background animation running.
    416         if (mBackgroundAnimator != null) {
    417             startAlpha = (Float) mBackgroundAnimator.getAnimatedValue();
    418             duration = (int) mBackgroundAnimator.getCurrentPlayTime();
    419             mBackgroundAnimator.removeAllListeners();
    420             mBackgroundAnimator.cancel();
    421             if (duration <= 0) {
    422                 updateBackground();
    423                 return;
    424             }
    425         }
    426         mBackgroundNormal.setAlpha(startAlpha);
    427         mBackgroundAnimator =
    428                 ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha);
    429         mBackgroundAnimator.setInterpolator(mFastOutSlowInInterpolator);
    430         mBackgroundAnimator.setDuration(duration);
    431         mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
    432             @Override
    433             public void onAnimationEnd(Animator animation) {
    434                 if (mDimmed) {
    435                     mBackgroundNormal.setVisibility(View.INVISIBLE);
    436                 } else {
    437                     mBackgroundDimmed.setVisibility(View.INVISIBLE);
    438                 }
    439                 mBackgroundAnimator = null;
    440             }
    441         });
    442         mBackgroundAnimator.start();
    443     }
    444 
    445     private void updateBackground() {
    446         if (mDimmed) {
    447             mBackgroundDimmed.setVisibility(View.VISIBLE);
    448             mBackgroundNormal.setVisibility(View.INVISIBLE);
    449         } else {
    450             cancelFadeAnimations();
    451             mBackgroundDimmed.setVisibility(View.INVISIBLE);
    452             mBackgroundNormal.setVisibility(View.VISIBLE);
    453             mBackgroundNormal.setAlpha(1f);
    454             removeCallbacks(mTapTimeoutRunnable);
    455         }
    456     }
    457 
    458     private void cancelFadeAnimations() {
    459         if (mBackgroundAnimator != null) {
    460             mBackgroundAnimator.cancel();
    461         }
    462         mBackgroundNormal.animate().cancel();
    463     }
    464 
    465     @Override
    466     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    467         super.onLayout(changed, left, top, right, bottom);
    468         setPivotX(getWidth() / 2);
    469     }
    470 
    471     @Override
    472     public void setActualHeight(int actualHeight, boolean notifyListeners) {
    473         super.setActualHeight(actualHeight, notifyListeners);
    474         setPivotY(actualHeight / 2);
    475         mBackgroundNormal.setActualHeight(actualHeight);
    476         mBackgroundDimmed.setActualHeight(actualHeight);
    477     }
    478 
    479     @Override
    480     public void setClipTopAmount(int clipTopAmount) {
    481         super.setClipTopAmount(clipTopAmount);
    482         mBackgroundNormal.setClipTopAmount(clipTopAmount);
    483         mBackgroundDimmed.setClipTopAmount(clipTopAmount);
    484     }
    485 
    486     @Override
    487     public void performRemoveAnimation(long duration, float translationDirection,
    488             Runnable onFinishedRunnable) {
    489         enableAppearDrawing(true);
    490         if (mDrawingAppearAnimation) {
    491             startAppearAnimation(false /* isAppearing */, translationDirection,
    492                     0, duration, onFinishedRunnable);
    493         } else if (onFinishedRunnable != null) {
    494             onFinishedRunnable.run();
    495         }
    496     }
    497 
    498     @Override
    499     public void performAddAnimation(long delay, long duration) {
    500         enableAppearDrawing(true);
    501         if (mDrawingAppearAnimation) {
    502             startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null);
    503         }
    504     }
    505 
    506     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
    507             long duration, final Runnable onFinishedRunnable) {
    508         if (mAppearAnimator != null) {
    509             mAppearAnimator.cancel();
    510         }
    511         mAnimationTranslationY = translationDirection * mActualHeight;
    512         if (mAppearAnimationFraction == -1.0f) {
    513             // not initialized yet, we start anew
    514             if (isAppearing) {
    515                 mAppearAnimationFraction = 0.0f;
    516                 mAppearAnimationTranslation = mAnimationTranslationY;
    517             } else {
    518                 mAppearAnimationFraction = 1.0f;
    519                 mAppearAnimationTranslation = 0;
    520             }
    521         }
    522 
    523         float targetValue;
    524         if (isAppearing) {
    525             mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
    526             mCurrentAlphaInterpolator = mLinearOutSlowInInterpolator;
    527             targetValue = 1.0f;
    528         } else {
    529             mCurrentAppearInterpolator = mFastOutSlowInInterpolator;
    530             mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator;
    531             targetValue = 0.0f;
    532         }
    533         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
    534                 targetValue);
    535         mAppearAnimator.setInterpolator(mLinearInterpolator);
    536         mAppearAnimator.setDuration(
    537                 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
    538         mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    539             @Override
    540             public void onAnimationUpdate(ValueAnimator animation) {
    541                 mAppearAnimationFraction = (float) animation.getAnimatedValue();
    542                 updateAppearAnimationAlpha();
    543                 updateAppearRect();
    544                 invalidate();
    545             }
    546         });
    547         if (delay > 0) {
    548             // we need to apply the initial state already to avoid drawn frames in the wrong state
    549             updateAppearAnimationAlpha();
    550             updateAppearRect();
    551             mAppearAnimator.setStartDelay(delay);
    552         }
    553         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
    554             private boolean mWasCancelled;
    555 
    556             @Override
    557             public void onAnimationEnd(Animator animation) {
    558                 if (onFinishedRunnable != null) {
    559                     onFinishedRunnable.run();
    560                 }
    561                 if (!mWasCancelled) {
    562                     mAppearAnimationFraction = -1;
    563                     setOutlineRect(null);
    564                     enableAppearDrawing(false);
    565                 }
    566             }
    567 
    568             @Override
    569             public void onAnimationStart(Animator animation) {
    570                 mWasCancelled = false;
    571             }
    572 
    573             @Override
    574             public void onAnimationCancel(Animator animation) {
    575                 mWasCancelled = true;
    576             }
    577         });
    578         mAppearAnimator.start();
    579     }
    580 
    581     private void updateAppearRect() {
    582         float inverseFraction = (1.0f - mAppearAnimationFraction);
    583         float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
    584         float translateYTotalAmount = translationFraction * mAnimationTranslationY;
    585         mAppearAnimationTranslation = translateYTotalAmount;
    586 
    587         // handle width animation
    588         float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START))
    589                 / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END);
    590         widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction));
    591         widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction);
    592         float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) *
    593                 widthFraction);
    594         float right = getWidth() - left;
    595 
    596         // handle top animation
    597         float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) /
    598                 VERTICAL_ANIMATION_START;
    599         heightFraction = Math.max(0.0f, heightFraction);
    600         heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction);
    601 
    602         float top;
    603         float bottom;
    604         if (mAnimationTranslationY > 0.0f) {
    605             bottom = mActualHeight - heightFraction * mAnimationTranslationY * 0.1f
    606                     - translateYTotalAmount;
    607             top = bottom * heightFraction;
    608         } else {
    609             top = heightFraction * (mActualHeight + mAnimationTranslationY) * 0.1f -
    610                     translateYTotalAmount;
    611             bottom = mActualHeight * (1 - heightFraction) + top * heightFraction;
    612         }
    613         mAppearAnimationRect.set(left, top, right, bottom);
    614         setOutlineRect(left, top + mAppearAnimationTranslation, right,
    615                 bottom + mAppearAnimationTranslation);
    616     }
    617 
    618     private void updateAppearAnimationAlpha() {
    619         int backgroundColor = getBackgroundColor();
    620         if (backgroundColor != -1) {
    621             float contentAlphaProgress = mAppearAnimationFraction;
    622             contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
    623             contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
    624             contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
    625             int sourceColor = Color.argb((int) (255 * (1.0f - contentAlphaProgress)),
    626                     Color.red(backgroundColor), Color.green(backgroundColor),
    627                     Color.blue(backgroundColor));
    628             mAppearAnimationFilter.setColor(sourceColor);
    629             mAppearPaint.setColorFilter(mAppearAnimationFilter);
    630         }
    631     }
    632 
    633     private int getBackgroundColor() {
    634         if (mBgTint != 0) {
    635             return mBgTint;
    636         } else if (mShowingLegacyBackground) {
    637             return mLegacyColor;
    638         } else if (mIsBelowSpeedBump) {
    639             return mLowPriorityColor;
    640         } else {
    641             return mNormalColor;
    642         }
    643     }
    644 
    645     private int getRippleColor() {
    646         if (mBgTint != 0) {
    647             return mTintedRippleColor;
    648         } else if (mShowingLegacyBackground) {
    649             return mTintedRippleColor;
    650         } else if (mIsBelowSpeedBump) {
    651             return mLowPriorityRippleColor;
    652         } else {
    653             return mNormalRippleColor;
    654         }
    655     }
    656 
    657     /**
    658      * When we draw the appear animation, we render the view in a bitmap and render this bitmap
    659      * as a shader of a rect. This call creates the Bitmap and switches the drawing mode,
    660      * such that the normal drawing of the views does not happen anymore.
    661      *
    662      * @param enable Should it be enabled.
    663      */
    664     private void enableAppearDrawing(boolean enable) {
    665         if (enable != mDrawingAppearAnimation) {
    666             if (enable) {
    667                 if (getWidth() == 0 || getActualHeight() == 0) {
    668                     // TODO: This should not happen, but it can during expansion. Needs
    669                     // investigation
    670                     return;
    671                 }
    672                 Bitmap bitmap = Bitmap.createBitmap(getWidth(), getActualHeight(),
    673                         Bitmap.Config.ARGB_8888);
    674                 Canvas canvas = new Canvas(bitmap);
    675                 draw(canvas);
    676                 mAppearPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP,
    677                         Shader.TileMode.CLAMP));
    678             } else {
    679                 mAppearPaint.setShader(null);
    680             }
    681             mDrawingAppearAnimation = enable;
    682             invalidate();
    683         }
    684     }
    685 
    686     @Override
    687     protected void dispatchDraw(Canvas canvas) {
    688         if (!mDrawingAppearAnimation) {
    689             super.dispatchDraw(canvas);
    690         } else {
    691             drawAppearRect(canvas);
    692         }
    693     }
    694 
    695     private void drawAppearRect(Canvas canvas) {
    696         canvas.save();
    697         canvas.translate(0, mAppearAnimationTranslation);
    698         canvas.drawRoundRect(mAppearAnimationRect, mRoundedRectCornerRadius,
    699                 mRoundedRectCornerRadius, mAppearPaint);
    700         canvas.restore();
    701     }
    702 
    703     public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
    704         mOnActivatedListener = onActivatedListener;
    705     }
    706 
    707     public void reset() {
    708         setTintColor(0);
    709         setShowingLegacyBackground(false);
    710         setBelowSpeedBump(false);
    711     }
    712 
    713     public interface OnActivatedListener {
    714         void onActivated(ActivatableNotificationView view);
    715         void onActivationReset(ActivatableNotificationView view);
    716     }
    717 }
    718