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