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.TimeAnimator;
     23 import android.animation.ValueAnimator;
     24 import android.content.Context;
     25 import android.graphics.Canvas;
     26 import android.graphics.Color;
     27 import android.graphics.RectF;
     28 import android.util.AttributeSet;
     29 import android.view.MotionEvent;
     30 import android.view.View;
     31 import android.view.ViewAnimationUtils;
     32 import android.view.ViewConfiguration;
     33 import android.view.accessibility.AccessibilityManager;
     34 import android.view.animation.Interpolator;
     35 import android.view.animation.PathInterpolator;
     36 
     37 import com.android.systemui.Interpolators;
     38 import com.android.systemui.R;
     39 import com.android.systemui.classifier.FalsingManager;
     40 import com.android.systemui.statusbar.notification.FakeShadowView;
     41 import com.android.systemui.statusbar.notification.NotificationUtils;
     42 import com.android.systemui.statusbar.phone.DoubleTapHelper;
     43 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     44 import com.android.systemui.statusbar.stack.StackStateAnimator;
     45 
     46 /**
     47  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
     48  * to implement dimming/activating on Keyguard for the double-tap gesture
     49  */
     50 public abstract class ActivatableNotificationView extends ExpandableOutlineView {
     51 
     52     private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
     53     private static final int ACTIVATE_ANIMATION_LENGTH = 220;
     54     private static final long DARK_ANIMATION_LENGTH = StackStateAnimator.ANIMATION_DURATION_WAKEUP;
     55 
     56     /**
     57      * The amount of width, which is kept in the end when performing a disappear animation (also
     58      * the amount from which the horizontal appearing begins)
     59      */
     60     private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f;
     61 
     62     /**
     63      * At which point from [0,1] does the horizontal collapse animation end (or start when
     64      * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
     65      */
     66     private static final float HORIZONTAL_ANIMATION_END = 0.2f;
     67 
     68     /**
     69      * At which point from [0,1] does the alpha animation end (or start when
     70      * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
     71      */
     72     private static final float ALPHA_ANIMATION_END = 0.0f;
     73 
     74     /**
     75      * At which point from [0,1] does the horizontal collapse animation start (or start when
     76      * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
     77      */
     78     private static final float HORIZONTAL_ANIMATION_START = 1.0f;
     79 
     80     /**
     81      * At which point from [0,1] does the vertical collapse animation start (or end when
     82      * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
     83      */
     84     private static final float VERTICAL_ANIMATION_START = 1.0f;
     85 
     86     /**
     87      * Scale for the background to animate from when exiting dark mode.
     88      */
     89     private static final float DARK_EXIT_SCALE_START = 0.93f;
     90 
     91     /**
     92      * A sentinel value when no color should be used. Can be used with {@link #setTintColor(int)}
     93      * or {@link #setOverrideTintColor(int, float)}.
     94      */
     95     protected static final int NO_COLOR = 0;
     96 
     97     private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR
     98             = new PathInterpolator(0.6f, 0, 0.5f, 1);
     99     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
    100             = new PathInterpolator(0, 0, 0.5f, 1);
    101     private final int mTintedRippleColor;
    102     private final int mLowPriorityRippleColor;
    103     protected final int mNormalRippleColor;
    104     private final AccessibilityManager mAccessibilityManager;
    105     private final DoubleTapHelper mDoubleTapHelper;
    106 
    107     private boolean mDimmed;
    108     private boolean mDark;
    109 
    110     protected int mBgTint = NO_COLOR;
    111     private float mBgAlpha = 1f;
    112 
    113     /**
    114      * Flag to indicate that the notification has been touched once and the second touch will
    115      * click it.
    116      */
    117     private boolean mActivated;
    118 
    119     private OnActivatedListener mOnActivatedListener;
    120 
    121     private final Interpolator mSlowOutFastInInterpolator;
    122     private final Interpolator mSlowOutLinearInInterpolator;
    123     private Interpolator mCurrentAppearInterpolator;
    124     private Interpolator mCurrentAlphaInterpolator;
    125 
    126     private NotificationBackgroundView mBackgroundNormal;
    127     private NotificationBackgroundView mBackgroundDimmed;
    128     private ObjectAnimator mBackgroundAnimator;
    129     private RectF mAppearAnimationRect = new RectF();
    130     private float mAnimationTranslationY;
    131     private boolean mDrawingAppearAnimation;
    132     private ValueAnimator mAppearAnimator;
    133     private ValueAnimator mBackgroundColorAnimator;
    134     private float mAppearAnimationFraction = -1.0f;
    135     private float mAppearAnimationTranslation;
    136     private final int mNormalColor;
    137     private final int mLowPriorityColor;
    138     private boolean mIsBelowSpeedBump;
    139     private FalsingManager mFalsingManager;
    140 
    141     private float mNormalBackgroundVisibilityAmount;
    142     private ValueAnimator mFadeInFromDarkAnimator;
    143     private float mDimmedBackgroundFadeInAmount = -1;
    144     private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater
    145             = new ValueAnimator.AnimatorUpdateListener() {
    146         @Override
    147         public void onAnimationUpdate(ValueAnimator animation) {
    148             setNormalBackgroundVisibilityAmount(mBackgroundNormal.getAlpha());
    149             mDimmedBackgroundFadeInAmount = mBackgroundDimmed.getAlpha();
    150         }
    151     };
    152     private AnimatorListenerAdapter mFadeInEndListener = new AnimatorListenerAdapter() {
    153         @Override
    154         public void onAnimationEnd(Animator animation) {
    155             super.onAnimationEnd(animation);
    156             mFadeInFromDarkAnimator = null;
    157             mDimmedBackgroundFadeInAmount = -1;
    158             updateBackground();
    159         }
    160     };
    161     private ValueAnimator.AnimatorUpdateListener mUpdateOutlineListener
    162             = new ValueAnimator.AnimatorUpdateListener() {
    163         @Override
    164         public void onAnimationUpdate(ValueAnimator animation) {
    165             updateOutlineAlpha();
    166         }
    167     };
    168     private float mShadowAlpha = 1.0f;
    169     private FakeShadowView mFakeShadow;
    170     private int mCurrentBackgroundTint;
    171     private int mTargetTint;
    172     private int mStartTint;
    173     private int mOverrideTint;
    174     private float mOverrideAmount;
    175     private boolean mShadowHidden;
    176     private boolean mWasActivatedOnDown;
    177     /**
    178      * Similar to mDimmed but is also true if it's not dimmable but should be
    179      */
    180     private boolean mNeedsDimming;
    181     private int mDimmedAlpha;
    182 
    183     public ActivatableNotificationView(Context context, AttributeSet attrs) {
    184         super(context, attrs);
    185         mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
    186         mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
    187         setClipChildren(false);
    188         setClipToPadding(false);
    189         mNormalColor = context.getColor(R.color.notification_material_background_color);
    190         mLowPriorityColor = context.getColor(
    191                 R.color.notification_material_background_low_priority_color);
    192         mTintedRippleColor = context.getColor(
    193                 R.color.notification_ripple_tinted_color);
    194         mLowPriorityRippleColor = context.getColor(
    195                 R.color.notification_ripple_color_low_priority);
    196         mNormalRippleColor = context.getColor(
    197                 R.color.notification_ripple_untinted_color);
    198         mFalsingManager = FalsingManager.getInstance(context);
    199         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
    200 
    201         mDoubleTapHelper = new DoubleTapHelper(this, (active) -> {
    202             if (active) {
    203                 makeActive();
    204             } else {
    205                 makeInactive(true /* animate */);
    206             }
    207         }, this::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
    208     }
    209 
    210     @Override
    211     protected void onFinishInflate() {
    212         super.onFinishInflate();
    213         mBackgroundNormal = findViewById(R.id.backgroundNormal);
    214         mFakeShadow = findViewById(R.id.fake_shadow);
    215         mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
    216         mBackgroundDimmed = findViewById(R.id.backgroundDimmed);
    217         mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
    218         mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
    219         mDimmedAlpha = Color.alpha(mContext.getColor(
    220                 R.color.notification_material_background_dimmed_color));
    221         updateBackground();
    222         updateBackgroundTint();
    223         updateOutlineAlpha();
    224     }
    225 
    226     private final Runnable mTapTimeoutRunnable = new Runnable() {
    227         @Override
    228         public void run() {
    229             makeInactive(true /* animate */);
    230         }
    231     };
    232 
    233     @Override
    234     public boolean onInterceptTouchEvent(MotionEvent ev) {
    235         if (mNeedsDimming && !mActivated && ev.getActionMasked() == MotionEvent.ACTION_DOWN
    236                 && disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
    237             return true;
    238         }
    239         return super.onInterceptTouchEvent(ev);
    240     }
    241 
    242     private boolean isTouchExplorationEnabled() {
    243         return mAccessibilityManager.isTouchExplorationEnabled();
    244     }
    245 
    246     protected boolean disallowSingleClick(MotionEvent ev) {
    247         return false;
    248     }
    249 
    250     protected boolean handleSlideBack() {
    251         return false;
    252     }
    253 
    254     @Override
    255     public boolean onTouchEvent(MotionEvent event) {
    256         boolean result;
    257         if (event.getAction() == MotionEvent.ACTION_DOWN) {
    258             mWasActivatedOnDown = mActivated;
    259         }
    260         if ((mNeedsDimming && !mActivated) && !isTouchExplorationEnabled() && isInteractive()) {
    261             boolean wasActivated = mActivated;
    262             result = handleTouchEventDimmed(event);
    263             if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
    264                 removeCallbacks(mTapTimeoutRunnable);
    265             }
    266         } else {
    267             result = super.onTouchEvent(event);
    268         }
    269         return result;
    270     }
    271 
    272     /**
    273      * @return whether this view is interactive and can be double tapped
    274      */
    275     protected boolean isInteractive() {
    276         return true;
    277     }
    278 
    279     @Override
    280     public void drawableHotspotChanged(float x, float y) {
    281         if (!mDimmed){
    282             mBackgroundNormal.drawableHotspotChanged(x, y);
    283         }
    284     }
    285 
    286     @Override
    287     protected void drawableStateChanged() {
    288         super.drawableStateChanged();
    289         if (mDimmed) {
    290             mBackgroundDimmed.setState(getDrawableState());
    291         } else {
    292             mBackgroundNormal.setState(getDrawableState());
    293         }
    294     }
    295 
    296     private boolean handleTouchEventDimmed(MotionEvent event) {
    297         if (mNeedsDimming && !mDimmed) {
    298             // We're actually dimmed, but our content isn't dimmable, let's ensure we have a ripple
    299             super.onTouchEvent(event);
    300         }
    301         return mDoubleTapHelper.onTouchEvent(event, getActualHeight());
    302     }
    303 
    304     @Override
    305     public boolean performClick() {
    306         if (mWasActivatedOnDown || !mNeedsDimming || isTouchExplorationEnabled()) {
    307             return super.performClick();
    308         }
    309         return false;
    310     }
    311 
    312     private void makeActive() {
    313         mFalsingManager.onNotificationActive();
    314         startActivateAnimation(false /* reverse */);
    315         mActivated = true;
    316         if (mOnActivatedListener != null) {
    317             mOnActivatedListener.onActivated(this);
    318         }
    319     }
    320 
    321     private void startActivateAnimation(final boolean reverse) {
    322         if (!isAttachedToWindow()) {
    323             return;
    324         }
    325         if (!isDimmable()) {
    326             return;
    327         }
    328         int widthHalf = mBackgroundNormal.getWidth()/2;
    329         int heightHalf = mBackgroundNormal.getActualHeight()/2;
    330         float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf);
    331         Animator animator;
    332         if (reverse) {
    333             animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
    334                     widthHalf, heightHalf, radius, 0);
    335         } else {
    336             animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
    337                     widthHalf, heightHalf, 0, radius);
    338         }
    339         mBackgroundNormal.setVisibility(View.VISIBLE);
    340         Interpolator interpolator;
    341         Interpolator alphaInterpolator;
    342         if (!reverse) {
    343             interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
    344             alphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
    345         } else {
    346             interpolator = ACTIVATE_INVERSE_INTERPOLATOR;
    347             alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR;
    348         }
    349         animator.setInterpolator(interpolator);
    350         animator.setDuration(ACTIVATE_ANIMATION_LENGTH);
    351         if (reverse) {
    352             mBackgroundNormal.setAlpha(1f);
    353             animator.addListener(new AnimatorListenerAdapter() {
    354                 @Override
    355                 public void onAnimationEnd(Animator animation) {
    356                     updateBackground();
    357                 }
    358             });
    359             animator.start();
    360         } else {
    361             mBackgroundNormal.setAlpha(0.4f);
    362             animator.start();
    363         }
    364         mBackgroundNormal.animate()
    365                 .alpha(reverse ? 0f : 1f)
    366                 .setInterpolator(alphaInterpolator)
    367                 .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    368                     @Override
    369                     public void onAnimationUpdate(ValueAnimator animation) {
    370                         float animatedFraction = animation.getAnimatedFraction();
    371                         if (reverse) {
    372                             animatedFraction = 1.0f - animatedFraction;
    373                         }
    374                         setNormalBackgroundVisibilityAmount(animatedFraction);
    375                     }
    376                 })
    377                 .setDuration(ACTIVATE_ANIMATION_LENGTH);
    378     }
    379 
    380     /**
    381      * Cancels the hotspot and makes the notification inactive.
    382      */
    383     public void makeInactive(boolean animate) {
    384         if (mActivated) {
    385             mActivated = false;
    386             if (mDimmed) {
    387                 if (animate) {
    388                     startActivateAnimation(true /* reverse */);
    389                 } else {
    390                     updateBackground();
    391                 }
    392             }
    393         }
    394         if (mOnActivatedListener != null) {
    395             mOnActivatedListener.onActivationReset(this);
    396         }
    397         removeCallbacks(mTapTimeoutRunnable);
    398     }
    399 
    400     public void setDimmed(boolean dimmed, boolean fade) {
    401         mNeedsDimming = dimmed;
    402         dimmed &= isDimmable();
    403         if (mDimmed != dimmed) {
    404             mDimmed = dimmed;
    405             resetBackgroundAlpha();
    406             if (fade) {
    407                 fadeDimmedBackground();
    408             } else {
    409                 updateBackground();
    410             }
    411         }
    412     }
    413 
    414     public boolean isDimmable() {
    415         return true;
    416     }
    417 
    418     public void setDark(boolean dark, boolean fade, long delay) {
    419         super.setDark(dark, fade, delay);
    420         if (mDark == dark) {
    421             return;
    422         }
    423         mDark = dark;
    424         updateBackground();
    425         updateBackgroundTint(false);
    426         if (!dark && fade && !shouldHideBackground()) {
    427             fadeInFromDark(delay);
    428         }
    429         updateOutlineAlpha();
    430     }
    431 
    432     private void updateOutlineAlpha() {
    433         if (mDark) {
    434             setOutlineAlpha(0f);
    435             return;
    436         }
    437         float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED;
    438         alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount);
    439         alpha *= mShadowAlpha;
    440         if (mFadeInFromDarkAnimator != null) {
    441             alpha *= mFadeInFromDarkAnimator.getAnimatedFraction();
    442         }
    443         setOutlineAlpha(alpha);
    444     }
    445 
    446     public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
    447         mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount;
    448         updateOutlineAlpha();
    449     }
    450 
    451     @Override
    452     public void setBelowSpeedBump(boolean below) {
    453         super.setBelowSpeedBump(below);
    454         if (below != mIsBelowSpeedBump) {
    455             mIsBelowSpeedBump = below;
    456             updateBackgroundTint();
    457             onBelowSpeedBumpChanged();
    458         }
    459     }
    460 
    461     protected void onBelowSpeedBumpChanged() {
    462     }
    463 
    464     /**
    465      * @return whether we are below the speed bump
    466      */
    467     public boolean isBelowSpeedBump() {
    468         return mIsBelowSpeedBump;
    469     }
    470 
    471     /**
    472      * Sets the tint color of the background
    473      */
    474     public void setTintColor(int color) {
    475         setTintColor(color, false);
    476     }
    477 
    478     /**
    479      * Sets the tint color of the background
    480      */
    481     public void setTintColor(int color, boolean animated) {
    482         if (color != mBgTint) {
    483             mBgTint = color;
    484             updateBackgroundTint(animated);
    485         }
    486     }
    487 
    488     /**
    489      * Set an override tint color that is used for the background.
    490      *
    491      * @param color the color that should be used to tint the background.
    492      *              This can be {@link #NO_COLOR} if the tint should be normally computed.
    493      * @param overrideAmount a value from 0 to 1 how much the override tint should be used. The
    494      *                       background color will then be the interpolation between this and the
    495      *                       regular background color, where 1 means the overrideTintColor is fully
    496      *                       used and the background color not at all.
    497      */
    498     public void setOverrideTintColor(int color, float overrideAmount) {
    499         if (mDark) {
    500             color = NO_COLOR;
    501             overrideAmount = 0;
    502         }
    503         mOverrideTint = color;
    504         mOverrideAmount = overrideAmount;
    505         int newColor = calculateBgColor();
    506         setBackgroundTintColor(newColor);
    507         if (!isDimmable() && mNeedsDimming) {
    508            mBackgroundNormal.setDrawableAlpha((int) NotificationUtils.interpolate(255,
    509                    mDimmedAlpha,
    510                    overrideAmount));
    511         } else {
    512             mBackgroundNormal.setDrawableAlpha(255);
    513         }
    514     }
    515 
    516     protected void updateBackgroundTint() {
    517         updateBackgroundTint(false /* animated */);
    518     }
    519 
    520     private void updateBackgroundTint(boolean animated) {
    521         if (mBackgroundColorAnimator != null) {
    522             mBackgroundColorAnimator.cancel();
    523         }
    524         int rippleColor = getRippleColor();
    525         mBackgroundDimmed.setRippleColor(rippleColor);
    526         mBackgroundNormal.setRippleColor(rippleColor);
    527         int color = calculateBgColor();
    528         if (!animated) {
    529             setBackgroundTintColor(color);
    530         } else if (color != mCurrentBackgroundTint) {
    531             mStartTint = mCurrentBackgroundTint;
    532             mTargetTint = color;
    533             mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
    534             mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    535                 @Override
    536                 public void onAnimationUpdate(ValueAnimator animation) {
    537                     int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
    538                             animation.getAnimatedFraction());
    539                     setBackgroundTintColor(newColor);
    540                 }
    541             });
    542             mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
    543             mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
    544             mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
    545                 @Override
    546                 public void onAnimationEnd(Animator animation) {
    547                     mBackgroundColorAnimator = null;
    548                 }
    549             });
    550             mBackgroundColorAnimator.start();
    551         }
    552     }
    553 
    554     private void setBackgroundTintColor(int color) {
    555         if (color != mCurrentBackgroundTint) {
    556             mCurrentBackgroundTint = color;
    557             if (color == mNormalColor) {
    558                 // We don't need to tint a normal notification
    559                 color = 0;
    560             }
    561             mBackgroundDimmed.setTint(color);
    562             mBackgroundNormal.setTint(color);
    563         }
    564     }
    565 
    566     /**
    567      * Fades in the background when exiting dark mode.
    568      */
    569     private void fadeInFromDark(long delay) {
    570         final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
    571         background.setAlpha(0f);
    572         mBackgroundVisibilityUpdater.onAnimationUpdate(null);
    573         background.animate()
    574                 .alpha(1f)
    575                 .setDuration(DARK_ANIMATION_LENGTH)
    576                 .setStartDelay(delay)
    577                 .setInterpolator(Interpolators.ALPHA_IN)
    578                 .setListener(new AnimatorListenerAdapter() {
    579                     @Override
    580                     public void onAnimationCancel(Animator animation) {
    581                         // Jump state if we are cancelled
    582                         background.setAlpha(1f);
    583                     }
    584                 })
    585                 .setUpdateListener(mBackgroundVisibilityUpdater)
    586                 .start();
    587         mFadeInFromDarkAnimator = TimeAnimator.ofFloat(0.0f, 1.0f);
    588         mFadeInFromDarkAnimator.setDuration(DARK_ANIMATION_LENGTH);
    589         mFadeInFromDarkAnimator.setStartDelay(delay);
    590         mFadeInFromDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
    591         mFadeInFromDarkAnimator.addListener(mFadeInEndListener);
    592         mFadeInFromDarkAnimator.addUpdateListener(mUpdateOutlineListener);
    593         mFadeInFromDarkAnimator.start();
    594     }
    595 
    596     /**
    597      * Fades the background when the dimmed state changes.
    598      */
    599     private void fadeDimmedBackground() {
    600         mBackgroundDimmed.animate().cancel();
    601         mBackgroundNormal.animate().cancel();
    602         if (mActivated) {
    603             updateBackground();
    604             return;
    605         }
    606         if (!shouldHideBackground()) {
    607             if (mDimmed) {
    608                 mBackgroundDimmed.setVisibility(View.VISIBLE);
    609             } else {
    610                 mBackgroundNormal.setVisibility(View.VISIBLE);
    611             }
    612         }
    613         float startAlpha = mDimmed ? 1f : 0;
    614         float endAlpha = mDimmed ? 0 : 1f;
    615         int duration = BACKGROUND_ANIMATION_LENGTH_MS;
    616         // Check whether there is already a background animation running.
    617         if (mBackgroundAnimator != null) {
    618             startAlpha = (Float) mBackgroundAnimator.getAnimatedValue();
    619             duration = (int) mBackgroundAnimator.getCurrentPlayTime();
    620             mBackgroundAnimator.removeAllListeners();
    621             mBackgroundAnimator.cancel();
    622             if (duration <= 0) {
    623                 updateBackground();
    624                 return;
    625             }
    626         }
    627         mBackgroundNormal.setAlpha(startAlpha);
    628         mBackgroundAnimator =
    629                 ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha);
    630         mBackgroundAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    631         mBackgroundAnimator.setDuration(duration);
    632         mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
    633             @Override
    634             public void onAnimationEnd(Animator animation) {
    635                 updateBackground();
    636                 mBackgroundAnimator = null;
    637                 if (mFadeInFromDarkAnimator == null) {
    638                     mDimmedBackgroundFadeInAmount = -1;
    639                 }
    640             }
    641         });
    642         mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater);
    643         mBackgroundAnimator.start();
    644     }
    645 
    646     protected void updateBackgroundAlpha(float transformationAmount) {
    647         mBgAlpha =  isChildInGroup() && mDimmed ? transformationAmount : 1f;
    648         if (mDimmedBackgroundFadeInAmount != -1) {
    649             mBgAlpha *= mDimmedBackgroundFadeInAmount;
    650         }
    651         mBackgroundDimmed.setAlpha(mBgAlpha);
    652     }
    653 
    654     protected void resetBackgroundAlpha() {
    655         updateBackgroundAlpha(0f /* transformationAmount */);
    656     }
    657 
    658     protected void updateBackground() {
    659         cancelFadeAnimations();
    660         if (shouldHideBackground()) {
    661             mBackgroundDimmed.setVisibility(INVISIBLE);
    662             mBackgroundNormal.setVisibility(mActivated ? VISIBLE : INVISIBLE);
    663         } else if (mDimmed) {
    664             // When groups are animating to the expanded state from the lockscreen, show the
    665             // normal background instead of the dimmed background
    666             final boolean dontShowDimmed = isGroupExpansionChanging() && isChildInGroup();
    667             mBackgroundDimmed.setVisibility(dontShowDimmed ? View.INVISIBLE : View.VISIBLE);
    668             mBackgroundNormal.setVisibility((mActivated || dontShowDimmed)
    669                     ? View.VISIBLE
    670                     : View.INVISIBLE);
    671         } else {
    672             mBackgroundDimmed.setVisibility(View.INVISIBLE);
    673             mBackgroundNormal.setVisibility(View.VISIBLE);
    674             mBackgroundNormal.setAlpha(1f);
    675             removeCallbacks(mTapTimeoutRunnable);
    676             // make in inactive to avoid it sticking around active
    677             makeInactive(false /* animate */);
    678         }
    679         setNormalBackgroundVisibilityAmount(
    680                 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
    681     }
    682 
    683     protected boolean shouldHideBackground() {
    684         return mDark;
    685     }
    686 
    687     private void cancelFadeAnimations() {
    688         if (mBackgroundAnimator != null) {
    689             mBackgroundAnimator.cancel();
    690         }
    691         mBackgroundDimmed.animate().cancel();
    692         mBackgroundNormal.animate().cancel();
    693     }
    694 
    695     @Override
    696     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    697         super.onLayout(changed, left, top, right, bottom);
    698         setPivotX(getWidth() / 2);
    699     }
    700 
    701     @Override
    702     public void setActualHeight(int actualHeight, boolean notifyListeners) {
    703         super.setActualHeight(actualHeight, notifyListeners);
    704         setPivotY(actualHeight / 2);
    705         mBackgroundNormal.setActualHeight(actualHeight);
    706         mBackgroundDimmed.setActualHeight(actualHeight);
    707     }
    708 
    709     @Override
    710     public void setClipTopAmount(int clipTopAmount) {
    711         super.setClipTopAmount(clipTopAmount);
    712         mBackgroundNormal.setClipTopAmount(clipTopAmount);
    713         mBackgroundDimmed.setClipTopAmount(clipTopAmount);
    714     }
    715 
    716     @Override
    717     public void setClipBottomAmount(int clipBottomAmount) {
    718         super.setClipBottomAmount(clipBottomAmount);
    719         mBackgroundNormal.setClipBottomAmount(clipBottomAmount);
    720         mBackgroundDimmed.setClipBottomAmount(clipBottomAmount);
    721     }
    722 
    723     @Override
    724     public void performRemoveAnimation(long duration, float translationDirection,
    725             Runnable onFinishedRunnable) {
    726         enableAppearDrawing(true);
    727         if (mDrawingAppearAnimation) {
    728             startAppearAnimation(false /* isAppearing */, translationDirection,
    729                     0, duration, onFinishedRunnable);
    730         } else if (onFinishedRunnable != null) {
    731             onFinishedRunnable.run();
    732         }
    733     }
    734 
    735     @Override
    736     public void performAddAnimation(long delay, long duration) {
    737         enableAppearDrawing(true);
    738         if (mDrawingAppearAnimation) {
    739             startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null);
    740         }
    741     }
    742 
    743     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
    744             long duration, final Runnable onFinishedRunnable) {
    745         cancelAppearAnimation();
    746         mAnimationTranslationY = translationDirection * getActualHeight();
    747         if (mAppearAnimationFraction == -1.0f) {
    748             // not initialized yet, we start anew
    749             if (isAppearing) {
    750                 mAppearAnimationFraction = 0.0f;
    751                 mAppearAnimationTranslation = mAnimationTranslationY;
    752             } else {
    753                 mAppearAnimationFraction = 1.0f;
    754                 mAppearAnimationTranslation = 0;
    755             }
    756         }
    757 
    758         float targetValue;
    759         if (isAppearing) {
    760             mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
    761             mCurrentAlphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
    762             targetValue = 1.0f;
    763         } else {
    764             mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
    765             mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator;
    766             targetValue = 0.0f;
    767         }
    768         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
    769                 targetValue);
    770         mAppearAnimator.setInterpolator(Interpolators.LINEAR);
    771         mAppearAnimator.setDuration(
    772                 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
    773         mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    774             @Override
    775             public void onAnimationUpdate(ValueAnimator animation) {
    776                 mAppearAnimationFraction = (float) animation.getAnimatedValue();
    777                 updateAppearAnimationAlpha();
    778                 updateAppearRect();
    779                 invalidate();
    780             }
    781         });
    782         if (delay > 0) {
    783             // we need to apply the initial state already to avoid drawn frames in the wrong state
    784             updateAppearAnimationAlpha();
    785             updateAppearRect();
    786             mAppearAnimator.setStartDelay(delay);
    787         }
    788         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
    789             private boolean mWasCancelled;
    790 
    791             @Override
    792             public void onAnimationEnd(Animator animation) {
    793                 if (onFinishedRunnable != null) {
    794                     onFinishedRunnable.run();
    795                 }
    796                 if (!mWasCancelled) {
    797                     enableAppearDrawing(false);
    798                     onAppearAnimationFinished(isAppearing);
    799                 }
    800             }
    801 
    802             @Override
    803             public void onAnimationStart(Animator animation) {
    804                 mWasCancelled = false;
    805             }
    806 
    807             @Override
    808             public void onAnimationCancel(Animator animation) {
    809                 mWasCancelled = true;
    810             }
    811         });
    812         mAppearAnimator.start();
    813     }
    814 
    815     protected void onAppearAnimationFinished(boolean wasAppearing) {
    816     }
    817 
    818     private void cancelAppearAnimation() {
    819         if (mAppearAnimator != null) {
    820             mAppearAnimator.cancel();
    821             mAppearAnimator = null;
    822         }
    823     }
    824 
    825     public void cancelAppearDrawing() {
    826         cancelAppearAnimation();
    827         enableAppearDrawing(false);
    828     }
    829 
    830     private void updateAppearRect() {
    831         float inverseFraction = (1.0f - mAppearAnimationFraction);
    832         float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
    833         float translateYTotalAmount = translationFraction * mAnimationTranslationY;
    834         mAppearAnimationTranslation = translateYTotalAmount;
    835 
    836         // handle width animation
    837         float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START))
    838                 / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END);
    839         widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction));
    840         widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction);
    841         float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) *
    842                 widthFraction);
    843         float right = getWidth() - left;
    844 
    845         // handle top animation
    846         float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) /
    847                 VERTICAL_ANIMATION_START;
    848         heightFraction = Math.max(0.0f, heightFraction);
    849         heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction);
    850 
    851         float top;
    852         float bottom;
    853         final int actualHeight = getActualHeight();
    854         if (mAnimationTranslationY > 0.0f) {
    855             bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f
    856                     - translateYTotalAmount;
    857             top = bottom * heightFraction;
    858         } else {
    859             top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f -
    860                     translateYTotalAmount;
    861             bottom = actualHeight * (1 - heightFraction) + top * heightFraction;
    862         }
    863         mAppearAnimationRect.set(left, top, right, bottom);
    864         setOutlineRect(left, top + mAppearAnimationTranslation, right,
    865                 bottom + mAppearAnimationTranslation);
    866     }
    867 
    868     private void updateAppearAnimationAlpha() {
    869         float contentAlphaProgress = mAppearAnimationFraction;
    870         contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
    871         contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
    872         contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
    873         setContentAlpha(contentAlphaProgress);
    874     }
    875 
    876     private void setContentAlpha(float contentAlpha) {
    877         View contentView = getContentView();
    878         if (contentView.hasOverlappingRendering()) {
    879             int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE
    880                     : LAYER_TYPE_HARDWARE;
    881             int currentLayerType = contentView.getLayerType();
    882             if (currentLayerType != layerType) {
    883                 contentView.setLayerType(layerType, null);
    884             }
    885         }
    886         contentView.setAlpha(contentAlpha);
    887     }
    888 
    889     protected abstract View getContentView();
    890 
    891     public int calculateBgColor() {
    892         return calculateBgColor(true /* withTint */, true /* withOverRide */);
    893     }
    894 
    895     /**
    896      * @param withTint should a possible tint be factored in?
    897      * @param withOverRide should the value be interpolated with {@link #mOverrideTint}
    898      * @return the calculated background color
    899      */
    900     private int calculateBgColor(boolean withTint, boolean withOverRide) {
    901         if (withTint && mDark) {
    902             return getContext().getColor(R.color.notification_material_background_dark_color);
    903         }
    904         if (withOverRide && mOverrideTint != NO_COLOR) {
    905             int defaultTint = calculateBgColor(withTint, false);
    906             return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount);
    907         }
    908         if (withTint && mBgTint != NO_COLOR) {
    909             return mBgTint;
    910         } else if (mIsBelowSpeedBump) {
    911             return mLowPriorityColor;
    912         } else {
    913             return mNormalColor;
    914         }
    915     }
    916 
    917     protected int getRippleColor() {
    918         if (mBgTint != 0) {
    919             return mTintedRippleColor;
    920         } else if (mIsBelowSpeedBump) {
    921             return mLowPriorityRippleColor;
    922         } else {
    923             return mNormalRippleColor;
    924         }
    925     }
    926 
    927     /**
    928      * When we draw the appear animation, we render the view in a bitmap and render this bitmap
    929      * as a shader of a rect. This call creates the Bitmap and switches the drawing mode,
    930      * such that the normal drawing of the views does not happen anymore.
    931      *
    932      * @param enable Should it be enabled.
    933      */
    934     private void enableAppearDrawing(boolean enable) {
    935         if (enable != mDrawingAppearAnimation) {
    936             mDrawingAppearAnimation = enable;
    937             if (!enable) {
    938                 setContentAlpha(1.0f);
    939                 mAppearAnimationFraction = -1;
    940                 setOutlineRect(null);
    941             }
    942             invalidate();
    943         }
    944     }
    945 
    946     @Override
    947     protected void dispatchDraw(Canvas canvas) {
    948         if (mDrawingAppearAnimation) {
    949             canvas.save();
    950             canvas.translate(0, mAppearAnimationTranslation);
    951         }
    952         super.dispatchDraw(canvas);
    953         if (mDrawingAppearAnimation) {
    954             canvas.restore();
    955         }
    956     }
    957 
    958     public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
    959         mOnActivatedListener = onActivatedListener;
    960     }
    961 
    962     public boolean hasSameBgColor(ActivatableNotificationView otherView) {
    963         return calculateBgColor() == otherView.calculateBgColor();
    964     }
    965 
    966     @Override
    967     public float getShadowAlpha() {
    968         return mShadowAlpha;
    969     }
    970 
    971     @Override
    972     public void setShadowAlpha(float shadowAlpha) {
    973         if (shadowAlpha != mShadowAlpha) {
    974             mShadowAlpha = shadowAlpha;
    975             updateOutlineAlpha();
    976         }
    977     }
    978 
    979     @Override
    980     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
    981             int outlineTranslation) {
    982         boolean hiddenBefore = mShadowHidden;
    983         mShadowHidden = shadowIntensity == 0.0f;
    984         if (!mShadowHidden || !hiddenBefore) {
    985             mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ()
    986                             + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd,
    987                     outlineTranslation);
    988         }
    989     }
    990 
    991     public int getBackgroundColorWithoutTint() {
    992         return calculateBgColor(false /* withTint */, false /* withOverride */);
    993     }
    994 
    995     public interface OnActivatedListener {
    996         void onActivated(ActivatableNotificationView view);
    997         void onActivationReset(ActivatableNotificationView view);
    998     }
    999 }
   1000