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