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