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