ActivatableNotificationView.java revision 06c19ea00190e69496be048758101f84f3bc43ad
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
17package com.android.systemui.statusbar;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.graphics.Bitmap;
25import android.graphics.BitmapShader;
26import android.graphics.Canvas;
27import android.graphics.Color;
28import android.graphics.ColorMatrix;
29import android.graphics.ColorMatrixColorFilter;
30import android.graphics.Paint;
31import android.graphics.PorterDuff;
32import android.graphics.PorterDuffColorFilter;
33import android.graphics.RectF;
34import android.graphics.Shader;
35import android.util.AttributeSet;
36import android.view.MotionEvent;
37import android.view.View;
38import android.view.ViewAnimationUtils;
39import android.view.ViewConfiguration;
40import android.view.animation.AnimationUtils;
41import android.view.animation.Interpolator;
42import android.view.animation.LinearInterpolator;
43import android.view.animation.PathInterpolator;
44
45import com.android.systemui.R;
46
47/**
48 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
49 * to implement dimming/activating on Keyguard for the double-tap gesture
50 */
51public abstract class ActivatableNotificationView extends ExpandableOutlineView {
52
53    private static final long DOUBLETAP_TIMEOUT_MS = 1200;
54    private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
55    private static final int ACTIVATE_ANIMATION_LENGTH = 220;
56
57    /**
58     * The amount of width, which is kept in the end when performing a disappear animation (also
59     * the amount from which the horizontal appearing begins)
60     */
61    private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f;
62
63    /**
64     * At which point from [0,1] does the horizontal collapse animation end (or start when
65     * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
66     */
67    private static final float HORIZONTAL_ANIMATION_END = 0.2f;
68
69    /**
70     * At which point from [0,1] does the alpha animation end (or start when
71     * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
72     */
73    private static final float ALPHA_ANIMATION_END = 0.0f;
74
75    /**
76     * At which point from [0,1] does the horizontal collapse animation start (or start when
77     * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
78     */
79    private static final float HORIZONTAL_ANIMATION_START = 1.0f;
80
81    /**
82     * At which point from [0,1] does the vertical collapse animation start (or end when
83     * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
84     */
85    private static final float VERTICAL_ANIMATION_START = 1.0f;
86
87    private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR
88            = new PathInterpolator(0.6f, 0, 0.5f, 1);
89    private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
90            = new PathInterpolator(0, 0, 0.5f, 1);
91    private final int mTintedRippleColor;
92    private final int mLowPriorityRippleColor;
93    private final int mNormalRippleColor;
94
95    private boolean mDimmed;
96    private boolean mDark;
97    private final Paint mDarkPaint = createDarkPaint();
98
99    private int mBgTint = 0;
100    private final int mRoundedRectCornerRadius;
101
102    /**
103     * Flag to indicate that the notification has been touched once and the second touch will
104     * click it.
105     */
106    private boolean mActivated;
107
108    private float mDownX;
109    private float mDownY;
110    private final float mTouchSlop;
111
112    private OnActivatedListener mOnActivatedListener;
113
114    private final Interpolator mLinearOutSlowInInterpolator;
115    private final Interpolator mFastOutSlowInInterpolator;
116    private final Interpolator mSlowOutFastInInterpolator;
117    private final Interpolator mSlowOutLinearInInterpolator;
118    private final Interpolator mLinearInterpolator;
119    private Interpolator mCurrentAppearInterpolator;
120    private Interpolator mCurrentAlphaInterpolator;
121
122    private NotificationBackgroundView mBackgroundNormal;
123    private NotificationBackgroundView mBackgroundDimmed;
124    private ObjectAnimator mBackgroundAnimator;
125    private RectF mAppearAnimationRect = new RectF();
126    private PorterDuffColorFilter mAppearAnimationFilter;
127    private float mAnimationTranslationY;
128    private boolean mDrawingAppearAnimation;
129    private Paint mAppearPaint = new Paint();
130    private ValueAnimator mAppearAnimator;
131    private float mAppearAnimationFraction = -1.0f;
132    private float mAppearAnimationTranslation;
133    private boolean mShowingLegacyBackground;
134    private final int mLegacyColor;
135    private final int mNormalColor;
136    private final int mLowPriorityColor;
137    private boolean mIsBelowSpeedBump;
138
139    public ActivatableNotificationView(Context context, AttributeSet attrs) {
140        super(context, attrs);
141        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
142        mFastOutSlowInInterpolator =
143                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
144        mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
145        mLinearOutSlowInInterpolator =
146                AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
147        mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
148        mLinearInterpolator = new LinearInterpolator();
149        setClipChildren(false);
150        setClipToPadding(false);
151        mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
152        mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
153                R.dimen.notification_material_rounded_rect_radius);
154        mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color);
155        mNormalColor = getResources().getColor(R.color.notification_material_background_color);
156        mLowPriorityColor = getResources().getColor(
157                R.color.notification_material_background_low_priority_color);
158        mTintedRippleColor = context.getResources().getColor(
159                R.color.notification_ripple_tinted_color);
160        mLowPriorityRippleColor = context.getResources().getColor(
161                R.color.notification_ripple_color_low_priority);
162        mNormalRippleColor = context.getResources().getColor(
163                R.color.notification_ripple_untinted_color);
164    }
165
166    @Override
167    protected void onFinishInflate() {
168        super.onFinishInflate();
169        mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal);
170        mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
171        mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
172        mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
173        updateBackground();
174        updateBackgroundTint();
175    }
176
177    private final Runnable mTapTimeoutRunnable = new Runnable() {
178        @Override
179        public void run() {
180            makeInactive(true /* animate */);
181        }
182    };
183
184    @Override
185    public boolean onTouchEvent(MotionEvent event) {
186        if (mDimmed) {
187            return handleTouchEventDimmed(event);
188        } else {
189            return super.onTouchEvent(event);
190        }
191    }
192
193    @Override
194    public void drawableHotspotChanged(float x, float y) {
195        if (!mDimmed){
196            mBackgroundNormal.drawableHotspotChanged(x, y);
197        }
198    }
199
200    @Override
201    protected void drawableStateChanged() {
202        super.drawableStateChanged();
203        if (mDimmed) {
204            mBackgroundDimmed.setState(getDrawableState());
205        } else {
206            mBackgroundNormal.setState(getDrawableState());
207        }
208    }
209
210    private boolean handleTouchEventDimmed(MotionEvent event) {
211        int action = event.getActionMasked();
212        switch (action) {
213            case MotionEvent.ACTION_DOWN:
214                mDownX = event.getX();
215                mDownY = event.getY();
216                if (mDownY > getActualHeight()) {
217                    return false;
218                }
219                break;
220            case MotionEvent.ACTION_MOVE:
221                if (!isWithinTouchSlop(event)) {
222                    makeInactive(true /* animate */);
223                    return false;
224                }
225                break;
226            case MotionEvent.ACTION_UP:
227                if (isWithinTouchSlop(event)) {
228                    if (!mActivated) {
229                        makeActive();
230                        postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
231                    } else {
232                        boolean performed = performClick();
233                        if (performed) {
234                            removeCallbacks(mTapTimeoutRunnable);
235                        }
236                    }
237                } else {
238                    makeInactive(true /* animate */);
239                }
240                break;
241            case MotionEvent.ACTION_CANCEL:
242                makeInactive(true /* animate */);
243                break;
244            default:
245                break;
246        }
247        return true;
248    }
249
250    private void makeActive() {
251        startActivateAnimation(false /* reverse */);
252        mActivated = true;
253        if (mOnActivatedListener != null) {
254            mOnActivatedListener.onActivated(this);
255        }
256    }
257
258    private void startActivateAnimation(boolean reverse) {
259        int widthHalf = mBackgroundNormal.getWidth()/2;
260        int heightHalf = mBackgroundNormal.getActualHeight()/2;
261        float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf);
262        Animator animator;
263        if (reverse) {
264            animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
265                    widthHalf, heightHalf, radius, 0);
266        } else {
267            animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
268                    widthHalf, heightHalf, 0, radius);
269        }
270        mBackgroundNormal.setVisibility(View.VISIBLE);
271        Interpolator interpolator;
272        Interpolator alphaInterpolator;
273        if (!reverse) {
274            interpolator = mLinearOutSlowInInterpolator;
275            alphaInterpolator = mLinearOutSlowInInterpolator;
276        } else {
277            interpolator = ACTIVATE_INVERSE_INTERPOLATOR;
278            alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR;
279        }
280        animator.setInterpolator(interpolator);
281        animator.setDuration(ACTIVATE_ANIMATION_LENGTH);
282        if (reverse) {
283            mBackgroundNormal.setAlpha(1f);
284            animator.addListener(new AnimatorListenerAdapter() {
285                @Override
286                public void onAnimationEnd(Animator animation) {
287                    if (mDimmed) {
288                        mBackgroundNormal.setVisibility(View.INVISIBLE);
289                    }
290                }
291            });
292            animator.start();
293        } else {
294            mBackgroundNormal.setAlpha(0.4f);
295            animator.start();
296        }
297        mBackgroundNormal.animate()
298                .alpha(reverse ? 0f : 1f)
299                .setInterpolator(alphaInterpolator)
300                .setDuration(ACTIVATE_ANIMATION_LENGTH);
301    }
302
303    /**
304     * Cancels the hotspot and makes the notification inactive.
305     */
306    public void makeInactive(boolean animate) {
307        if (mActivated) {
308            if (mDimmed) {
309                if (animate) {
310                    startActivateAnimation(true /* reverse */);
311                } else {
312                    mBackgroundNormal.setVisibility(View.INVISIBLE);
313                }
314            }
315            mActivated = false;
316        }
317        if (mOnActivatedListener != null) {
318            mOnActivatedListener.onActivationReset(this);
319        }
320        removeCallbacks(mTapTimeoutRunnable);
321    }
322
323    private boolean isWithinTouchSlop(MotionEvent event) {
324        return Math.abs(event.getX() - mDownX) < mTouchSlop
325                && Math.abs(event.getY() - mDownY) < mTouchSlop;
326    }
327
328    public void setDimmed(boolean dimmed, boolean fade) {
329        if (mDimmed != dimmed) {
330            mDimmed = dimmed;
331            if (fade) {
332                fadeBackground();
333            } else {
334                updateBackground();
335            }
336        }
337    }
338
339    public void setDark(boolean dark, boolean fade) {
340        // TODO implement fade
341        if (mDark != dark) {
342            mDark = dark;
343            if (mDark) {
344                setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
345            } else {
346                setLayerType(View.LAYER_TYPE_NONE, null);
347            }
348        }
349    }
350
351    private static Paint createDarkPaint() {
352        final Paint p = new Paint();
353        final float[] invert = {
354            -1f,  0f,  0f, 1f, 1f,
355             0f, -1f,  0f, 1f, 1f,
356             0f,  0f, -1f, 1f, 1f,
357             0f,  0f,  0f, 1f, 0f
358        };
359        final ColorMatrix m = new ColorMatrix(invert);
360        final ColorMatrix grayscale = new ColorMatrix();
361        grayscale.setSaturation(0);
362        m.preConcat(grayscale);
363        p.setColorFilter(new ColorMatrixColorFilter(m));
364        return p;
365    }
366
367    public void setShowingLegacyBackground(boolean showing) {
368        mShowingLegacyBackground = showing;
369        updateBackgroundTint();
370    }
371
372    @Override
373    public void setBelowSpeedBump(boolean below) {
374        super.setBelowSpeedBump(below);
375        if (below != mIsBelowSpeedBump) {
376            mIsBelowSpeedBump = below;
377            updateBackgroundTint();
378        }
379    }
380
381    /**
382     * Sets the tint color of the background
383     */
384    public void setTintColor(int color) {
385        mBgTint = color;
386        updateBackgroundTint();
387    }
388
389    private void updateBackgroundTint() {
390        int color = getBackgroundColor();
391        int rippleColor = getRippleColor();
392        if (color == mNormalColor) {
393            // We don't need to tint a normal notification
394            color = 0;
395        }
396        mBackgroundDimmed.setTint(color);
397        mBackgroundNormal.setTint(color);
398        mBackgroundDimmed.setRippleColor(rippleColor);
399        mBackgroundNormal.setRippleColor(rippleColor);
400    }
401
402    private void fadeBackground() {
403        if (mDimmed) {
404            mBackgroundDimmed.setVisibility(View.VISIBLE);
405        } else {
406            mBackgroundNormal.setVisibility(View.VISIBLE);
407        }
408        float startAlpha = mDimmed ? 1f : 0;
409        float endAlpha = mDimmed ? 0 : 1f;
410        int duration = BACKGROUND_ANIMATION_LENGTH_MS;
411        // Check whether there is already a background animation running.
412        if (mBackgroundAnimator != null) {
413            startAlpha = (Float) mBackgroundAnimator.getAnimatedValue();
414            duration = (int) mBackgroundAnimator.getCurrentPlayTime();
415            mBackgroundAnimator.removeAllListeners();
416            mBackgroundAnimator.cancel();
417            if (duration <= 0) {
418                updateBackground();
419                return;
420            }
421        }
422        mBackgroundNormal.setAlpha(startAlpha);
423        mBackgroundAnimator =
424                ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha);
425        mBackgroundAnimator.setInterpolator(mFastOutSlowInInterpolator);
426        mBackgroundAnimator.setDuration(duration);
427        mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
428            @Override
429            public void onAnimationEnd(Animator animation) {
430                if (mDimmed) {
431                    mBackgroundNormal.setVisibility(View.INVISIBLE);
432                } else {
433                    mBackgroundDimmed.setVisibility(View.INVISIBLE);
434                }
435                mBackgroundAnimator = null;
436            }
437        });
438        mBackgroundAnimator.start();
439    }
440
441    private void updateBackground() {
442        if (mDimmed) {
443            mBackgroundDimmed.setVisibility(View.VISIBLE);
444            mBackgroundNormal.setVisibility(View.INVISIBLE);
445        } else {
446            mBackgroundDimmed.setVisibility(View.INVISIBLE);
447            mBackgroundNormal.setVisibility(View.VISIBLE);
448            mBackgroundNormal.setAlpha(1f);
449            removeCallbacks(mTapTimeoutRunnable);
450        }
451    }
452
453    @Override
454    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
455        super.onLayout(changed, left, top, right, bottom);
456        setPivotX(getWidth() / 2);
457    }
458
459    @Override
460    public void setActualHeight(int actualHeight, boolean notifyListeners) {
461        super.setActualHeight(actualHeight, notifyListeners);
462        setPivotY(actualHeight / 2);
463        mBackgroundNormal.setActualHeight(actualHeight);
464        mBackgroundDimmed.setActualHeight(actualHeight);
465    }
466
467    @Override
468    public void setClipTopAmount(int clipTopAmount) {
469        super.setClipTopAmount(clipTopAmount);
470        mBackgroundNormal.setClipTopAmount(clipTopAmount);
471        mBackgroundDimmed.setClipTopAmount(clipTopAmount);
472    }
473
474    @Override
475    public void performRemoveAnimation(long duration, float translationDirection,
476            Runnable onFinishedRunnable) {
477        enableAppearDrawing(true);
478        if (mDrawingAppearAnimation) {
479            startAppearAnimation(false /* isAppearing */, translationDirection,
480                    0, duration, onFinishedRunnable);
481        } else if (onFinishedRunnable != null) {
482            onFinishedRunnable.run();
483        }
484    }
485
486    @Override
487    public void performAddAnimation(long delay, long duration) {
488        enableAppearDrawing(true);
489        if (mDrawingAppearAnimation) {
490            startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null);
491        }
492    }
493
494    private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
495            long duration, final Runnable onFinishedRunnable) {
496        if (mAppearAnimator != null) {
497            mAppearAnimator.cancel();
498        }
499        mAnimationTranslationY = translationDirection * mActualHeight;
500        if (mAppearAnimationFraction == -1.0f) {
501            // not initialized yet, we start anew
502            if (isAppearing) {
503                mAppearAnimationFraction = 0.0f;
504                mAppearAnimationTranslation = mAnimationTranslationY;
505            } else {
506                mAppearAnimationFraction = 1.0f;
507                mAppearAnimationTranslation = 0;
508            }
509        }
510
511        float targetValue;
512        if (isAppearing) {
513            mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
514            mCurrentAlphaInterpolator = mLinearOutSlowInInterpolator;
515            targetValue = 1.0f;
516        } else {
517            mCurrentAppearInterpolator = mFastOutSlowInInterpolator;
518            mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator;
519            targetValue = 0.0f;
520        }
521        mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
522                targetValue);
523        mAppearAnimator.setInterpolator(mLinearInterpolator);
524        mAppearAnimator.setDuration(
525                (long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
526        mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
527            @Override
528            public void onAnimationUpdate(ValueAnimator animation) {
529                mAppearAnimationFraction = (float) animation.getAnimatedValue();
530                updateAppearAnimationAlpha();
531                updateAppearRect();
532                invalidate();
533            }
534        });
535        if (delay > 0) {
536            // we need to apply the initial state already to avoid drawn frames in the wrong state
537            updateAppearAnimationAlpha();
538            updateAppearRect();
539            mAppearAnimator.setStartDelay(delay);
540        }
541        mAppearAnimator.addListener(new AnimatorListenerAdapter() {
542            private boolean mWasCancelled;
543
544            @Override
545            public void onAnimationEnd(Animator animation) {
546                if (onFinishedRunnable != null) {
547                    onFinishedRunnable.run();
548                }
549                if (!mWasCancelled) {
550                    mAppearAnimationFraction = -1;
551                    setOutlineRect(null);
552                    enableAppearDrawing(false);
553                }
554            }
555
556            @Override
557            public void onAnimationStart(Animator animation) {
558                mWasCancelled = false;
559            }
560
561            @Override
562            public void onAnimationCancel(Animator animation) {
563                mWasCancelled = true;
564            }
565        });
566        mAppearAnimator.start();
567    }
568
569    private void updateAppearRect() {
570        float inverseFraction = (1.0f - mAppearAnimationFraction);
571        float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
572        float translateYTotalAmount = translationFraction * mAnimationTranslationY;
573        mAppearAnimationTranslation = translateYTotalAmount;
574
575        // handle width animation
576        float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START))
577                / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END);
578        widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction));
579        widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction);
580        float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) *
581                widthFraction);
582        float right = getWidth() - left;
583
584        // handle top animation
585        float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) /
586                VERTICAL_ANIMATION_START;
587        heightFraction = Math.max(0.0f, heightFraction);
588        heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction);
589
590        float top;
591        float bottom;
592        if (mAnimationTranslationY > 0.0f) {
593            bottom = mActualHeight - heightFraction * mAnimationTranslationY * 0.1f
594                    - translateYTotalAmount;
595            top = bottom * heightFraction;
596        } else {
597            top = heightFraction * (mActualHeight + mAnimationTranslationY) * 0.1f -
598                    translateYTotalAmount;
599            bottom = mActualHeight * (1 - heightFraction) + top * heightFraction;
600        }
601        mAppearAnimationRect.set(left, top, right, bottom);
602        setOutlineRect(left, top + mAppearAnimationTranslation, right,
603                bottom + mAppearAnimationTranslation);
604    }
605
606    private void updateAppearAnimationAlpha() {
607        int backgroundColor = getBackgroundColor();
608        if (backgroundColor != -1) {
609            float contentAlphaProgress = mAppearAnimationFraction;
610            contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
611            contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
612            contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
613            int sourceColor = Color.argb((int) (255 * (1.0f - contentAlphaProgress)),
614                    Color.red(backgroundColor), Color.green(backgroundColor),
615                    Color.blue(backgroundColor));
616            mAppearAnimationFilter.setColor(sourceColor);
617            mAppearPaint.setColorFilter(mAppearAnimationFilter);
618        }
619    }
620
621    private int getBackgroundColor() {
622        if (mBgTint != 0) {
623            return mBgTint;
624        } else if (mShowingLegacyBackground) {
625            return mLegacyColor;
626        } else if (mIsBelowSpeedBump) {
627            return mLowPriorityColor;
628        } else {
629            return mNormalColor;
630        }
631    }
632
633    private int getRippleColor() {
634        if (mBgTint != 0) {
635            return mTintedRippleColor;
636        } else if (mShowingLegacyBackground) {
637            return mTintedRippleColor;
638        } else if (mIsBelowSpeedBump) {
639            return mLowPriorityRippleColor;
640        } else {
641            return mNormalRippleColor;
642        }
643    }
644
645    /**
646     * When we draw the appear animation, we render the view in a bitmap and render this bitmap
647     * as a shader of a rect. This call creates the Bitmap and switches the drawing mode,
648     * such that the normal drawing of the views does not happen anymore.
649     *
650     * @param enable Should it be enabled.
651     */
652    private void enableAppearDrawing(boolean enable) {
653        if (enable != mDrawingAppearAnimation) {
654            if (enable) {
655                if (getWidth() == 0 || getActualHeight() == 0) {
656                    // TODO: This should not happen, but it can during expansion. Needs
657                    // investigation
658                    return;
659                }
660                Bitmap bitmap = Bitmap.createBitmap(getWidth(), getActualHeight(),
661                        Bitmap.Config.ARGB_8888);
662                Canvas canvas = new Canvas(bitmap);
663                draw(canvas);
664                mAppearPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP,
665                        Shader.TileMode.CLAMP));
666            } else {
667                mAppearPaint.setShader(null);
668            }
669            mDrawingAppearAnimation = enable;
670            invalidate();
671        }
672    }
673
674    @Override
675    protected void dispatchDraw(Canvas canvas) {
676        if (!mDrawingAppearAnimation) {
677            super.dispatchDraw(canvas);
678        } else {
679            drawAppearRect(canvas);
680        }
681    }
682
683    private void drawAppearRect(Canvas canvas) {
684        canvas.save();
685        canvas.translate(0, mAppearAnimationTranslation);
686        canvas.drawRoundRect(mAppearAnimationRect, mRoundedRectCornerRadius,
687                mRoundedRectCornerRadius, mAppearPaint);
688        canvas.restore();
689    }
690
691    public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
692        mOnActivatedListener = onActivatedListener;
693    }
694
695    public void reset() {
696        setTintColor(0);
697        setShowingLegacyBackground(false);
698        setBelowSpeedBump(false);
699    }
700
701    public interface OnActivatedListener {
702        void onActivated(ActivatableNotificationView view);
703        void onActivationReset(ActivatableNotificationView view);
704    }
705}
706