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