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                 if (mFadeInFromDarkAnimator == null) {
    597                     mDimmedBackgroundFadeInAmount = -1;
    598                 }
    599             }
    600         });
    601         mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater);
    602         mBackgroundAnimator.start();
    603     }
    604 
    605     protected void updateBackgroundAlpha(float transformationAmount) {
    606         mBgAlpha =  isChildInGroup() && mDimmed ? transformationAmount : 1f;
    607         if (mDimmedBackgroundFadeInAmount != -1) {
    608             mBgAlpha *= mDimmedBackgroundFadeInAmount;
    609         }
    610         mBackgroundDimmed.setAlpha(mBgAlpha);
    611     }
    612 
    613     protected void resetBackgroundAlpha() {
    614         updateBackgroundAlpha(0f /* transformationAmount */);
    615     }
    616 
    617     protected void updateBackground() {
    618         cancelFadeAnimations();
    619         if (shouldHideBackground()) {
    620             mBackgroundDimmed.setVisibility(View.INVISIBLE);
    621             mBackgroundNormal.setVisibility(View.INVISIBLE);
    622         } else if (mDimmed) {
    623             // When groups are animating to the expanded state from the lockscreen, show the
    624             // normal background instead of the dimmed background
    625             final boolean dontShowDimmed = isGroupExpansionChanging() && isChildInGroup();
    626             mBackgroundDimmed.setVisibility(dontShowDimmed ? View.INVISIBLE : View.VISIBLE);
    627             mBackgroundNormal.setVisibility((mActivated || dontShowDimmed)
    628                     ? View.VISIBLE
    629                     : View.INVISIBLE);
    630         } else {
    631             mBackgroundDimmed.setVisibility(View.INVISIBLE);
    632             mBackgroundNormal.setVisibility(View.VISIBLE);
    633             mBackgroundNormal.setAlpha(1f);
    634             removeCallbacks(mTapTimeoutRunnable);
    635             // make in inactive to avoid it sticking around active
    636             makeInactive(false /* animate */);
    637         }
    638         setNormalBackgroundVisibilityAmount(
    639                 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
    640     }
    641 
    642     protected boolean shouldHideBackground() {
    643         return mDark;
    644     }
    645 
    646     private void cancelFadeAnimations() {
    647         if (mBackgroundAnimator != null) {
    648             mBackgroundAnimator.cancel();
    649         }
    650         mBackgroundDimmed.animate().cancel();
    651         mBackgroundNormal.animate().cancel();
    652     }
    653 
    654     @Override
    655     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    656         super.onLayout(changed, left, top, right, bottom);
    657         setPivotX(getWidth() / 2);
    658     }
    659 
    660     @Override
    661     public void setActualHeight(int actualHeight, boolean notifyListeners) {
    662         super.setActualHeight(actualHeight, notifyListeners);
    663         setPivotY(actualHeight / 2);
    664         mBackgroundNormal.setActualHeight(actualHeight);
    665         mBackgroundDimmed.setActualHeight(actualHeight);
    666     }
    667 
    668     @Override
    669     public void setClipTopAmount(int clipTopAmount) {
    670         super.setClipTopAmount(clipTopAmount);
    671         mBackgroundNormal.setClipTopAmount(clipTopAmount);
    672         mBackgroundDimmed.setClipTopAmount(clipTopAmount);
    673     }
    674 
    675     @Override
    676     public void performRemoveAnimation(long duration, float translationDirection,
    677             Runnable onFinishedRunnable) {
    678         enableAppearDrawing(true);
    679         if (mDrawingAppearAnimation) {
    680             startAppearAnimation(false /* isAppearing */, translationDirection,
    681                     0, duration, onFinishedRunnable);
    682         } else if (onFinishedRunnable != null) {
    683             onFinishedRunnable.run();
    684         }
    685     }
    686 
    687     @Override
    688     public void performAddAnimation(long delay, long duration) {
    689         enableAppearDrawing(true);
    690         if (mDrawingAppearAnimation) {
    691             startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null);
    692         }
    693     }
    694 
    695     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
    696             long duration, final Runnable onFinishedRunnable) {
    697         cancelAppearAnimation();
    698         mAnimationTranslationY = translationDirection * getActualHeight();
    699         if (mAppearAnimationFraction == -1.0f) {
    700             // not initialized yet, we start anew
    701             if (isAppearing) {
    702                 mAppearAnimationFraction = 0.0f;
    703                 mAppearAnimationTranslation = mAnimationTranslationY;
    704             } else {
    705                 mAppearAnimationFraction = 1.0f;
    706                 mAppearAnimationTranslation = 0;
    707             }
    708         }
    709 
    710         float targetValue;
    711         if (isAppearing) {
    712             mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
    713             mCurrentAlphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
    714             targetValue = 1.0f;
    715         } else {
    716             mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
    717             mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator;
    718             targetValue = 0.0f;
    719         }
    720         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
    721                 targetValue);
    722         mAppearAnimator.setInterpolator(Interpolators.LINEAR);
    723         mAppearAnimator.setDuration(
    724                 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
    725         mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    726             @Override
    727             public void onAnimationUpdate(ValueAnimator animation) {
    728                 mAppearAnimationFraction = (float) animation.getAnimatedValue();
    729                 updateAppearAnimationAlpha();
    730                 updateAppearRect();
    731                 invalidate();
    732             }
    733         });
    734         if (delay > 0) {
    735             // we need to apply the initial state already to avoid drawn frames in the wrong state
    736             updateAppearAnimationAlpha();
    737             updateAppearRect();
    738             mAppearAnimator.setStartDelay(delay);
    739         }
    740         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
    741             private boolean mWasCancelled;
    742 
    743             @Override
    744             public void onAnimationEnd(Animator animation) {
    745                 if (onFinishedRunnable != null) {
    746                     onFinishedRunnable.run();
    747                 }
    748                 if (!mWasCancelled) {
    749                     enableAppearDrawing(false);
    750                     onAppearAnimationFinished(isAppearing);
    751                 }
    752             }
    753 
    754             @Override
    755             public void onAnimationStart(Animator animation) {
    756                 mWasCancelled = false;
    757             }
    758 
    759             @Override
    760             public void onAnimationCancel(Animator animation) {
    761                 mWasCancelled = true;
    762             }
    763         });
    764         mAppearAnimator.start();
    765     }
    766 
    767     protected void onAppearAnimationFinished(boolean wasAppearing) {
    768     }
    769 
    770     private void cancelAppearAnimation() {
    771         if (mAppearAnimator != null) {
    772             mAppearAnimator.cancel();
    773             mAppearAnimator = null;
    774         }
    775     }
    776 
    777     public void cancelAppearDrawing() {
    778         cancelAppearAnimation();
    779         enableAppearDrawing(false);
    780     }
    781 
    782     private void updateAppearRect() {
    783         float inverseFraction = (1.0f - mAppearAnimationFraction);
    784         float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
    785         float translateYTotalAmount = translationFraction * mAnimationTranslationY;
    786         mAppearAnimationTranslation = translateYTotalAmount;
    787 
    788         // handle width animation
    789         float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START))
    790                 / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END);
    791         widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction));
    792         widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction);
    793         float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) *
    794                 widthFraction);
    795         float right = getWidth() - left;
    796 
    797         // handle top animation
    798         float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) /
    799                 VERTICAL_ANIMATION_START;
    800         heightFraction = Math.max(0.0f, heightFraction);
    801         heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction);
    802 
    803         float top;
    804         float bottom;
    805         final int actualHeight = getActualHeight();
    806         if (mAnimationTranslationY > 0.0f) {
    807             bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f
    808                     - translateYTotalAmount;
    809             top = bottom * heightFraction;
    810         } else {
    811             top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f -
    812                     translateYTotalAmount;
    813             bottom = actualHeight * (1 - heightFraction) + top * heightFraction;
    814         }
    815         mAppearAnimationRect.set(left, top, right, bottom);
    816         setOutlineRect(left, top + mAppearAnimationTranslation, right,
    817                 bottom + mAppearAnimationTranslation);
    818     }
    819 
    820     private void updateAppearAnimationAlpha() {
    821         float contentAlphaProgress = mAppearAnimationFraction;
    822         contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
    823         contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
    824         contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
    825         setContentAlpha(contentAlphaProgress);
    826     }
    827 
    828     private void setContentAlpha(float contentAlpha) {
    829         View contentView = getContentView();
    830         if (contentView.hasOverlappingRendering()) {
    831             int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE
    832                     : LAYER_TYPE_HARDWARE;
    833             int currentLayerType = contentView.getLayerType();
    834             if (currentLayerType != layerType) {
    835                 contentView.setLayerType(layerType, null);
    836             }
    837         }
    838         contentView.setAlpha(contentAlpha);
    839     }
    840 
    841     protected abstract View getContentView();
    842 
    843     public int calculateBgColor() {
    844         return calculateBgColor(true /* withTint */);
    845     }
    846 
    847     private int calculateBgColor(boolean withTint) {
    848         if (withTint && mBgTint != 0) {
    849             return mBgTint;
    850         } else if (mShowingLegacyBackground) {
    851             return mLegacyColor;
    852         } else if (mIsBelowSpeedBump) {
    853             return mLowPriorityColor;
    854         } else {
    855             return mNormalColor;
    856         }
    857     }
    858 
    859     protected int getRippleColor() {
    860         if (mBgTint != 0) {
    861             return mTintedRippleColor;
    862         } else if (mShowingLegacyBackground) {
    863             return mTintedRippleColor;
    864         } else if (mIsBelowSpeedBump) {
    865             return mLowPriorityRippleColor;
    866         } else {
    867             return mNormalRippleColor;
    868         }
    869     }
    870 
    871     /**
    872      * When we draw the appear animation, we render the view in a bitmap and render this bitmap
    873      * as a shader of a rect. This call creates the Bitmap and switches the drawing mode,
    874      * such that the normal drawing of the views does not happen anymore.
    875      *
    876      * @param enable Should it be enabled.
    877      */
    878     private void enableAppearDrawing(boolean enable) {
    879         if (enable != mDrawingAppearAnimation) {
    880             mDrawingAppearAnimation = enable;
    881             if (!enable) {
    882                 setContentAlpha(1.0f);
    883                 mAppearAnimationFraction = -1;
    884                 setOutlineRect(null);
    885             }
    886             invalidate();
    887         }
    888     }
    889 
    890     @Override
    891     protected void dispatchDraw(Canvas canvas) {
    892         if (mDrawingAppearAnimation) {
    893             canvas.save();
    894             canvas.translate(0, mAppearAnimationTranslation);
    895         }
    896         super.dispatchDraw(canvas);
    897         if (mDrawingAppearAnimation) {
    898             canvas.restore();
    899         }
    900     }
    901 
    902     public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
    903         mOnActivatedListener = onActivatedListener;
    904     }
    905 
    906     public void reset() {
    907         setTintColor(0);
    908         resetBackgroundAlpha();
    909         setShowingLegacyBackground(false);
    910         setBelowSpeedBump(false);
    911     }
    912 
    913     public boolean hasSameBgColor(ActivatableNotificationView otherView) {
    914         return calculateBgColor() == otherView.calculateBgColor();
    915     }
    916 
    917     @Override
    918     public float getShadowAlpha() {
    919         return mShadowAlpha;
    920     }
    921 
    922     @Override
    923     public void setShadowAlpha(float shadowAlpha) {
    924         if (shadowAlpha != mShadowAlpha) {
    925             mShadowAlpha = shadowAlpha;
    926             updateOutlineAlpha();
    927         }
    928     }
    929 
    930     @Override
    931     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
    932             int outlineTranslation) {
    933         mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ()
    934                 + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd,
    935                 outlineTranslation);
    936     }
    937 
    938     public int getBackgroundColorWithoutTint() {
    939         return calculateBgColor(false /* withTint */);
    940     }
    941 
    942     public interface OnActivatedListener {
    943         void onActivated(ActivatableNotificationView view);
    944         void onActivationReset(ActivatableNotificationView view);
    945     }
    946 }
    947