2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar.notification.row;
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;
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;
48 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
49 * to implement dimming/activating on Keyguard for the double-tap gesture
51 public abstract class ActivatableNotificationView extends ExpandableOutlineView {
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;
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)
61 private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f;
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.
67 private static final float HORIZONTAL_ANIMATION_END = 0.2f;
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.
73 private static final float ALPHA_ANIMATION_END = 0.0f;
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.
79 private static final float HORIZONTAL_ANIMATION_START = 1.0f;
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.
85 private static final float VERTICAL_ANIMATION_START = 1.0f;
88 * Scale for the background to animate from when exiting dark mode.
90 private static final float DARK_EXIT_SCALE_START = 0.93f;
93 * A sentinel value when no color should be used. Can be used with {@link #setTintColor(int)}
94 * or {@link #setOverrideTintColor(int, float)}.
96 protected static final int NO_COLOR = 0;
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;
107 private boolean mDimmed;
108 protected boolean mDark;
110 protected int mBgTint = NO_COLOR;
111 private float mBgAlpha = 1f;
114 * Flag to indicate that the notification has been touched once and the second touch will
117 private boolean mActivated;
119 private OnActivatedListener mOnActivatedListener;
121 private final Interpolator mSlowOutFastInInterpolator;
122 private final Interpolator mSlowOutLinearInInterpolator;
123 private Interpolator mCurrentAppearInterpolator;
124 private Interpolator mCurrentAlphaInterpolator;
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;
142 private float mNormalBackgroundVisibilityAmount;
143 private float mDimmedBackgroundFadeInAmount = -1;
144 private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater
145 = new ValueAnimator.AnimatorUpdateListener() {
147 public void onAnimationUpdate(ValueAnimator animation) {
148 setNormalBackgroundVisibilityAmount(mBackgroundNormal.getAlpha());
149 mDimmedBackgroundFadeInAmount = mBackgroundDimmed.getAlpha();
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;
160 * Similar to mDimmed but is also true if it's not dimmable but should be
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;
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);
177 mFalsingManager = FalsingManagerFactory.getInstance(context);
178 mAccessibilityManager = AccessibilityManager.getInstance(mContext);
180 mDoubleTapHelper = new DoubleTapHelper(this, (active) -> {
184 makeInactive(true /* animate */);
186 }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
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));
200 private void initDimens() {
201 mHeadsUpAddStartLocation = getResources().getDimensionPixelSize(
202 com.android.internal.R.dimen.notification_content_margin_start);
206 public void onDensityOrFontScaleChanged() {
207 super.onDensityOrFontScaleChanged();
211 protected void updateBackgroundColors() {
214 updateBackgroundTint();
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);
226 updateBackgroundTint();
227 updateOutlineAlpha();
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.
235 protected void initBackground() {
236 mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
237 mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
240 private final Runnable mTapTimeoutRunnable = new Runnable() {
243 makeInactive(true /* animate */);
248 public boolean onInterceptTouchEvent(MotionEvent ev) {
249 if (mNeedsDimming && ev.getActionMasked() == MotionEvent.ACTION_DOWN
250 && disallowSingleClick(ev) && !isTouchExplorationEnabled()) {
253 } else if (!mDoubleTapHelper.isWithinDoubleTapSlop(ev)) {
254 mBlockNextTouch = true;
255 makeInactive(true /* animate */);
259 return super.onInterceptTouchEvent(ev);
262 private boolean isTouchExplorationEnabled() {
263 return mAccessibilityManager.isTouchExplorationEnabled();
266 protected boolean disallowSingleClick(MotionEvent ev) {
270 protected boolean handleSlideBack() {
275 public boolean onTouchEvent(MotionEvent event) {
277 if (mBlockNextTouch) {
278 mBlockNextTouch = false;
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);
288 result = super.onTouchEvent(event);
294 * @return whether this view is interactive and can be double tapped
296 protected boolean isInteractive() {
301 public void drawableHotspotChanged(float x, float y) {
303 mBackgroundNormal.drawableHotspotChanged(x, y);
308 protected void drawableStateChanged() {
309 super.drawableStateChanged();
311 mBackgroundDimmed.setState(getDrawableState());
313 mBackgroundNormal.setState(getDrawableState());
317 public void setRippleAllowed(boolean allowed) {
318 mBackgroundNormal.setPressedAllowed(allowed);
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);
326 return mDoubleTapHelper.onTouchEvent(event, getActualHeight());
330 public boolean performClick() {
331 if (!mNeedsDimming || isTouchExplorationEnabled()) {
332 return super.performClick();
337 private void makeActive() {
338 mFalsingManager.onNotificationActive();
339 startActivateAnimation(false /* reverse */);
341 if (mOnActivatedListener != null) {
342 mOnActivatedListener.onActivated(this);
346 private void startActivateAnimation(final boolean reverse) {
347 if (!isAttachedToWindow()) {
353 int widthHalf = mBackgroundNormal.getWidth()/2;
354 int heightHalf = mBackgroundNormal.getActualHeight()/2;
355 float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf);
358 animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
359 widthHalf, heightHalf, radius, 0);
361 animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
362 widthHalf, heightHalf, 0, radius);
364 mBackgroundNormal.setVisibility(View.VISIBLE);
365 Interpolator interpolator;
366 Interpolator alphaInterpolator;
368 interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
369 alphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
371 interpolator = ACTIVATE_INVERSE_INTERPOLATOR;
372 alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR;
374 animator.setInterpolator(interpolator);
375 animator.setDuration(ACTIVATE_ANIMATION_LENGTH);
377 mBackgroundNormal.setAlpha(1f);
378 animator.addListener(new AnimatorListenerAdapter() {
380 public void onAnimationEnd(Animator animation) {
386 mBackgroundNormal.setAlpha(0.4f);
389 mBackgroundNormal.animate()
390 .alpha(reverse ? 0f : 1f)
391 .setInterpolator(alphaInterpolator)
392 .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
394 public void onAnimationUpdate(ValueAnimator animation) {
395 float animatedFraction = animation.getAnimatedFraction();
397 animatedFraction = 1.0f - animatedFraction;
399 setNormalBackgroundVisibilityAmount(animatedFraction);
402 .setDuration(ACTIVATE_ANIMATION_LENGTH);
406 * Cancels the hotspot and makes the notification inactive.
408 public void makeInactive(boolean animate) {
413 startActivateAnimation(true /* reverse */);
419 if (mOnActivatedListener != null) {
420 mOnActivatedListener.onActivationReset(this);
422 removeCallbacks(mTapTimeoutRunnable);
425 public void setDimmed(boolean dimmed, boolean fade) {
426 mNeedsDimming = dimmed;
427 dimmed &= isDimmable();
428 if (mDimmed != dimmed) {
430 resetBackgroundAlpha();
432 fadeDimmedBackground();
439 public boolean isDimmable() {
443 public void setDark(boolean dark, boolean fade, long delay) {
444 super.setDark(dark, fade, delay);
450 updateBackgroundTint(false);
453 private void updateOutlineAlpha() {
454 float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED;
455 alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount);
456 setOutlineAlpha(alpha);
459 public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
460 mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount;
461 updateOutlineAlpha();
465 public void setBelowSpeedBump(boolean below) {
466 super.setBelowSpeedBump(below);
467 if (below != mIsBelowSpeedBump) {
468 mIsBelowSpeedBump = below;
469 updateBackgroundTint();
470 onBelowSpeedBumpChanged();
474 protected void onBelowSpeedBumpChanged() {
478 * @return whether we are below the speed bump
480 public boolean isBelowSpeedBump() {
481 return mIsBelowSpeedBump;
485 * Sets the tint color of the background
487 public void setTintColor(int color) {
488 setTintColor(color, false);
492 * Sets the tint color of the background
494 public void setTintColor(int color, boolean animated) {
495 if (color != mBgTint) {
497 updateBackgroundTint(animated);
502 public void setDistanceToTopRoundness(float distanceToTopRoundness) {
503 super.setDistanceToTopRoundness(distanceToTopRoundness);
504 mBackgroundNormal.setDistanceToTopRoundness(distanceToTopRoundness);
505 mBackgroundDimmed.setDistanceToTopRoundness(distanceToTopRoundness);
508 public boolean isLastInSection() {
509 return mLastInSection;
512 public boolean isFirstInSection() {
513 return mFirstInSection;
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);
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);
535 * Set an override tint color that is used for the background.
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.
544 public void setOverrideTintColor(int color, float overrideAmount) {
549 mOverrideTint = color;
550 mOverrideAmount = overrideAmount;
551 int newColor = calculateBgColor();
552 setBackgroundTintColor(newColor);
553 if (!isDimmable() && mNeedsDimming) {
554 mBackgroundNormal.setDrawableAlpha((int) NotificationUtils.interpolate(255,
558 mBackgroundNormal.setDrawableAlpha(255);
562 protected void updateBackgroundTint() {
563 updateBackgroundTint(false /* animated */);
566 private void updateBackgroundTint(boolean animated) {
567 if (mBackgroundColorAnimator != null) {
568 mBackgroundColorAnimator.cancel();
570 int rippleColor = getRippleColor();
571 mBackgroundDimmed.setRippleColor(rippleColor);
572 mBackgroundNormal.setRippleColor(rippleColor);
573 int color = calculateBgColor();
575 setBackgroundTintColor(color);
576 } else if (color != mCurrentBackgroundTint) {
577 mStartTint = mCurrentBackgroundTint;
579 mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
580 mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
582 public void onAnimationUpdate(ValueAnimator animation) {
583 int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
584 animation.getAnimatedFraction());
585 setBackgroundTintColor(newColor);
588 mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
589 mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
590 mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
592 public void onAnimationEnd(Animator animation) {
593 mBackgroundColorAnimator = null;
596 mBackgroundColorAnimator.start();
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
607 mBackgroundDimmed.setTint(color);
608 mBackgroundNormal.setTint(color);
613 * Fades the background when the dimmed state changes.
615 private void fadeDimmedBackground() {
616 mBackgroundDimmed.animate().cancel();
617 mBackgroundNormal.animate().cancel();
622 if (!shouldHideBackground()) {
624 mBackgroundDimmed.setVisibility(View.VISIBLE);
626 mBackgroundNormal.setVisibility(View.VISIBLE);
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();
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() {
650 public void onAnimationEnd(Animator animation) {
652 mBackgroundAnimator = null;
653 mDimmedBackgroundFadeInAmount = -1;
656 mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater);
657 mBackgroundAnimator.start();
660 protected void updateBackgroundAlpha(float transformationAmount) {
661 mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f;
662 if (mDimmedBackgroundFadeInAmount != -1) {
663 mBgAlpha *= mDimmedBackgroundFadeInAmount;
665 mBackgroundDimmed.setAlpha(mBgAlpha);
668 protected void resetBackgroundAlpha() {
669 updateBackgroundAlpha(0f /* transformationAmount */);
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)
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 */);
693 setNormalBackgroundVisibilityAmount(
694 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
697 protected void updateBackgroundClipping() {
698 mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
699 mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
702 protected boolean shouldHideBackground() {
706 private void cancelFadeAnimations() {
707 if (mBackgroundAnimator != null) {
708 mBackgroundAnimator.cancel();
710 mBackgroundDimmed.animate().cancel();
711 mBackgroundNormal.animate().cancel();
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);
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);
729 public void setClipTopAmount(int clipTopAmount) {
730 super.setClipTopAmount(clipTopAmount);
731 mBackgroundNormal.setClipTopAmount(clipTopAmount);
732 mBackgroundDimmed.setClipTopAmount(clipTopAmount);
736 public void setClipBottomAmount(int clipBottomAmount) {
737 super.setClipBottomAmount(clipBottomAmount);
738 mBackgroundNormal.setClipBottomAmount(clipBottomAmount);
739 mBackgroundDimmed.setClipBottomAmount(clipBottomAmount);
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();
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);
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
777 mAppearAnimationFraction = 0.0f;
778 mAppearAnimationTranslation = mAnimationTranslationY;
780 mAppearAnimationFraction = 1.0f;
781 mAppearAnimationTranslation = 0;
784 mIsAppearing = isAppearing;
788 mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
789 mCurrentAlphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
792 mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
793 mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator;
796 mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
798 mAppearAnimator.setInterpolator(Interpolators.LINEAR);
799 mAppearAnimator.setDuration(
800 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
801 mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
803 public void onAnimationUpdate(ValueAnimator animation) {
804 mAppearAnimationFraction = (float) animation.getAnimatedValue();
805 updateAppearAnimationAlpha();
810 if (animationListener != null) {
811 mAppearAnimator.addListener(animationListener);
814 // we need to apply the initial state already to avoid drawn frames in the wrong state
815 updateAppearAnimationAlpha();
817 mAppearAnimator.setStartDelay(delay);
819 mAppearAnimator.addListener(new AnimatorListenerAdapter() {
820 private boolean mWasCancelled;
823 public void onAnimationEnd(Animator animation) {
824 if (onFinishedRunnable != null) {
825 onFinishedRunnable.run();
827 if (!mWasCancelled) {
828 enableAppearDrawing(false);
829 onAppearAnimationFinished(isAppearing);
834 public void onAnimationStart(Animator animation) {
835 mWasCancelled = false;
839 public void onAnimationCancel(Animator animation) {
840 mWasCancelled = true;
843 mAppearAnimator.start();
846 protected void onAppearAnimationFinished(boolean wasAppearing) {
849 private void cancelAppearAnimation() {
850 if (mAppearAnimator != null) {
851 mAppearAnimator.cancel();
852 mAppearAnimator = null;
856 public void cancelAppearDrawing() {
857 cancelAppearAnimation();
858 enableAppearDrawing(false);
861 private void updateAppearRect() {
862 float inverseFraction = (1.0f - mAppearAnimationFraction);
863 float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
864 float translateYTotalAmount = translationFraction * mAnimationTranslationY;
865 mAppearAnimationTranslation = translateYTotalAmount;
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;
876 float width = MathUtils.lerp(startWidthFraction, 1.0f, 1.0f - widthFraction)
880 if (mIsHeadsUpAnimation) {
881 left = MathUtils.lerp(mHeadsUpLocation, 0, 1.0f - widthFraction);
882 right = left + width;
884 left = getWidth() * 0.5f - width / 2.0f;
885 right = getWidth() - left;
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);
896 final int actualHeight = getActualHeight();
897 if (mAnimationTranslationY > 0.0f) {
898 bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f
899 - translateYTotalAmount;
900 top = bottom * heightFraction;
902 top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f -
903 translateYTotalAmount;
904 bottom = actualHeight * (1 - heightFraction) + top * heightFraction;
906 mAppearAnimationRect.set(left, top, right, bottom);
907 setOutlineRect(left, top + mAppearAnimationTranslation, right,
908 bottom + mAppearAnimationTranslation);
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);
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);
929 contentView.setAlpha(contentAlpha);
933 protected void applyRoundness() {
934 super.applyRoundness();
935 applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
936 getCurrentBackgroundRadiusBottom());
939 protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
940 mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
941 mBackgroundNormal.setRoundness(topRadius, bottomRadius);
945 protected void setBackgroundTop(int backgroundTop) {
946 mBackgroundDimmed.setBackgroundTop(backgroundTop);
947 mBackgroundNormal.setBackgroundTop(backgroundTop);
950 protected abstract View getContentView();
952 public int calculateBgColor() {
953 return calculateBgColor(true /* withTint */, true /* withOverRide */);
957 protected boolean childNeedsClipping(View child) {
958 if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
961 return super.childNeedsClipping(child);
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
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);
974 if (withTint && mBgTint != NO_COLOR) {
981 protected int getRippleColor() {
983 return mTintedRippleColor;
985 return mNormalRippleColor;
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.
994 * @param enable Should it be enabled.
996 private void enableAppearDrawing(boolean enable) {
997 if (enable != mDrawingAppearAnimation) {
998 mDrawingAppearAnimation = enable;
1000 setContentAlpha(1.0f);
1001 mAppearAnimationFraction = -1;
1002 setOutlineRect(null);
1008 public boolean isDrawingAppearAnimation() {
1009 return mDrawingAppearAnimation;
1013 protected void dispatchDraw(Canvas canvas) {
1014 if (mDrawingAppearAnimation) {
1016 canvas.translate(0, mAppearAnimationTranslation);
1018 super.dispatchDraw(canvas);
1019 if (mDrawingAppearAnimation) {
1024 public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
1025 mOnActivatedListener = onActivatedListener;
1028 public boolean hasSameBgColor(ActivatableNotificationView otherView) {
1029 return calculateBgColor() == otherView.calculateBgColor();
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);
1044 public int getBackgroundColorWithoutTint() {
1045 return calculateBgColor(false /* withTint */, false /* withOverride */);
1048 public int getCurrentBackgroundTint() {
1049 return mCurrentBackgroundTint;
1052 public boolean isPinned() {
1056 public boolean isHeadsUpAnimatingAway() {
1060 public interface OnActivatedListener {
1061 void onActivated(ActivatableNotificationView view);
1062 void onActivationReset(ActivatableNotificationView view);