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 17 package com.android.systemui.statusbar; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.ValueAnimator; 23 import android.content.Context; 24 import android.graphics.Bitmap; 25 import android.graphics.BitmapShader; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.ColorMatrix; 29 import android.graphics.ColorMatrixColorFilter; 30 import android.graphics.Paint; 31 import android.graphics.PorterDuff; 32 import android.graphics.PorterDuffColorFilter; 33 import android.graphics.RectF; 34 import android.graphics.Shader; 35 import android.util.AttributeSet; 36 import android.view.MotionEvent; 37 import android.view.View; 38 import android.view.ViewAnimationUtils; 39 import android.view.ViewConfiguration; 40 import android.view.animation.AnimationUtils; 41 import android.view.animation.Interpolator; 42 import android.view.animation.LinearInterpolator; 43 import android.view.animation.PathInterpolator; 44 45 import 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 */ 51 public 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 if (!isAttachedToWindow()) { 260 return; 261 } 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 mBackgroundNormal.animate().cancel(); 407 if (mDimmed) { 408 mBackgroundDimmed.setVisibility(View.VISIBLE); 409 } else { 410 mBackgroundNormal.setVisibility(View.VISIBLE); 411 } 412 float startAlpha = mDimmed ? 1f : 0; 413 float endAlpha = mDimmed ? 0 : 1f; 414 int duration = BACKGROUND_ANIMATION_LENGTH_MS; 415 // Check whether there is already a background animation running. 416 if (mBackgroundAnimator != null) { 417 startAlpha = (Float) mBackgroundAnimator.getAnimatedValue(); 418 duration = (int) mBackgroundAnimator.getCurrentPlayTime(); 419 mBackgroundAnimator.removeAllListeners(); 420 mBackgroundAnimator.cancel(); 421 if (duration <= 0) { 422 updateBackground(); 423 return; 424 } 425 } 426 mBackgroundNormal.setAlpha(startAlpha); 427 mBackgroundAnimator = 428 ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha); 429 mBackgroundAnimator.setInterpolator(mFastOutSlowInInterpolator); 430 mBackgroundAnimator.setDuration(duration); 431 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 432 @Override 433 public void onAnimationEnd(Animator animation) { 434 if (mDimmed) { 435 mBackgroundNormal.setVisibility(View.INVISIBLE); 436 } else { 437 mBackgroundDimmed.setVisibility(View.INVISIBLE); 438 } 439 mBackgroundAnimator = null; 440 } 441 }); 442 mBackgroundAnimator.start(); 443 } 444 445 private void updateBackground() { 446 if (mDimmed) { 447 mBackgroundDimmed.setVisibility(View.VISIBLE); 448 mBackgroundNormal.setVisibility(View.INVISIBLE); 449 } else { 450 cancelFadeAnimations(); 451 mBackgroundDimmed.setVisibility(View.INVISIBLE); 452 mBackgroundNormal.setVisibility(View.VISIBLE); 453 mBackgroundNormal.setAlpha(1f); 454 removeCallbacks(mTapTimeoutRunnable); 455 } 456 } 457 458 private void cancelFadeAnimations() { 459 if (mBackgroundAnimator != null) { 460 mBackgroundAnimator.cancel(); 461 } 462 mBackgroundNormal.animate().cancel(); 463 } 464 465 @Override 466 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 467 super.onLayout(changed, left, top, right, bottom); 468 setPivotX(getWidth() / 2); 469 } 470 471 @Override 472 public void setActualHeight(int actualHeight, boolean notifyListeners) { 473 super.setActualHeight(actualHeight, notifyListeners); 474 setPivotY(actualHeight / 2); 475 mBackgroundNormal.setActualHeight(actualHeight); 476 mBackgroundDimmed.setActualHeight(actualHeight); 477 } 478 479 @Override 480 public void setClipTopAmount(int clipTopAmount) { 481 super.setClipTopAmount(clipTopAmount); 482 mBackgroundNormal.setClipTopAmount(clipTopAmount); 483 mBackgroundDimmed.setClipTopAmount(clipTopAmount); 484 } 485 486 @Override 487 public void performRemoveAnimation(long duration, float translationDirection, 488 Runnable onFinishedRunnable) { 489 enableAppearDrawing(true); 490 if (mDrawingAppearAnimation) { 491 startAppearAnimation(false /* isAppearing */, translationDirection, 492 0, duration, onFinishedRunnable); 493 } else if (onFinishedRunnable != null) { 494 onFinishedRunnable.run(); 495 } 496 } 497 498 @Override 499 public void performAddAnimation(long delay, long duration) { 500 enableAppearDrawing(true); 501 if (mDrawingAppearAnimation) { 502 startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null); 503 } 504 } 505 506 private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, 507 long duration, final Runnable onFinishedRunnable) { 508 if (mAppearAnimator != null) { 509 mAppearAnimator.cancel(); 510 } 511 mAnimationTranslationY = translationDirection * mActualHeight; 512 if (mAppearAnimationFraction == -1.0f) { 513 // not initialized yet, we start anew 514 if (isAppearing) { 515 mAppearAnimationFraction = 0.0f; 516 mAppearAnimationTranslation = mAnimationTranslationY; 517 } else { 518 mAppearAnimationFraction = 1.0f; 519 mAppearAnimationTranslation = 0; 520 } 521 } 522 523 float targetValue; 524 if (isAppearing) { 525 mCurrentAppearInterpolator = mSlowOutFastInInterpolator; 526 mCurrentAlphaInterpolator = mLinearOutSlowInInterpolator; 527 targetValue = 1.0f; 528 } else { 529 mCurrentAppearInterpolator = mFastOutSlowInInterpolator; 530 mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator; 531 targetValue = 0.0f; 532 } 533 mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, 534 targetValue); 535 mAppearAnimator.setInterpolator(mLinearInterpolator); 536 mAppearAnimator.setDuration( 537 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); 538 mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 539 @Override 540 public void onAnimationUpdate(ValueAnimator animation) { 541 mAppearAnimationFraction = (float) animation.getAnimatedValue(); 542 updateAppearAnimationAlpha(); 543 updateAppearRect(); 544 invalidate(); 545 } 546 }); 547 if (delay > 0) { 548 // we need to apply the initial state already to avoid drawn frames in the wrong state 549 updateAppearAnimationAlpha(); 550 updateAppearRect(); 551 mAppearAnimator.setStartDelay(delay); 552 } 553 mAppearAnimator.addListener(new AnimatorListenerAdapter() { 554 private boolean mWasCancelled; 555 556 @Override 557 public void onAnimationEnd(Animator animation) { 558 if (onFinishedRunnable != null) { 559 onFinishedRunnable.run(); 560 } 561 if (!mWasCancelled) { 562 mAppearAnimationFraction = -1; 563 setOutlineRect(null); 564 enableAppearDrawing(false); 565 } 566 } 567 568 @Override 569 public void onAnimationStart(Animator animation) { 570 mWasCancelled = false; 571 } 572 573 @Override 574 public void onAnimationCancel(Animator animation) { 575 mWasCancelled = true; 576 } 577 }); 578 mAppearAnimator.start(); 579 } 580 581 private void updateAppearRect() { 582 float inverseFraction = (1.0f - mAppearAnimationFraction); 583 float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction); 584 float translateYTotalAmount = translationFraction * mAnimationTranslationY; 585 mAppearAnimationTranslation = translateYTotalAmount; 586 587 // handle width animation 588 float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START)) 589 / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END); 590 widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction)); 591 widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction); 592 float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) * 593 widthFraction); 594 float right = getWidth() - left; 595 596 // handle top animation 597 float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) / 598 VERTICAL_ANIMATION_START; 599 heightFraction = Math.max(0.0f, heightFraction); 600 heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction); 601 602 float top; 603 float bottom; 604 if (mAnimationTranslationY > 0.0f) { 605 bottom = mActualHeight - heightFraction * mAnimationTranslationY * 0.1f 606 - translateYTotalAmount; 607 top = bottom * heightFraction; 608 } else { 609 top = heightFraction * (mActualHeight + mAnimationTranslationY) * 0.1f - 610 translateYTotalAmount; 611 bottom = mActualHeight * (1 - heightFraction) + top * heightFraction; 612 } 613 mAppearAnimationRect.set(left, top, right, bottom); 614 setOutlineRect(left, top + mAppearAnimationTranslation, right, 615 bottom + mAppearAnimationTranslation); 616 } 617 618 private void updateAppearAnimationAlpha() { 619 int backgroundColor = getBackgroundColor(); 620 if (backgroundColor != -1) { 621 float contentAlphaProgress = mAppearAnimationFraction; 622 contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END); 623 contentAlphaProgress = Math.min(1.0f, contentAlphaProgress); 624 contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress); 625 int sourceColor = Color.argb((int) (255 * (1.0f - contentAlphaProgress)), 626 Color.red(backgroundColor), Color.green(backgroundColor), 627 Color.blue(backgroundColor)); 628 mAppearAnimationFilter.setColor(sourceColor); 629 mAppearPaint.setColorFilter(mAppearAnimationFilter); 630 } 631 } 632 633 private int getBackgroundColor() { 634 if (mBgTint != 0) { 635 return mBgTint; 636 } else if (mShowingLegacyBackground) { 637 return mLegacyColor; 638 } else if (mIsBelowSpeedBump) { 639 return mLowPriorityColor; 640 } else { 641 return mNormalColor; 642 } 643 } 644 645 private int getRippleColor() { 646 if (mBgTint != 0) { 647 return mTintedRippleColor; 648 } else if (mShowingLegacyBackground) { 649 return mTintedRippleColor; 650 } else if (mIsBelowSpeedBump) { 651 return mLowPriorityRippleColor; 652 } else { 653 return mNormalRippleColor; 654 } 655 } 656 657 /** 658 * When we draw the appear animation, we render the view in a bitmap and render this bitmap 659 * as a shader of a rect. This call creates the Bitmap and switches the drawing mode, 660 * such that the normal drawing of the views does not happen anymore. 661 * 662 * @param enable Should it be enabled. 663 */ 664 private void enableAppearDrawing(boolean enable) { 665 if (enable != mDrawingAppearAnimation) { 666 if (enable) { 667 if (getWidth() == 0 || getActualHeight() == 0) { 668 // TODO: This should not happen, but it can during expansion. Needs 669 // investigation 670 return; 671 } 672 Bitmap bitmap = Bitmap.createBitmap(getWidth(), getActualHeight(), 673 Bitmap.Config.ARGB_8888); 674 Canvas canvas = new Canvas(bitmap); 675 draw(canvas); 676 mAppearPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, 677 Shader.TileMode.CLAMP)); 678 } else { 679 mAppearPaint.setShader(null); 680 } 681 mDrawingAppearAnimation = enable; 682 invalidate(); 683 } 684 } 685 686 @Override 687 protected void dispatchDraw(Canvas canvas) { 688 if (!mDrawingAppearAnimation) { 689 super.dispatchDraw(canvas); 690 } else { 691 drawAppearRect(canvas); 692 } 693 } 694 695 private void drawAppearRect(Canvas canvas) { 696 canvas.save(); 697 canvas.translate(0, mAppearAnimationTranslation); 698 canvas.drawRoundRect(mAppearAnimationRect, mRoundedRectCornerRadius, 699 mRoundedRectCornerRadius, mAppearPaint); 700 canvas.restore(); 701 } 702 703 public void setOnActivatedListener(OnActivatedListener onActivatedListener) { 704 mOnActivatedListener = onActivatedListener; 705 } 706 707 public void reset() { 708 setTintColor(0); 709 setShowingLegacyBackground(false); 710 setBelowSpeedBump(false); 711 } 712 713 public interface OnActivatedListener { 714 void onActivated(ActivatableNotificationView view); 715 void onActivationReset(ActivatableNotificationView view); 716 } 717 } 718