OSDN Git Service

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