ActivatableNotificationView.java revision db16737031091160a3a7d080ac44ce2836402d74
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.TimeAnimator; 23import android.animation.ValueAnimator; 24import android.content.Context; 25import android.graphics.Canvas; 26import android.graphics.RectF; 27import android.util.AttributeSet; 28import android.view.MotionEvent; 29import android.view.View; 30import android.view.ViewAnimationUtils; 31import android.view.ViewConfiguration; 32import android.view.animation.Interpolator; 33import android.view.animation.PathInterpolator; 34 35import com.android.systemui.Interpolators; 36import com.android.systemui.R; 37import com.android.systemui.classifier.FalsingManager; 38import com.android.systemui.statusbar.notification.FakeShadowView; 39import com.android.systemui.statusbar.notification.NotificationUtils; 40import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 41import com.android.systemui.statusbar.stack.StackStateAnimator; 42 43/** 44 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} 45 * to implement dimming/activating on Keyguard for the double-tap gesture 46 */ 47public abstract class ActivatableNotificationView extends ExpandableOutlineView { 48 49 private static final long DOUBLETAP_TIMEOUT_MS = 1200; 50 private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220; 51 private static final int ACTIVATE_ANIMATION_LENGTH = 220; 52 private static final int DARK_ANIMATION_LENGTH = 170; 53 54 /** 55 * The amount of width, which is kept in the end when performing a disappear animation (also 56 * the amount from which the horizontal appearing begins) 57 */ 58 private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f; 59 60 /** 61 * At which point from [0,1] does the horizontal collapse animation end (or start when 62 * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated. 63 */ 64 private static final float HORIZONTAL_ANIMATION_END = 0.2f; 65 66 /** 67 * At which point from [0,1] does the alpha animation end (or start when 68 * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated. 69 */ 70 private static final float ALPHA_ANIMATION_END = 0.0f; 71 72 /** 73 * At which point from [0,1] does the horizontal collapse animation start (or start when 74 * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all. 75 */ 76 private static final float HORIZONTAL_ANIMATION_START = 1.0f; 77 78 /** 79 * At which point from [0,1] does the vertical collapse animation start (or end when 80 * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all. 81 */ 82 private static final float VERTICAL_ANIMATION_START = 1.0f; 83 84 /** 85 * Scale for the background to animate from when exiting dark mode. 86 */ 87 private static final float DARK_EXIT_SCALE_START = 0.93f; 88 89 private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR 90 = new PathInterpolator(0.6f, 0, 0.5f, 1); 91 private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR 92 = new PathInterpolator(0, 0, 0.5f, 1); 93 private final int mTintedRippleColor; 94 private final int mLowPriorityRippleColor; 95 protected final int mNormalRippleColor; 96 97 private boolean mDimmed; 98 private boolean mDark; 99 100 private int mBgTint = 0; 101 private float mBgAlpha = 1f; 102 103 /** 104 * Flag to indicate that the notification has been touched once and the second touch will 105 * click it. 106 */ 107 private boolean mActivated; 108 109 private float mDownX; 110 private float mDownY; 111 private final float mTouchSlop; 112 113 private OnActivatedListener mOnActivatedListener; 114 115 private final Interpolator mSlowOutFastInInterpolator; 116 private final Interpolator mSlowOutLinearInInterpolator; 117 private Interpolator mCurrentAppearInterpolator; 118 private Interpolator mCurrentAlphaInterpolator; 119 120 private NotificationBackgroundView mBackgroundNormal; 121 private NotificationBackgroundView mBackgroundDimmed; 122 private ObjectAnimator mBackgroundAnimator; 123 private RectF mAppearAnimationRect = new RectF(); 124 private float mAnimationTranslationY; 125 private boolean mDrawingAppearAnimation; 126 private ValueAnimator mAppearAnimator; 127 private ValueAnimator mBackgroundColorAnimator; 128 private float mAppearAnimationFraction = -1.0f; 129 private float mAppearAnimationTranslation; 130 private boolean mShowingLegacyBackground; 131 private final int mLegacyColor; 132 private final int mNormalColor; 133 private final int mLowPriorityColor; 134 private boolean mIsBelowSpeedBump; 135 private FalsingManager mFalsingManager; 136 private boolean mTrackTouch; 137 138 private float mNormalBackgroundVisibilityAmount; 139 private ValueAnimator mFadeInFromDarkAnimator; 140 private float mDimmedBackgroundFadeInAmount = -1; 141 private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater 142 = new ValueAnimator.AnimatorUpdateListener() { 143 @Override 144 public void onAnimationUpdate(ValueAnimator animation) { 145 setNormalBackgroundVisibilityAmount(mBackgroundNormal.getAlpha()); 146 mDimmedBackgroundFadeInAmount = mBackgroundDimmed.getAlpha(); 147 } 148 }; 149 private AnimatorListenerAdapter mFadeInEndListener = new AnimatorListenerAdapter() { 150 @Override 151 public void onAnimationEnd(Animator animation) { 152 super.onAnimationEnd(animation); 153 mFadeInFromDarkAnimator = null; 154 mDimmedBackgroundFadeInAmount = -1; 155 updateBackground(); 156 } 157 }; 158 private ValueAnimator.AnimatorUpdateListener mUpdateOutlineListener 159 = new ValueAnimator.AnimatorUpdateListener() { 160 @Override 161 public void onAnimationUpdate(ValueAnimator animation) { 162 updateOutlineAlpha(); 163 } 164 }; 165 private float mShadowAlpha = 1.0f; 166 private FakeShadowView mFakeShadow; 167 private int mCurrentBackgroundTint; 168 private int mTargetTint; 169 private int mStartTint; 170 171 public ActivatableNotificationView(Context context, AttributeSet attrs) { 172 super(context, attrs); 173 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 174 mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); 175 mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); 176 setClipChildren(false); 177 setClipToPadding(false); 178 mLegacyColor = context.getColor(R.color.notification_legacy_background_color); 179 mNormalColor = context.getColor(R.color.notification_material_background_color); 180 mLowPriorityColor = context.getColor( 181 R.color.notification_material_background_low_priority_color); 182 mTintedRippleColor = context.getColor( 183 R.color.notification_ripple_tinted_color); 184 mLowPriorityRippleColor = context.getColor( 185 R.color.notification_ripple_color_low_priority); 186 mNormalRippleColor = context.getColor( 187 R.color.notification_ripple_untinted_color); 188 mFalsingManager = FalsingManager.getInstance(context); 189 } 190 191 @Override 192 protected void onFinishInflate() { 193 super.onFinishInflate(); 194 mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal); 195 mFakeShadow = (FakeShadowView) findViewById(R.id.fake_shadow); 196 mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed); 197 mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg); 198 mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim); 199 updateBackground(); 200 updateBackgroundTint(); 201 updateOutlineAlpha(); 202 } 203 204 private final Runnable mTapTimeoutRunnable = new Runnable() { 205 @Override 206 public void run() { 207 makeInactive(true /* animate */); 208 } 209 }; 210 211 @Override 212 public boolean onInterceptTouchEvent(MotionEvent ev) { 213 if (mDimmed && !mActivated 214 && ev.getActionMasked() == MotionEvent.ACTION_DOWN && disallowSingleClick(ev)) { 215 return true; 216 } 217 return super.onInterceptTouchEvent(ev); 218 } 219 220 protected boolean disallowSingleClick(MotionEvent ev) { 221 return false; 222 } 223 224 protected boolean handleSlideBack() { 225 return false; 226 } 227 228 @Override 229 public boolean onTouchEvent(MotionEvent event) { 230 boolean result; 231 if (mDimmed) { 232 boolean wasActivated = mActivated; 233 result = handleTouchEventDimmed(event); 234 if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) { 235 mFalsingManager.onNotificationDoubleTap(); 236 removeCallbacks(mTapTimeoutRunnable); 237 } 238 } else { 239 result = super.onTouchEvent(event); 240 } 241 return result; 242 } 243 244 @Override 245 public void drawableHotspotChanged(float x, float y) { 246 if (!mDimmed){ 247 mBackgroundNormal.drawableHotspotChanged(x, y); 248 } 249 } 250 251 @Override 252 protected void drawableStateChanged() { 253 super.drawableStateChanged(); 254 if (mDimmed) { 255 mBackgroundDimmed.setState(getDrawableState()); 256 } else { 257 mBackgroundNormal.setState(getDrawableState()); 258 } 259 } 260 261 private boolean handleTouchEventDimmed(MotionEvent event) { 262 int action = event.getActionMasked(); 263 switch (action) { 264 case MotionEvent.ACTION_DOWN: 265 mDownX = event.getX(); 266 mDownY = event.getY(); 267 mTrackTouch = true; 268 if (mDownY > getActualHeight()) { 269 mTrackTouch = false; 270 } 271 break; 272 case MotionEvent.ACTION_MOVE: 273 if (!isWithinTouchSlop(event)) { 274 makeInactive(true /* animate */); 275 mTrackTouch = false; 276 } 277 break; 278 case MotionEvent.ACTION_UP: 279 if (isWithinTouchSlop(event)) { 280 if (handleSlideBack()) { 281 return true; 282 } 283 if (!mActivated) { 284 makeActive(); 285 postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS); 286 } else { 287 if (!performClick()) { 288 return false; 289 } 290 } 291 } else { 292 makeInactive(true /* animate */); 293 mTrackTouch = false; 294 } 295 break; 296 case MotionEvent.ACTION_CANCEL: 297 makeInactive(true /* animate */); 298 mTrackTouch = false; 299 break; 300 default: 301 break; 302 } 303 return mTrackTouch; 304 } 305 306 private void makeActive() { 307 mFalsingManager.onNotificationActive(); 308 startActivateAnimation(false /* reverse */); 309 mActivated = true; 310 if (mOnActivatedListener != null) { 311 mOnActivatedListener.onActivated(this); 312 } 313 } 314 315 private void startActivateAnimation(final boolean reverse) { 316 if (!isAttachedToWindow()) { 317 return; 318 } 319 int widthHalf = mBackgroundNormal.getWidth()/2; 320 int heightHalf = mBackgroundNormal.getActualHeight()/2; 321 float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf); 322 Animator animator; 323 if (reverse) { 324 animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal, 325 widthHalf, heightHalf, radius, 0); 326 } else { 327 animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal, 328 widthHalf, heightHalf, 0, radius); 329 } 330 mBackgroundNormal.setVisibility(View.VISIBLE); 331 Interpolator interpolator; 332 Interpolator alphaInterpolator; 333 if (!reverse) { 334 interpolator = Interpolators.LINEAR_OUT_SLOW_IN; 335 alphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; 336 } else { 337 interpolator = ACTIVATE_INVERSE_INTERPOLATOR; 338 alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR; 339 } 340 animator.setInterpolator(interpolator); 341 animator.setDuration(ACTIVATE_ANIMATION_LENGTH); 342 if (reverse) { 343 mBackgroundNormal.setAlpha(1f); 344 animator.addListener(new AnimatorListenerAdapter() { 345 @Override 346 public void onAnimationEnd(Animator animation) { 347 updateBackground(); 348 } 349 }); 350 animator.start(); 351 } else { 352 mBackgroundNormal.setAlpha(0.4f); 353 animator.start(); 354 } 355 mBackgroundNormal.animate() 356 .alpha(reverse ? 0f : 1f) 357 .setInterpolator(alphaInterpolator) 358 .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 359 @Override 360 public void onAnimationUpdate(ValueAnimator animation) { 361 float animatedFraction = animation.getAnimatedFraction(); 362 if (reverse) { 363 animatedFraction = 1.0f - animatedFraction; 364 } 365 setNormalBackgroundVisibilityAmount(animatedFraction); 366 } 367 }) 368 .setDuration(ACTIVATE_ANIMATION_LENGTH); 369 } 370 371 /** 372 * Cancels the hotspot and makes the notification inactive. 373 */ 374 public void makeInactive(boolean animate) { 375 if (mActivated) { 376 mActivated = false; 377 if (mDimmed) { 378 if (animate) { 379 startActivateAnimation(true /* reverse */); 380 } else { 381 updateBackground(); 382 } 383 } 384 } 385 if (mOnActivatedListener != null) { 386 mOnActivatedListener.onActivationReset(this); 387 } 388 removeCallbacks(mTapTimeoutRunnable); 389 } 390 391 private boolean isWithinTouchSlop(MotionEvent event) { 392 return Math.abs(event.getX() - mDownX) < mTouchSlop 393 && Math.abs(event.getY() - mDownY) < mTouchSlop; 394 } 395 396 public void setDimmed(boolean dimmed, boolean fade) { 397 if (mDimmed != dimmed) { 398 mDimmed = dimmed; 399 resetBackgroundAlpha(); 400 if (fade) { 401 fadeDimmedBackground(); 402 } else { 403 updateBackground(); 404 } 405 } 406 } 407 408 public void setDark(boolean dark, boolean fade, long delay) { 409 super.setDark(dark, fade, delay); 410 if (mDark == dark) { 411 return; 412 } 413 mDark = dark; 414 updateBackground(); 415 if (!dark && fade && !shouldHideBackground()) { 416 fadeInFromDark(delay); 417 } 418 updateOutlineAlpha(); 419 } 420 421 private void updateOutlineAlpha() { 422 if (mDark) { 423 setOutlineAlpha(0f); 424 return; 425 } 426 float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED; 427 alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount); 428 alpha *= mShadowAlpha; 429 if (mFadeInFromDarkAnimator != null) { 430 alpha *= mFadeInFromDarkAnimator.getAnimatedFraction(); 431 } 432 setOutlineAlpha(alpha); 433 } 434 435 public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { 436 mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount; 437 updateOutlineAlpha(); 438 } 439 440 public void setShowingLegacyBackground(boolean showing) { 441 mShowingLegacyBackground = showing; 442 updateBackgroundTint(); 443 } 444 445 @Override 446 public void setBelowSpeedBump(boolean below) { 447 super.setBelowSpeedBump(below); 448 if (below != mIsBelowSpeedBump) { 449 mIsBelowSpeedBump = below; 450 updateBackgroundTint(); 451 onBelowSpeedBumpChanged(); 452 } 453 } 454 455 protected void onBelowSpeedBumpChanged() { 456 } 457 458 /** 459 * @return whether we are below the speed bump 460 */ 461 public boolean isBelowSpeedBump() { 462 return mIsBelowSpeedBump; 463 } 464 465 /** 466 * Sets the tint color of the background 467 */ 468 public void setTintColor(int color) { 469 setTintColor(color, false); 470 } 471 472 /** 473 * Sets the tint color of the background 474 */ 475 public void setTintColor(int color, boolean animated) { 476 mBgTint = color; 477 updateBackgroundTint(animated); 478 } 479 480 protected void updateBackgroundTint() { 481 updateBackgroundTint(false /* animated */); 482 } 483 484 private void updateBackgroundTint(boolean animated) { 485 if (mBackgroundColorAnimator != null) { 486 mBackgroundColorAnimator.cancel(); 487 } 488 int rippleColor = getRippleColor(); 489 mBackgroundDimmed.setRippleColor(rippleColor); 490 mBackgroundNormal.setRippleColor(rippleColor); 491 int color = calculateBgColor(); 492 if (!animated) { 493 setBackgroundTintColor(color); 494 } else if (color != mCurrentBackgroundTint) { 495 mStartTint = mCurrentBackgroundTint; 496 mTargetTint = color; 497 mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); 498 mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 499 @Override 500 public void onAnimationUpdate(ValueAnimator animation) { 501 int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, 502 animation.getAnimatedFraction()); 503 setBackgroundTintColor(newColor); 504 } 505 }); 506 mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 507 mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); 508 mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() { 509 @Override 510 public void onAnimationEnd(Animator animation) { 511 mBackgroundColorAnimator = null; 512 } 513 }); 514 mBackgroundColorAnimator.start(); 515 } 516 } 517 518 private void setBackgroundTintColor(int color) { 519 mCurrentBackgroundTint = color; 520 if (color == mNormalColor) { 521 // We don't need to tint a normal notification 522 color = 0; 523 } 524 mBackgroundDimmed.setTint(color); 525 mBackgroundNormal.setTint(color); 526 } 527 528 /** 529 * Fades in the background when exiting dark mode. 530 */ 531 private void fadeInFromDark(long delay) { 532 final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal; 533 background.setAlpha(0f); 534 mBackgroundVisibilityUpdater.onAnimationUpdate(null); 535 background.setPivotX(mBackgroundDimmed.getWidth() / 2f); 536 background.setPivotY(getActualHeight() / 2f); 537 background.setScaleX(DARK_EXIT_SCALE_START); 538 background.setScaleY(DARK_EXIT_SCALE_START); 539 background.animate() 540 .alpha(1f) 541 .scaleX(1f) 542 .scaleY(1f) 543 .setDuration(DARK_ANIMATION_LENGTH) 544 .setStartDelay(delay) 545 .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) 546 .setListener(new AnimatorListenerAdapter() { 547 @Override 548 public void onAnimationCancel(Animator animation) { 549 // Jump state if we are cancelled 550 background.setScaleX(1f); 551 background.setScaleY(1f); 552 background.setAlpha(1f); 553 } 554 }) 555 .setUpdateListener(mBackgroundVisibilityUpdater) 556 .start(); 557 mFadeInFromDarkAnimator = TimeAnimator.ofFloat(0.0f, 1.0f); 558 mFadeInFromDarkAnimator.setDuration(DARK_ANIMATION_LENGTH); 559 mFadeInFromDarkAnimator.setStartDelay(delay); 560 mFadeInFromDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 561 mFadeInFromDarkAnimator.addListener(mFadeInEndListener); 562 mFadeInFromDarkAnimator.addUpdateListener(mUpdateOutlineListener); 563 mFadeInFromDarkAnimator.start(); 564 } 565 566 /** 567 * Fades the background when the dimmed state changes. 568 */ 569 private void fadeDimmedBackground() { 570 mBackgroundDimmed.animate().cancel(); 571 mBackgroundNormal.animate().cancel(); 572 if (mActivated) { 573 updateBackground(); 574 return; 575 } 576 if (!shouldHideBackground()) { 577 if (mDimmed) { 578 mBackgroundDimmed.setVisibility(View.VISIBLE); 579 } else { 580 mBackgroundNormal.setVisibility(View.VISIBLE); 581 } 582 } 583 float startAlpha = mDimmed ? 1f : 0; 584 float endAlpha = mDimmed ? 0 : 1f; 585 int duration = BACKGROUND_ANIMATION_LENGTH_MS; 586 // Check whether there is already a background animation running. 587 if (mBackgroundAnimator != null) { 588 startAlpha = (Float) mBackgroundAnimator.getAnimatedValue(); 589 duration = (int) mBackgroundAnimator.getCurrentPlayTime(); 590 mBackgroundAnimator.removeAllListeners(); 591 mBackgroundAnimator.cancel(); 592 if (duration <= 0) { 593 updateBackground(); 594 return; 595 } 596 } 597 mBackgroundNormal.setAlpha(startAlpha); 598 mBackgroundAnimator = 599 ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha); 600 mBackgroundAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 601 mBackgroundAnimator.setDuration(duration); 602 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 603 @Override 604 public void onAnimationEnd(Animator animation) { 605 updateBackground(); 606 mBackgroundAnimator = null; 607 if (mFadeInFromDarkAnimator == null) { 608 mDimmedBackgroundFadeInAmount = -1; 609 } 610 } 611 }); 612 mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater); 613 mBackgroundAnimator.start(); 614 } 615 616 protected void updateBackgroundAlpha(float transformationAmount) { 617 mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; 618 if (mDimmedBackgroundFadeInAmount != -1) { 619 mBgAlpha *= mDimmedBackgroundFadeInAmount; 620 } 621 mBackgroundDimmed.setAlpha(mBgAlpha); 622 } 623 624 protected void resetBackgroundAlpha() { 625 updateBackgroundAlpha(0f /* transformationAmount */); 626 } 627 628 protected void updateBackground() { 629 cancelFadeAnimations(); 630 if (shouldHideBackground()) { 631 mBackgroundDimmed.setVisibility(View.INVISIBLE); 632 mBackgroundNormal.setVisibility(View.INVISIBLE); 633 } else if (mDimmed) { 634 // When groups are animating to the expanded state from the lockscreen, show the 635 // normal background instead of the dimmed background 636 final boolean dontShowDimmed = isGroupExpansionChanging() && isChildInGroup(); 637 mBackgroundDimmed.setVisibility(dontShowDimmed ? View.INVISIBLE : View.VISIBLE); 638 mBackgroundNormal.setVisibility((mActivated || dontShowDimmed) 639 ? View.VISIBLE 640 : View.INVISIBLE); 641 } else { 642 mBackgroundDimmed.setVisibility(View.INVISIBLE); 643 mBackgroundNormal.setVisibility(View.VISIBLE); 644 mBackgroundNormal.setAlpha(1f); 645 removeCallbacks(mTapTimeoutRunnable); 646 // make in inactive to avoid it sticking around active 647 makeInactive(false /* animate */); 648 } 649 setNormalBackgroundVisibilityAmount( 650 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); 651 } 652 653 protected boolean shouldHideBackground() { 654 return mDark; 655 } 656 657 private void cancelFadeAnimations() { 658 if (mBackgroundAnimator != null) { 659 mBackgroundAnimator.cancel(); 660 } 661 mBackgroundDimmed.animate().cancel(); 662 mBackgroundNormal.animate().cancel(); 663 } 664 665 @Override 666 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 667 super.onLayout(changed, left, top, right, bottom); 668 setPivotX(getWidth() / 2); 669 } 670 671 @Override 672 public void setActualHeight(int actualHeight, boolean notifyListeners) { 673 super.setActualHeight(actualHeight, notifyListeners); 674 setPivotY(actualHeight / 2); 675 mBackgroundNormal.setActualHeight(actualHeight); 676 mBackgroundDimmed.setActualHeight(actualHeight); 677 } 678 679 @Override 680 public void setClipTopAmount(int clipTopAmount) { 681 super.setClipTopAmount(clipTopAmount); 682 mBackgroundNormal.setClipTopAmount(clipTopAmount); 683 mBackgroundDimmed.setClipTopAmount(clipTopAmount); 684 } 685 686 @Override 687 public void setClipBottomAmount(int clipBottomAmount) { 688 super.setClipBottomAmount(clipBottomAmount); 689 mBackgroundNormal.setClipBottomAmount(clipBottomAmount); 690 mBackgroundDimmed.setClipBottomAmount(clipBottomAmount); 691 } 692 693 @Override 694 public void performRemoveAnimation(long duration, float translationDirection, 695 Runnable onFinishedRunnable) { 696 enableAppearDrawing(true); 697 if (mDrawingAppearAnimation) { 698 startAppearAnimation(false /* isAppearing */, translationDirection, 699 0, duration, onFinishedRunnable); 700 } else if (onFinishedRunnable != null) { 701 onFinishedRunnable.run(); 702 } 703 } 704 705 @Override 706 public void performAddAnimation(long delay, long duration) { 707 enableAppearDrawing(true); 708 if (mDrawingAppearAnimation) { 709 startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null); 710 } 711 } 712 713 private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, 714 long duration, final Runnable onFinishedRunnable) { 715 cancelAppearAnimation(); 716 mAnimationTranslationY = translationDirection * getActualHeight(); 717 if (mAppearAnimationFraction == -1.0f) { 718 // not initialized yet, we start anew 719 if (isAppearing) { 720 mAppearAnimationFraction = 0.0f; 721 mAppearAnimationTranslation = mAnimationTranslationY; 722 } else { 723 mAppearAnimationFraction = 1.0f; 724 mAppearAnimationTranslation = 0; 725 } 726 } 727 728 float targetValue; 729 if (isAppearing) { 730 mCurrentAppearInterpolator = mSlowOutFastInInterpolator; 731 mCurrentAlphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; 732 targetValue = 1.0f; 733 } else { 734 mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN; 735 mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator; 736 targetValue = 0.0f; 737 } 738 mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, 739 targetValue); 740 mAppearAnimator.setInterpolator(Interpolators.LINEAR); 741 mAppearAnimator.setDuration( 742 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); 743 mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 744 @Override 745 public void onAnimationUpdate(ValueAnimator animation) { 746 mAppearAnimationFraction = (float) animation.getAnimatedValue(); 747 updateAppearAnimationAlpha(); 748 updateAppearRect(); 749 invalidate(); 750 } 751 }); 752 if (delay > 0) { 753 // we need to apply the initial state already to avoid drawn frames in the wrong state 754 updateAppearAnimationAlpha(); 755 updateAppearRect(); 756 mAppearAnimator.setStartDelay(delay); 757 } 758 mAppearAnimator.addListener(new AnimatorListenerAdapter() { 759 private boolean mWasCancelled; 760 761 @Override 762 public void onAnimationEnd(Animator animation) { 763 if (onFinishedRunnable != null) { 764 onFinishedRunnable.run(); 765 } 766 if (!mWasCancelled) { 767 enableAppearDrawing(false); 768 onAppearAnimationFinished(isAppearing); 769 } 770 } 771 772 @Override 773 public void onAnimationStart(Animator animation) { 774 mWasCancelled = false; 775 } 776 777 @Override 778 public void onAnimationCancel(Animator animation) { 779 mWasCancelled = true; 780 } 781 }); 782 mAppearAnimator.start(); 783 } 784 785 protected void onAppearAnimationFinished(boolean wasAppearing) { 786 } 787 788 private void cancelAppearAnimation() { 789 if (mAppearAnimator != null) { 790 mAppearAnimator.cancel(); 791 mAppearAnimator = null; 792 } 793 } 794 795 public void cancelAppearDrawing() { 796 cancelAppearAnimation(); 797 enableAppearDrawing(false); 798 } 799 800 private void updateAppearRect() { 801 float inverseFraction = (1.0f - mAppearAnimationFraction); 802 float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction); 803 float translateYTotalAmount = translationFraction * mAnimationTranslationY; 804 mAppearAnimationTranslation = translateYTotalAmount; 805 806 // handle width animation 807 float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START)) 808 / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END); 809 widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction)); 810 widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction); 811 float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) * 812 widthFraction); 813 float right = getWidth() - left; 814 815 // handle top animation 816 float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) / 817 VERTICAL_ANIMATION_START; 818 heightFraction = Math.max(0.0f, heightFraction); 819 heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction); 820 821 float top; 822 float bottom; 823 final int actualHeight = getActualHeight(); 824 if (mAnimationTranslationY > 0.0f) { 825 bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f 826 - translateYTotalAmount; 827 top = bottom * heightFraction; 828 } else { 829 top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f - 830 translateYTotalAmount; 831 bottom = actualHeight * (1 - heightFraction) + top * heightFraction; 832 } 833 mAppearAnimationRect.set(left, top, right, bottom); 834 setOutlineRect(left, top + mAppearAnimationTranslation, right, 835 bottom + mAppearAnimationTranslation); 836 } 837 838 private void updateAppearAnimationAlpha() { 839 float contentAlphaProgress = mAppearAnimationFraction; 840 contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END); 841 contentAlphaProgress = Math.min(1.0f, contentAlphaProgress); 842 contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress); 843 setContentAlpha(contentAlphaProgress); 844 } 845 846 private void setContentAlpha(float contentAlpha) { 847 View contentView = getContentView(); 848 if (contentView.hasOverlappingRendering()) { 849 int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE 850 : LAYER_TYPE_HARDWARE; 851 int currentLayerType = contentView.getLayerType(); 852 if (currentLayerType != layerType) { 853 contentView.setLayerType(layerType, null); 854 } 855 } 856 contentView.setAlpha(contentAlpha); 857 } 858 859 protected abstract View getContentView(); 860 861 public int calculateBgColor() { 862 return calculateBgColor(true /* withTint */); 863 } 864 865 private int calculateBgColor(boolean withTint) { 866 if (withTint && mBgTint != 0) { 867 return mBgTint; 868 } else if (mShowingLegacyBackground) { 869 return mLegacyColor; 870 } else if (mIsBelowSpeedBump) { 871 return mLowPriorityColor; 872 } else { 873 return mNormalColor; 874 } 875 } 876 877 protected int getRippleColor() { 878 if (mBgTint != 0) { 879 return mTintedRippleColor; 880 } else if (mShowingLegacyBackground) { 881 return mTintedRippleColor; 882 } else if (mIsBelowSpeedBump) { 883 return mLowPriorityRippleColor; 884 } else { 885 return mNormalRippleColor; 886 } 887 } 888 889 /** 890 * When we draw the appear animation, we render the view in a bitmap and render this bitmap 891 * as a shader of a rect. This call creates the Bitmap and switches the drawing mode, 892 * such that the normal drawing of the views does not happen anymore. 893 * 894 * @param enable Should it be enabled. 895 */ 896 private void enableAppearDrawing(boolean enable) { 897 if (enable != mDrawingAppearAnimation) { 898 mDrawingAppearAnimation = enable; 899 if (!enable) { 900 setContentAlpha(1.0f); 901 mAppearAnimationFraction = -1; 902 setOutlineRect(null); 903 } 904 invalidate(); 905 } 906 } 907 908 @Override 909 protected void dispatchDraw(Canvas canvas) { 910 if (mDrawingAppearAnimation) { 911 canvas.save(); 912 canvas.translate(0, mAppearAnimationTranslation); 913 } 914 super.dispatchDraw(canvas); 915 if (mDrawingAppearAnimation) { 916 canvas.restore(); 917 } 918 } 919 920 public void setOnActivatedListener(OnActivatedListener onActivatedListener) { 921 mOnActivatedListener = onActivatedListener; 922 } 923 924 public void reset() { 925 setTintColor(0); 926 resetBackgroundAlpha(); 927 setShowingLegacyBackground(false); 928 setBelowSpeedBump(false); 929 } 930 931 public boolean hasSameBgColor(ActivatableNotificationView otherView) { 932 return calculateBgColor() == otherView.calculateBgColor(); 933 } 934 935 @Override 936 public float getShadowAlpha() { 937 return mShadowAlpha; 938 } 939 940 @Override 941 public void setShadowAlpha(float shadowAlpha) { 942 if (shadowAlpha != mShadowAlpha) { 943 mShadowAlpha = shadowAlpha; 944 updateOutlineAlpha(); 945 } 946 } 947 948 @Override 949 public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, 950 int outlineTranslation) { 951 mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ() 952 + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd, 953 outlineTranslation); 954 } 955 956 public int getBackgroundColorWithoutTint() { 957 return calculateBgColor(false /* withTint */); 958 } 959 960 public interface OnActivatedListener { 961 void onActivated(ActivatableNotificationView view); 962 void onActivationReset(ActivatableNotificationView view); 963 } 964} 965