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