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.ValueAnimator;
22 import android.content.Context;
23 import android.graphics.Canvas;
24 import android.graphics.Point;
25 import android.util.AttributeSet;
26 import android.util.MathUtils;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.accessibility.AccessibilityManager;
30 import android.view.animation.Interpolator;
31 import android.view.animation.PathInterpolator;
32 
33 import com.android.internal.jank.InteractionJankMonitor;
34 import com.android.internal.jank.InteractionJankMonitor.Configuration;
35 import com.android.settingslib.Utils;
36 import com.android.systemui.Gefingerpoken;
37 import com.android.systemui.R;
38 import com.android.systemui.animation.Interpolators;
39 import com.android.systemui.statusbar.NotificationShelf;
40 import com.android.systemui.statusbar.notification.FakeShadowView;
41 import com.android.systemui.statusbar.notification.NotificationUtils;
42 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
43 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
44 
45 /**
46  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
47  * to implement dimming/activating on Keyguard for the double-tap gesture
48  */
49 public abstract class ActivatableNotificationView extends ExpandableOutlineView {
50 
51     /**
52      * The amount of width, which is kept in the end when performing a disappear animation (also
53      * the amount from which the horizontal appearing begins)
54      */
55     private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f;
56 
57     /**
58      * At which point from [0,1] does the horizontal collapse animation end (or start when
59      * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
60      */
61     private static final float HORIZONTAL_ANIMATION_END = 0.2f;
62 
63     /**
64      * At which point from [0,1] does the horizontal collapse animation start (or start when
65      * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
66      */
67     private static final float HORIZONTAL_ANIMATION_START = 1.0f;
68 
69     /**
70      * At which point from [0,1] does the vertical collapse animation start (or end when
71      * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
72      */
73     private static final float VERTICAL_ANIMATION_START = 1.0f;
74 
75     /**
76      * A sentinel value when no color should be used. Can be used with {@link #setTintColor(int)}
77      * or {@link #setOverrideTintColor(int, float)}.
78      */
79     protected static final int NO_COLOR = 0;
80     /**
81      * The content of the view should start showing at animation progress value of
82      * #ALPHA_APPEAR_START_FRACTION.
83      */
84     private static final float ALPHA_APPEAR_START_FRACTION = .4f;
85     /**
86      * The content should show fully with progress at #ALPHA_APPEAR_END_FRACTION
87      * The start of the animation is at #ALPHA_APPEAR_START_FRACTION
88      */
89     private static final float ALPHA_APPEAR_END_FRACTION = 1;
90     private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR
91             = new PathInterpolator(0.6f, 0, 0.5f, 1);
92     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
93             = new PathInterpolator(0, 0, 0.5f, 1);
94     private int mTintedRippleColor;
95     private int mNormalRippleColor;
96     private Gefingerpoken mTouchHandler;
97 
98     int mBgTint = NO_COLOR;
99 
100     /**
101      * Flag to indicate that the notification has been touched once and the second touch will
102      * click it.
103      */
104     private boolean mActivated;
105 
106     private OnActivatedListener mOnActivatedListener;
107 
108     private final Interpolator mSlowOutFastInInterpolator;
109     private final Interpolator mSlowOutLinearInInterpolator;
110     private Interpolator mCurrentAppearInterpolator;
111 
112     NotificationBackgroundView mBackgroundNormal;
113     private float mAnimationTranslationY;
114     private boolean mDrawingAppearAnimation;
115     private ValueAnimator mAppearAnimator;
116     private ValueAnimator mBackgroundColorAnimator;
117     private float mAppearAnimationFraction = -1.0f;
118     private float mAppearAnimationTranslation;
119     private int mNormalColor;
120     private boolean mIsBelowSpeedBump;
121     private long mLastActionUpTime;
122 
123     private float mNormalBackgroundVisibilityAmount;
124     private FakeShadowView mFakeShadow;
125     private int mCurrentBackgroundTint;
126     private int mTargetTint;
127     private int mStartTint;
128     private int mOverrideTint;
129     private float mOverrideAmount;
130     private boolean mShadowHidden;
131     private boolean mIsHeadsUpAnimation;
132     /* In order to track headsup longpress coorindate. */
133     protected Point mTargetPoint;
134     private boolean mDismissed;
135     private boolean mRefocusOnDismiss;
136     private AccessibilityManager mAccessibilityManager;
137 
ActivatableNotificationView(Context context, AttributeSet attrs)138     public ActivatableNotificationView(Context context, AttributeSet attrs) {
139         super(context, attrs);
140         mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
141         mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
142         setClipChildren(false);
143         setClipToPadding(false);
144         updateColors();
145     }
146 
updateColors()147     private void updateColors() {
148         mNormalColor = Utils.getColorAttrDefaultColor(mContext,
149                 com.android.internal.R.attr.colorSurface);
150         mTintedRippleColor = mContext.getColor(
151                 R.color.notification_ripple_tinted_color);
152         mNormalRippleColor = mContext.getColor(
153                 R.color.notification_ripple_untinted_color);
154     }
155 
156     /**
157      * Reload background colors from resources and invalidate views.
158      */
updateBackgroundColors()159     public void updateBackgroundColors() {
160         updateColors();
161         initBackground();
162         updateBackgroundTint();
163     }
164 
165     @Override
onFinishInflate()166     protected void onFinishInflate() {
167         super.onFinishInflate();
168         mBackgroundNormal = findViewById(R.id.backgroundNormal);
169         mFakeShadow = findViewById(R.id.fake_shadow);
170         mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
171         initBackground();
172         updateBackgroundTint();
173         updateOutlineAlpha();
174     }
175 
176     /**
177      * Sets the custom background on {@link #mBackgroundNormal}
178      * This method can also be used to reload the backgrounds on both of those views, which can
179      * be useful in a configuration change.
180      */
initBackground()181     protected void initBackground() {
182         mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
183     }
184 
hideBackground()185     protected boolean hideBackground() {
186         return false;
187     }
188 
updateBackground()189     protected void updateBackground() {
190         mBackgroundNormal.setVisibility(hideBackground() ? INVISIBLE : VISIBLE);
191     }
192 
193 
194     @Override
onInterceptTouchEvent(MotionEvent ev)195     public boolean onInterceptTouchEvent(MotionEvent ev) {
196         if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) {
197             return true;
198         }
199         return super.onInterceptTouchEvent(ev);
200     }
201 
202     /**
203      * Called by the TouchHandler when this view is tapped. This will be called for actual taps
204      * only, i.e. taps that have been filtered by the FalsingManager.
205      */
onTap()206     public void onTap() {}
207 
208     /** Sets the last action up time this view was touched. */
setLastActionUpTime(long eventTime)209     void setLastActionUpTime(long eventTime) {
210         mLastActionUpTime = eventTime;
211     }
212 
213     /**
214      * Returns the last action up time. The last time will also be cleared because the source of
215      * action is not only from touch event. That prevents the caller from utilizing the time with
216      * unrelated event. The time can be 0 if the event is unavailable.
217      */
getAndResetLastActionUpTime()218     public long getAndResetLastActionUpTime() {
219         long lastActionUpTime = mLastActionUpTime;
220         mLastActionUpTime = 0;
221         return lastActionUpTime;
222     }
223 
disallowSingleClick(MotionEvent ev)224     protected boolean disallowSingleClick(MotionEvent ev) {
225         return false;
226     }
227 
handleSlideBack()228     protected boolean handleSlideBack() {
229         return false;
230     }
231 
232     /**
233      * @return whether this view is interactive and can be double tapped
234      */
isInteractive()235     protected boolean isInteractive() {
236         return true;
237     }
238 
239     @Override
drawableStateChanged()240     protected void drawableStateChanged() {
241         super.drawableStateChanged();
242         mBackgroundNormal.setState(getDrawableState());
243     }
244 
setRippleAllowed(boolean allowed)245     void setRippleAllowed(boolean allowed) {
246         mBackgroundNormal.setPressedAllowed(allowed);
247     }
248 
makeActive()249     void makeActive() {
250         mActivated = true;
251         if (mOnActivatedListener != null) {
252             mOnActivatedListener.onActivated(this);
253         }
254     }
255 
isActive()256     public boolean isActive() {
257         return mActivated;
258     }
259 
260     /**
261      * Cancels the hotspot and makes the notification inactive.
262      */
makeInactive(boolean animate)263     public void makeInactive(boolean animate) {
264         if (mActivated) {
265             mActivated = false;
266         }
267         if (mOnActivatedListener != null) {
268             mOnActivatedListener.onActivationReset(this);
269         }
270     }
271 
updateOutlineAlpha()272     private void updateOutlineAlpha() {
273         float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED;
274         alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount);
275         setOutlineAlpha(alpha);
276     }
277 
setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount)278     private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
279         mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount;
280         updateOutlineAlpha();
281     }
282 
283     @Override
setBelowSpeedBump(boolean below)284     public void setBelowSpeedBump(boolean below) {
285         super.setBelowSpeedBump(below);
286         if (below != mIsBelowSpeedBump) {
287             mIsBelowSpeedBump = below;
288             updateBackgroundTint();
289             onBelowSpeedBumpChanged();
290         }
291     }
292 
onBelowSpeedBumpChanged()293     protected void onBelowSpeedBumpChanged() {
294     }
295 
296     /**
297      * @return whether we are below the speed bump
298      */
isBelowSpeedBump()299     public boolean isBelowSpeedBump() {
300         return mIsBelowSpeedBump;
301     }
302 
303     /**
304      * Sets the tint color of the background
305      */
setTintColor(int color)306     protected void setTintColor(int color) {
307         setTintColor(color, false);
308     }
309 
310     /**
311      * Sets the tint color of the background
312      */
setTintColor(int color, boolean animated)313     void setTintColor(int color, boolean animated) {
314         if (color != mBgTint) {
315             mBgTint = color;
316             updateBackgroundTint(animated);
317         }
318     }
319 
320     /**
321      * Set an override tint color that is used for the background.
322      *
323      * @param color the color that should be used to tint the background.
324      *              This can be {@link #NO_COLOR} if the tint should be normally computed.
325      * @param overrideAmount a value from 0 to 1 how much the override tint should be used. The
326      *                       background color will then be the interpolation between this and the
327      *                       regular background color, where 1 means the overrideTintColor is fully
328      *                       used and the background color not at all.
329      */
setOverrideTintColor(int color, float overrideAmount)330     public void setOverrideTintColor(int color, float overrideAmount) {
331         mOverrideTint = color;
332         mOverrideAmount = overrideAmount;
333         int newColor = calculateBgColor();
334         setBackgroundTintColor(newColor);
335     }
336 
updateBackgroundTint()337     protected void updateBackgroundTint() {
338         updateBackgroundTint(false /* animated */);
339     }
340 
updateBackgroundTint(boolean animated)341     private void updateBackgroundTint(boolean animated) {
342         if (mBackgroundColorAnimator != null) {
343             mBackgroundColorAnimator.cancel();
344         }
345         int rippleColor = getRippleColor();
346         mBackgroundNormal.setRippleColor(rippleColor);
347         int color = calculateBgColor();
348         if (!animated) {
349             setBackgroundTintColor(color);
350         } else if (color != mCurrentBackgroundTint) {
351             mStartTint = mCurrentBackgroundTint;
352             mTargetTint = color;
353             mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
354             mBackgroundColorAnimator.addUpdateListener(animation -> {
355                 int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
356                         animation.getAnimatedFraction());
357                 setBackgroundTintColor(newColor);
358             });
359             mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
360             mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
361             mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
362                 @Override
363                 public void onAnimationEnd(Animator animation) {
364                     mBackgroundColorAnimator = null;
365                 }
366             });
367             mBackgroundColorAnimator.start();
368         }
369     }
370 
setBackgroundTintColor(int color)371     protected void setBackgroundTintColor(int color) {
372         if (color != mCurrentBackgroundTint) {
373             mCurrentBackgroundTint = color;
374             if (color == mNormalColor) {
375                 // We don't need to tint a normal notification
376                 color = 0;
377             }
378             mBackgroundNormal.setTint(color);
379         }
380     }
381 
updateBackgroundClipping()382     protected void updateBackgroundClipping() {
383         mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
384     }
385 
386     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)387     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
388         super.onLayout(changed, left, top, right, bottom);
389         setPivotX(getWidth() / 2);
390     }
391 
392     @Override
setActualHeight(int actualHeight, boolean notifyListeners)393     public void setActualHeight(int actualHeight, boolean notifyListeners) {
394         super.setActualHeight(actualHeight, notifyListeners);
395         setPivotY(actualHeight / 2);
396         mBackgroundNormal.setActualHeight(actualHeight);
397     }
398 
399     @Override
setClipTopAmount(int clipTopAmount)400     public void setClipTopAmount(int clipTopAmount) {
401         super.setClipTopAmount(clipTopAmount);
402         mBackgroundNormal.setClipTopAmount(clipTopAmount);
403     }
404 
405     @Override
setClipBottomAmount(int clipBottomAmount)406     public void setClipBottomAmount(int clipBottomAmount) {
407         super.setClipBottomAmount(clipBottomAmount);
408         mBackgroundNormal.setClipBottomAmount(clipBottomAmount);
409     }
410 
411     @Override
performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)412     public long performRemoveAnimation(long duration, long delay,
413             float translationDirection, boolean isHeadsUpAnimation, float endLocation,
414             Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) {
415         enableAppearDrawing(true);
416         mIsHeadsUpAnimation = isHeadsUpAnimation;
417         if (mDrawingAppearAnimation) {
418             startAppearAnimation(false /* isAppearing */, translationDirection,
419                     delay, duration, onFinishedRunnable, animationListener);
420         } else if (onFinishedRunnable != null) {
421             onFinishedRunnable.run();
422         }
423         return 0;
424     }
425 
426     @Override
performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)427     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
428         enableAppearDrawing(true);
429         mIsHeadsUpAnimation = isHeadsUpAppear;
430         if (mDrawingAppearAnimation) {
431             startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay,
432                     duration, null, null);
433         }
434     }
435 
startAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, final Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)436     private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
437             long duration, final Runnable onFinishedRunnable,
438             AnimatorListenerAdapter animationListener) {
439         mAnimationTranslationY = translationDirection * getActualHeight();
440         cancelAppearAnimation();
441         if (mAppearAnimationFraction == -1.0f) {
442             // not initialized yet, we start anew
443             if (isAppearing) {
444                 mAppearAnimationFraction = 0.0f;
445                 mAppearAnimationTranslation = mAnimationTranslationY;
446             } else {
447                 mAppearAnimationFraction = 1.0f;
448                 mAppearAnimationTranslation = 0;
449             }
450         }
451 
452         float targetValue;
453         if (isAppearing) {
454             mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
455             targetValue = 1.0f;
456         } else {
457             mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
458             targetValue = 0.0f;
459         }
460         mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
461                 targetValue);
462         mAppearAnimator.setInterpolator(Interpolators.LINEAR);
463         mAppearAnimator.setDuration(
464                 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
465         mAppearAnimator.addUpdateListener(animation -> {
466             mAppearAnimationFraction = (float) animation.getAnimatedValue();
467             updateAppearAnimationAlpha();
468             updateAppearRect();
469             invalidate();
470         });
471         if (animationListener != null) {
472             mAppearAnimator.addListener(animationListener);
473         }
474         if (delay > 0) {
475             // we need to apply the initial state already to avoid drawn frames in the wrong state
476             updateAppearAnimationAlpha();
477             updateAppearRect();
478             mAppearAnimator.setStartDelay(delay);
479         }
480         mAppearAnimator.addListener(new AnimatorListenerAdapter() {
481             private boolean mWasCancelled;
482 
483             @Override
484             public void onAnimationEnd(Animator animation) {
485                 if (onFinishedRunnable != null) {
486                     onFinishedRunnable.run();
487                 }
488                 if (!mWasCancelled) {
489                     enableAppearDrawing(false);
490                     onAppearAnimationFinished(isAppearing);
491                     InteractionJankMonitor.getInstance().end(getCujType(isAppearing));
492                 } else {
493                     InteractionJankMonitor.getInstance().cancel(getCujType(isAppearing));
494                 }
495             }
496 
497             @Override
498             public void onAnimationStart(Animator animation) {
499                 mWasCancelled = false;
500                 Configuration.Builder builder = Configuration.Builder
501                         .withView(getCujType(isAppearing), ActivatableNotificationView.this);
502                 InteractionJankMonitor.getInstance().begin(builder);
503             }
504 
505             @Override
506             public void onAnimationCancel(Animator animation) {
507                 mWasCancelled = true;
508             }
509         });
510         mAppearAnimator.start();
511     }
512 
getCujType(boolean isAppearing)513     private int getCujType(boolean isAppearing) {
514         if (mIsHeadsUpAnimation) {
515             return isAppearing
516                     ? InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR
517                     : InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
518         } else {
519             return isAppearing
520                     ? InteractionJankMonitor.CUJ_NOTIFICATION_ADD
521                     : InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE;
522         }
523     }
524 
onAppearAnimationFinished(boolean wasAppearing)525     protected void onAppearAnimationFinished(boolean wasAppearing) {
526     }
527 
cancelAppearAnimation()528     private void cancelAppearAnimation() {
529         if (mAppearAnimator != null) {
530             mAppearAnimator.cancel();
531             mAppearAnimator = null;
532         }
533     }
534 
cancelAppearDrawing()535     public void cancelAppearDrawing() {
536         cancelAppearAnimation();
537         enableAppearDrawing(false);
538     }
539 
updateAppearRect()540     private void updateAppearRect() {
541         float interpolatedFraction = mCurrentAppearInterpolator.getInterpolation(
542                 mAppearAnimationFraction);
543         mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY;
544         final int actualHeight = getActualHeight();
545         float bottom = actualHeight * interpolatedFraction;
546 
547         if (mTargetPoint != null) {
548             int width = getWidth();
549             float fraction = 1 - mAppearAnimationFraction;
550 
551             setOutlineRect(mTargetPoint.x * fraction,
552                     mAnimationTranslationY
553                             + (mAnimationTranslationY - mTargetPoint.y) * fraction,
554                     width - (width - mTargetPoint.x) * fraction,
555                     actualHeight - (actualHeight - mTargetPoint.y) * fraction);
556         } else {
557             setOutlineRect(0, mAppearAnimationTranslation, getWidth(),
558                     bottom + mAppearAnimationTranslation);
559         }
560     }
561 
getInterpolatedAppearAnimationFraction()562     private float getInterpolatedAppearAnimationFraction() {
563         if (mAppearAnimationFraction >= 0) {
564             return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction);
565         }
566         return 1.0f;
567     }
568 
updateAppearAnimationAlpha()569     private void updateAppearAnimationAlpha() {
570         float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction,
571                 ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION);
572         float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION;
573         float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range;
574         setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha));
575     }
576 
setContentAlpha(float contentAlpha)577     private void setContentAlpha(float contentAlpha) {
578         View contentView = getContentView();
579         if (contentView.hasOverlappingRendering()) {
580             int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE
581                     : LAYER_TYPE_HARDWARE;
582             contentView.setLayerType(layerType, null);
583         }
584         contentView.setAlpha(contentAlpha);
585         // After updating the current view, reset all views.
586         if (contentAlpha == 1f) {
587             resetAllContentAlphas();
588         }
589     }
590 
591     /**
592      * If a subclass's {@link #getContentView()} returns different views depending on state,
593      * this method is an opportunity to reset the alpha of ALL content views, not just the
594      * current one, which may prevent a content view that is temporarily hidden from being reset.
595      *
596      * This should setAlpha(1.0f) and setLayerType(LAYER_TYPE_NONE) for all content views.
597      */
resetAllContentAlphas()598     protected void resetAllContentAlphas() {}
599 
600     @Override
applyRoundness()601     protected void applyRoundness() {
602         super.applyRoundness();
603         applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
604                 getCurrentBackgroundRadiusBottom());
605     }
606 
607     @Override
getCurrentBackgroundRadiusTop()608     public float getCurrentBackgroundRadiusTop() {
609         float fraction = getInterpolatedAppearAnimationFraction();
610         return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction);
611     }
612 
613     @Override
getCurrentBackgroundRadiusBottom()614     public float getCurrentBackgroundRadiusBottom() {
615         float fraction = getInterpolatedAppearAnimationFraction();
616         return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction);
617     }
618 
applyBackgroundRoundness(float topRadius, float bottomRadius)619     private void applyBackgroundRoundness(float topRadius, float bottomRadius) {
620         mBackgroundNormal.setRadius(topRadius, bottomRadius);
621     }
622 
623     @Override
setBackgroundTop(int backgroundTop)624     protected void setBackgroundTop(int backgroundTop) {
625         mBackgroundNormal.setBackgroundTop(backgroundTop);
626     }
627 
getContentView()628     protected abstract View getContentView();
629 
calculateBgColor()630     public int calculateBgColor() {
631         return calculateBgColor(true /* withTint */, true /* withOverRide */);
632     }
633 
634     @Override
childNeedsClipping(View child)635     protected boolean childNeedsClipping(View child) {
636         if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
637             return true;
638         }
639         return super.childNeedsClipping(child);
640     }
641 
642     /**
643      * @param withTint should a possible tint be factored in?
644      * @param withOverride should the value be interpolated with {@link #mOverrideTint}
645      * @return the calculated background color
646      */
calculateBgColor(boolean withTint, boolean withOverride)647     private int calculateBgColor(boolean withTint, boolean withOverride) {
648         if (withOverride && mOverrideTint != NO_COLOR) {
649             int defaultTint = calculateBgColor(withTint, false);
650             return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount);
651         }
652         if (withTint && mBgTint != NO_COLOR) {
653             return mBgTint;
654         } else {
655             return mNormalColor;
656         }
657     }
658 
getRippleColor()659     private int getRippleColor() {
660         if (mBgTint != 0) {
661             return mTintedRippleColor;
662         } else {
663             return mNormalRippleColor;
664         }
665     }
666 
667     /**
668      * When we draw the appear animation, we render the view in a bitmap and render this bitmap
669      * as a shader of a rect. This call creates the Bitmap and switches the drawing mode,
670      * such that the normal drawing of the views does not happen anymore.
671      *
672      * @param enable Should it be enabled.
673      */
enableAppearDrawing(boolean enable)674     private void enableAppearDrawing(boolean enable) {
675         if (enable != mDrawingAppearAnimation) {
676             mDrawingAppearAnimation = enable;
677             if (!enable) {
678                 setContentAlpha(1.0f);
679                 mAppearAnimationFraction = -1;
680                 setOutlineRect(null);
681             }
682             invalidate();
683         }
684     }
685 
isDrawingAppearAnimation()686     public boolean isDrawingAppearAnimation() {
687         return mDrawingAppearAnimation;
688     }
689 
690     @Override
dispatchDraw(Canvas canvas)691     protected void dispatchDraw(Canvas canvas) {
692         if (mDrawingAppearAnimation) {
693             canvas.save();
694             canvas.translate(0, mAppearAnimationTranslation);
695         }
696         super.dispatchDraw(canvas);
697         if (mDrawingAppearAnimation) {
698             canvas.restore();
699         }
700     }
701 
setOnActivatedListener(OnActivatedListener onActivatedListener)702     public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
703         mOnActivatedListener = onActivatedListener;
704     }
705 
706     @Override
setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation)707     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
708             int outlineTranslation) {
709         boolean hiddenBefore = mShadowHidden;
710         mShadowHidden = shadowIntensity == 0.0f;
711         if (!mShadowHidden || !hiddenBefore) {
712             mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ()
713                             + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd,
714                     outlineTranslation);
715         }
716     }
717 
getBackgroundColorWithoutTint()718     public int getBackgroundColorWithoutTint() {
719         return calculateBgColor(false /* withTint */, false /* withOverride */);
720     }
721 
getCurrentBackgroundTint()722     public int getCurrentBackgroundTint() {
723         return mCurrentBackgroundTint;
724     }
725 
isHeadsUp()726     public boolean isHeadsUp() {
727         return false;
728     }
729 
730     @Override
getHeadsUpHeightWithoutHeader()731     public int getHeadsUpHeightWithoutHeader() {
732         return getHeight();
733     }
734 
735     /** Mark that this view has been dismissed. */
dismiss(boolean refocusOnDismiss)736     public void dismiss(boolean refocusOnDismiss) {
737         mDismissed = true;
738         mRefocusOnDismiss = refocusOnDismiss;
739     }
740 
741     /** Mark that this view is no longer dismissed. */
unDismiss()742     public void unDismiss() {
743         mDismissed = false;
744     }
745 
746     /** Is this view marked as dismissed? */
isDismissed()747     public boolean isDismissed() {
748         return mDismissed;
749     }
750 
751     /** Should a re-focus occur upon dismissing this view? */
shouldRefocusOnDismiss()752     public boolean shouldRefocusOnDismiss() {
753         return mRefocusOnDismiss || isAccessibilityFocused();
754     }
755 
setTouchHandler(Gefingerpoken touchHandler)756     void setTouchHandler(Gefingerpoken touchHandler) {
757         mTouchHandler = touchHandler;
758     }
759 
setAccessibilityManager(AccessibilityManager accessibilityManager)760     public void setAccessibilityManager(AccessibilityManager accessibilityManager) {
761         mAccessibilityManager = accessibilityManager;
762     }
763 
764     public interface OnActivatedListener {
onActivated(ActivatableNotificationView view)765         void onActivated(ActivatableNotificationView view);
onActivationReset(ActivatableNotificationView view)766         void onActivationReset(ActivatableNotificationView view);
767     }
768 }
769