• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ActivatableNotificationView(Context context, AttributeSet attrs)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
onFinishInflate()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
onInterceptTouchEvent(MotionEvent ev)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 
disallowSingleClick(MotionEvent ev)220     protected boolean disallowSingleClick(MotionEvent ev) {
221         return false;
222     }
223 
handleSlideBack()224     protected boolean handleSlideBack() {
225         return false;
226     }
227 
228     @Override
onTouchEvent(MotionEvent event)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
drawableHotspotChanged(float x, float y)245     public void drawableHotspotChanged(float x, float y) {
246         if (!mDimmed){
247             mBackgroundNormal.drawableHotspotChanged(x, y);
248         }
249     }
250 
251     @Override
drawableStateChanged()252     protected void drawableStateChanged() {
253         super.drawableStateChanged();
254         if (mDimmed) {
255             mBackgroundDimmed.setState(getDrawableState());
256         } else {
257             mBackgroundNormal.setState(getDrawableState());
258         }
259     }
260 
handleTouchEventDimmed(MotionEvent event)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 
makeActive()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 
startActivateAnimation(final boolean reverse)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      */
makeInactive(boolean animate)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 
isWithinTouchSlop(MotionEvent event)391     private boolean isWithinTouchSlop(MotionEvent event) {
392         return Math.abs(event.getX() - mDownX) < mTouchSlop
393                 && Math.abs(event.getY() - mDownY) < mTouchSlop;
394     }
395 
setDimmed(boolean dimmed, boolean fade)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 
setDark(boolean dark, boolean fade, long delay)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 
updateOutlineAlpha()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 
setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount)435     public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
436         mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount;
437         updateOutlineAlpha();
438     }
439 
setShowingLegacyBackground(boolean showing)440     public void setShowingLegacyBackground(boolean showing) {
441         mShowingLegacyBackground = showing;
442         updateBackgroundTint();
443     }
444 
445     @Override
setBelowSpeedBump(boolean below)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      */
setTintColor(int color)457     public void setTintColor(int color) {
458         setTintColor(color, false);
459     }
460 
461     /**
462      * Sets the tint color of the background
463      */
setTintColor(int color, boolean animated)464     public void setTintColor(int color, boolean animated) {
465         mBgTint = color;
466         updateBackgroundTint(animated);
467     }
468 
updateBackgroundTint()469     protected void updateBackgroundTint() {
470         updateBackgroundTint(false /* animated */);
471     }
472 
updateBackgroundTint(boolean animated)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 
setBackgroundTintColor(int color)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      */
fadeInFromDark(long delay)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      */
fadeDimmedBackground()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 
updateBackgroundAlpha(float transformationAmount)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 
resetBackgroundAlpha()610     protected void resetBackgroundAlpha() {
611         updateBackgroundAlpha(0f /* transformationAmount */);
612     }
613 
updateBackground()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 
shouldHideBackground()639     protected boolean shouldHideBackground() {
640         return mDark;
641     }
642 
cancelFadeAnimations()643     private void cancelFadeAnimations() {
644         if (mBackgroundAnimator != null) {
645             mBackgroundAnimator.cancel();
646         }
647         mBackgroundDimmed.animate().cancel();
648         mBackgroundNormal.animate().cancel();
649     }
650 
651     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)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
setActualHeight(int actualHeight, boolean notifyListeners)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
setClipTopAmount(int clipTopAmount)666     public void setClipTopAmount(int clipTopAmount) {
667         super.setClipTopAmount(clipTopAmount);
668         mBackgroundNormal.setClipTopAmount(clipTopAmount);
669         mBackgroundDimmed.setClipTopAmount(clipTopAmount);
670     }
671 
672     @Override
performRemoveAnimation(long duration, float translationDirection, Runnable onFinishedRunnable)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
performAddAnimation(long delay, long duration)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 
startAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, final Runnable onFinishedRunnable)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 
onAppearAnimationFinished(boolean wasAppearing)764     protected void onAppearAnimationFinished(boolean wasAppearing) {
765     }
766 
cancelAppearAnimation()767     private void cancelAppearAnimation() {
768         if (mAppearAnimator != null) {
769             mAppearAnimator.cancel();
770             mAppearAnimator = null;
771         }
772     }
773 
cancelAppearDrawing()774     public void cancelAppearDrawing() {
775         cancelAppearAnimation();
776         enableAppearDrawing(false);
777     }
778 
updateAppearRect()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 
updateAppearAnimationAlpha()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 
setContentAlpha(float contentAlpha)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 
getContentView()838     protected abstract View getContentView();
839 
calculateBgColor()840     public int calculateBgColor() {
841         return calculateBgColor(true /* withTint */);
842     }
843 
calculateBgColor(boolean withTint)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 
getRippleColor()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      */
enableAppearDrawing(boolean enable)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
dispatchDraw(Canvas canvas)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 
setOnActivatedListener(OnActivatedListener onActivatedListener)899     public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
900         mOnActivatedListener = onActivatedListener;
901     }
902 
reset()903     public void reset() {
904         setTintColor(0);
905         resetBackgroundAlpha();
906         setShowingLegacyBackground(false);
907         setBelowSpeedBump(false);
908     }
909 
hasSameBgColor(ActivatableNotificationView otherView)910     public boolean hasSameBgColor(ActivatableNotificationView otherView) {
911         return calculateBgColor() == otherView.calculateBgColor();
912     }
913 
914     @Override
getShadowAlpha()915     public float getShadowAlpha() {
916         return mShadowAlpha;
917     }
918 
919     @Override
setShadowAlpha(float shadowAlpha)920     public void setShadowAlpha(float shadowAlpha) {
921         if (shadowAlpha != mShadowAlpha) {
922             mShadowAlpha = shadowAlpha;
923             updateOutlineAlpha();
924         }
925     }
926 
927     @Override
setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation)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 
getBackgroundColorWithoutTint()935     public int getBackgroundColorWithoutTint() {
936         return calculateBgColor(false /* withTint */);
937     }
938 
939     public interface OnActivatedListener {
onActivated(ActivatableNotificationView view)940         void onActivated(ActivatableNotificationView view);
onActivationReset(ActivatableNotificationView view)941         void onActivationReset(ActivatableNotificationView view);
942     }
943 }
944