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