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