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