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.TimeAnimator; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.graphics.Canvas; 26 import android.graphics.RectF; 27 import android.util.AttributeSet; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewAnimationUtils; 31 import android.view.ViewConfiguration; 32 import android.view.animation.Interpolator; 33 import android.view.animation.PathInterpolator; 34 35 import com.android.systemui.Interpolators; 36 import com.android.systemui.R; 37 import com.android.systemui.classifier.FalsingManager; 38 import com.android.systemui.statusbar.notification.FakeShadowView; 39 import com.android.systemui.statusbar.notification.NotificationUtils; 40 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 41 import com.android.systemui.statusbar.stack.StackStateAnimator; 42 43 /** 44 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} 45 * to implement dimming/activating on Keyguard for the double-tap gesture 46 */ 47 public 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 } 452 } 453 454 /** 455 * Sets the tint color of the background 456 */ 457 public void setTintColor(int color) { 458 setTintColor(color, false); 459 } 460 461 /** 462 * Sets the tint color of the background 463 */ 464 public void setTintColor(int color, boolean animated) { 465 mBgTint = color; 466 updateBackgroundTint(animated); 467 } 468 469 protected void updateBackgroundTint() { 470 updateBackgroundTint(false /* animated */); 471 } 472 473 private void updateBackgroundTint(boolean animated) { 474 if (mBackgroundColorAnimator != null) { 475 mBackgroundColorAnimator.cancel(); 476 } 477 int rippleColor = getRippleColor(); 478 mBackgroundDimmed.setRippleColor(rippleColor); 479 mBackgroundNormal.setRippleColor(rippleColor); 480 int color = calculateBgColor(); 481 if (!animated) { 482 setBackgroundTintColor(color); 483 } else if (color != mCurrentBackgroundTint) { 484 mStartTint = mCurrentBackgroundTint; 485 mTargetTint = color; 486 mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); 487 mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 488 @Override 489 public void onAnimationUpdate(ValueAnimator animation) { 490 int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, 491 animation.getAnimatedFraction()); 492 setBackgroundTintColor(newColor); 493 } 494 }); 495 mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 496 mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); 497 mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() { 498 @Override 499 public void onAnimationEnd(Animator animation) { 500 mBackgroundColorAnimator = null; 501 } 502 }); 503 mBackgroundColorAnimator.start(); 504 } 505 } 506 507 private void setBackgroundTintColor(int color) { 508 mCurrentBackgroundTint = color; 509 if (color == mNormalColor) { 510 // We don't need to tint a normal notification 511 color = 0; 512 } 513 mBackgroundDimmed.setTint(color); 514 mBackgroundNormal.setTint(color); 515 } 516 517 /** 518 * Fades in the background when exiting dark mode. 519 */ 520 private void fadeInFromDark(long delay) { 521 final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal; 522 background.setAlpha(0f); 523 mBackgroundVisibilityUpdater.onAnimationUpdate(null); 524 background.setPivotX(mBackgroundDimmed.getWidth() / 2f); 525 background.setPivotY(getActualHeight() / 2f); 526 background.setScaleX(DARK_EXIT_SCALE_START); 527 background.setScaleY(DARK_EXIT_SCALE_START); 528 background.animate() 529 .alpha(1f) 530 .scaleX(1f) 531 .scaleY(1f) 532 .setDuration(DARK_ANIMATION_LENGTH) 533 .setStartDelay(delay) 534 .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN) 535 .setListener(new AnimatorListenerAdapter() { 536 @Override 537 public void onAnimationCancel(Animator animation) { 538 // Jump state if we are cancelled 539 background.setScaleX(1f); 540 background.setScaleY(1f); 541 background.setAlpha(1f); 542 } 543 }) 544 .setUpdateListener(mBackgroundVisibilityUpdater) 545 .start(); 546 mFadeInFromDarkAnimator = TimeAnimator.ofFloat(0.0f, 1.0f); 547 mFadeInFromDarkAnimator.setDuration(DARK_ANIMATION_LENGTH); 548 mFadeInFromDarkAnimator.setStartDelay(delay); 549 mFadeInFromDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 550 mFadeInFromDarkAnimator.addListener(mFadeInEndListener); 551 mFadeInFromDarkAnimator.addUpdateListener(mUpdateOutlineListener); 552 mFadeInFromDarkAnimator.start(); 553 } 554 555 /** 556 * Fades the background when the dimmed state changes. 557 */ 558 private void fadeDimmedBackground() { 559 mBackgroundDimmed.animate().cancel(); 560 mBackgroundNormal.animate().cancel(); 561 if (mActivated) { 562 updateBackground(); 563 return; 564 } 565 if (!shouldHideBackground()) { 566 if (mDimmed) { 567 mBackgroundDimmed.setVisibility(View.VISIBLE); 568 } else { 569 mBackgroundNormal.setVisibility(View.VISIBLE); 570 } 571 } 572 float startAlpha = mDimmed ? 1f : 0; 573 float endAlpha = mDimmed ? 0 : 1f; 574 int duration = BACKGROUND_ANIMATION_LENGTH_MS; 575 // Check whether there is already a background animation running. 576 if (mBackgroundAnimator != null) { 577 startAlpha = (Float) mBackgroundAnimator.getAnimatedValue(); 578 duration = (int) mBackgroundAnimator.getCurrentPlayTime(); 579 mBackgroundAnimator.removeAllListeners(); 580 mBackgroundAnimator.cancel(); 581 if (duration <= 0) { 582 updateBackground(); 583 return; 584 } 585 } 586 mBackgroundNormal.setAlpha(startAlpha); 587 mBackgroundAnimator = 588 ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha); 589 mBackgroundAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 590 mBackgroundAnimator.setDuration(duration); 591 mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { 592 @Override 593 public void onAnimationEnd(Animator animation) { 594 updateBackground(); 595 mBackgroundAnimator = null; 596 } 597 }); 598 mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater); 599 mBackgroundAnimator.start(); 600 } 601 602 protected void updateBackgroundAlpha(float transformationAmount) { 603 mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f; 604 if (mDimmedBackgroundFadeInAmount != -1) { 605 mBgAlpha *= mDimmedBackgroundFadeInAmount; 606 } 607 mBackgroundDimmed.setAlpha(mBgAlpha); 608 } 609 610 protected void resetBackgroundAlpha() { 611 updateBackgroundAlpha(0f /* transformationAmount */); 612 } 613 614 protected void updateBackground() { 615 cancelFadeAnimations(); 616 if (shouldHideBackground()) { 617 mBackgroundDimmed.setVisibility(View.INVISIBLE); 618 mBackgroundNormal.setVisibility(View.INVISIBLE); 619 } else if (mDimmed) { 620 // When groups are animating to the expanded state from the lockscreen, show the 621 // normal background instead of the dimmed background 622 final boolean dontShowDimmed = isGroupExpansionChanging() && isChildInGroup(); 623 mBackgroundDimmed.setVisibility(dontShowDimmed ? View.INVISIBLE : View.VISIBLE); 624 mBackgroundNormal.setVisibility((mActivated || dontShowDimmed) 625 ? View.VISIBLE 626 : View.INVISIBLE); 627 } else { 628 mBackgroundDimmed.setVisibility(View.INVISIBLE); 629 mBackgroundNormal.setVisibility(View.VISIBLE); 630 mBackgroundNormal.setAlpha(1f); 631 removeCallbacks(mTapTimeoutRunnable); 632 // make in inactive to avoid it sticking around active 633 makeInactive(false /* animate */); 634 } 635 setNormalBackgroundVisibilityAmount( 636 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); 637 } 638 639 protected boolean shouldHideBackground() { 640 return mDark; 641 } 642 643 private void cancelFadeAnimations() { 644 if (mBackgroundAnimator != null) { 645 mBackgroundAnimator.cancel(); 646 } 647 mBackgroundDimmed.animate().cancel(); 648 mBackgroundNormal.animate().cancel(); 649 } 650 651 @Override 652 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 653 super.onLayout(changed, left, top, right, bottom); 654 setPivotX(getWidth() / 2); 655 } 656 657 @Override 658 public void setActualHeight(int actualHeight, boolean notifyListeners) { 659 super.setActualHeight(actualHeight, notifyListeners); 660 setPivotY(actualHeight / 2); 661 mBackgroundNormal.setActualHeight(actualHeight); 662 mBackgroundDimmed.setActualHeight(actualHeight); 663 } 664 665 @Override 666 public void setClipTopAmount(int clipTopAmount) { 667 super.setClipTopAmount(clipTopAmount); 668 mBackgroundNormal.setClipTopAmount(clipTopAmount); 669 mBackgroundDimmed.setClipTopAmount(clipTopAmount); 670 } 671 672 @Override 673 public void performRemoveAnimation(long duration, float translationDirection, 674 Runnable onFinishedRunnable) { 675 enableAppearDrawing(true); 676 if (mDrawingAppearAnimation) { 677 startAppearAnimation(false /* isAppearing */, translationDirection, 678 0, duration, onFinishedRunnable); 679 } else if (onFinishedRunnable != null) { 680 onFinishedRunnable.run(); 681 } 682 } 683 684 @Override 685 public void performAddAnimation(long delay, long duration) { 686 enableAppearDrawing(true); 687 if (mDrawingAppearAnimation) { 688 startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null); 689 } 690 } 691 692 private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, 693 long duration, final Runnable onFinishedRunnable) { 694 cancelAppearAnimation(); 695 mAnimationTranslationY = translationDirection * getActualHeight(); 696 if (mAppearAnimationFraction == -1.0f) { 697 // not initialized yet, we start anew 698 if (isAppearing) { 699 mAppearAnimationFraction = 0.0f; 700 mAppearAnimationTranslation = mAnimationTranslationY; 701 } else { 702 mAppearAnimationFraction = 1.0f; 703 mAppearAnimationTranslation = 0; 704 } 705 } 706 707 float targetValue; 708 if (isAppearing) { 709 mCurrentAppearInterpolator = mSlowOutFastInInterpolator; 710 mCurrentAlphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN; 711 targetValue = 1.0f; 712 } else { 713 mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN; 714 mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator; 715 targetValue = 0.0f; 716 } 717 mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, 718 targetValue); 719 mAppearAnimator.setInterpolator(Interpolators.LINEAR); 720 mAppearAnimator.setDuration( 721 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); 722 mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 723 @Override 724 public void onAnimationUpdate(ValueAnimator animation) { 725 mAppearAnimationFraction = (float) animation.getAnimatedValue(); 726 updateAppearAnimationAlpha(); 727 updateAppearRect(); 728 invalidate(); 729 } 730 }); 731 if (delay > 0) { 732 // we need to apply the initial state already to avoid drawn frames in the wrong state 733 updateAppearAnimationAlpha(); 734 updateAppearRect(); 735 mAppearAnimator.setStartDelay(delay); 736 } 737 mAppearAnimator.addListener(new AnimatorListenerAdapter() { 738 private boolean mWasCancelled; 739 740 @Override 741 public void onAnimationEnd(Animator animation) { 742 if (onFinishedRunnable != null) { 743 onFinishedRunnable.run(); 744 } 745 if (!mWasCancelled) { 746 enableAppearDrawing(false); 747 onAppearAnimationFinished(isAppearing); 748 } 749 } 750 751 @Override 752 public void onAnimationStart(Animator animation) { 753 mWasCancelled = false; 754 } 755 756 @Override 757 public void onAnimationCancel(Animator animation) { 758 mWasCancelled = true; 759 } 760 }); 761 mAppearAnimator.start(); 762 } 763 764 protected void onAppearAnimationFinished(boolean wasAppearing) { 765 } 766 767 private void cancelAppearAnimation() { 768 if (mAppearAnimator != null) { 769 mAppearAnimator.cancel(); 770 mAppearAnimator = null; 771 } 772 } 773 774 public void cancelAppearDrawing() { 775 cancelAppearAnimation(); 776 enableAppearDrawing(false); 777 } 778 779 private void updateAppearRect() { 780 float inverseFraction = (1.0f - mAppearAnimationFraction); 781 float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction); 782 float translateYTotalAmount = translationFraction * mAnimationTranslationY; 783 mAppearAnimationTranslation = translateYTotalAmount; 784 785 // handle width animation 786 float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START)) 787 / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END); 788 widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction)); 789 widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction); 790 float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) * 791 widthFraction); 792 float right = getWidth() - left; 793 794 // handle top animation 795 float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) / 796 VERTICAL_ANIMATION_START; 797 heightFraction = Math.max(0.0f, heightFraction); 798 heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction); 799 800 float top; 801 float bottom; 802 final int actualHeight = getActualHeight(); 803 if (mAnimationTranslationY > 0.0f) { 804 bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f 805 - translateYTotalAmount; 806 top = bottom * heightFraction; 807 } else { 808 top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f - 809 translateYTotalAmount; 810 bottom = actualHeight * (1 - heightFraction) + top * heightFraction; 811 } 812 mAppearAnimationRect.set(left, top, right, bottom); 813 setOutlineRect(left, top + mAppearAnimationTranslation, right, 814 bottom + mAppearAnimationTranslation); 815 } 816 817 private void updateAppearAnimationAlpha() { 818 float contentAlphaProgress = mAppearAnimationFraction; 819 contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END); 820 contentAlphaProgress = Math.min(1.0f, contentAlphaProgress); 821 contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress); 822 setContentAlpha(contentAlphaProgress); 823 } 824 825 private void setContentAlpha(float contentAlpha) { 826 View contentView = getContentView(); 827 if (contentView.hasOverlappingRendering()) { 828 int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE 829 : LAYER_TYPE_HARDWARE; 830 int currentLayerType = contentView.getLayerType(); 831 if (currentLayerType != layerType) { 832 contentView.setLayerType(layerType, null); 833 } 834 } 835 contentView.setAlpha(contentAlpha); 836 } 837 838 protected abstract View getContentView(); 839 840 public int calculateBgColor() { 841 return calculateBgColor(true /* withTint */); 842 } 843 844 private int calculateBgColor(boolean withTint) { 845 if (withTint && mBgTint != 0) { 846 return mBgTint; 847 } else if (mShowingLegacyBackground) { 848 return mLegacyColor; 849 } else if (mIsBelowSpeedBump) { 850 return mLowPriorityColor; 851 } else { 852 return mNormalColor; 853 } 854 } 855 856 protected int getRippleColor() { 857 if (mBgTint != 0) { 858 return mTintedRippleColor; 859 } else if (mShowingLegacyBackground) { 860 return mTintedRippleColor; 861 } else if (mIsBelowSpeedBump) { 862 return mLowPriorityRippleColor; 863 } else { 864 return mNormalRippleColor; 865 } 866 } 867 868 /** 869 * When we draw the appear animation, we render the view in a bitmap and render this bitmap 870 * as a shader of a rect. This call creates the Bitmap and switches the drawing mode, 871 * such that the normal drawing of the views does not happen anymore. 872 * 873 * @param enable Should it be enabled. 874 */ 875 private void enableAppearDrawing(boolean enable) { 876 if (enable != mDrawingAppearAnimation) { 877 mDrawingAppearAnimation = enable; 878 if (!enable) { 879 setContentAlpha(1.0f); 880 mAppearAnimationFraction = -1; 881 setOutlineRect(null); 882 } 883 invalidate(); 884 } 885 } 886 887 @Override 888 protected void dispatchDraw(Canvas canvas) { 889 if (mDrawingAppearAnimation) { 890 canvas.save(); 891 canvas.translate(0, mAppearAnimationTranslation); 892 } 893 super.dispatchDraw(canvas); 894 if (mDrawingAppearAnimation) { 895 canvas.restore(); 896 } 897 } 898 899 public void setOnActivatedListener(OnActivatedListener onActivatedListener) { 900 mOnActivatedListener = onActivatedListener; 901 } 902 903 public void reset() { 904 setTintColor(0); 905 resetBackgroundAlpha(); 906 setShowingLegacyBackground(false); 907 setBelowSpeedBump(false); 908 } 909 910 public boolean hasSameBgColor(ActivatableNotificationView otherView) { 911 return calculateBgColor() == otherView.calculateBgColor(); 912 } 913 914 @Override 915 public float getShadowAlpha() { 916 return mShadowAlpha; 917 } 918 919 @Override 920 public void setShadowAlpha(float shadowAlpha) { 921 if (shadowAlpha != mShadowAlpha) { 922 mShadowAlpha = shadowAlpha; 923 updateOutlineAlpha(); 924 } 925 } 926 927 @Override 928 public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, 929 int outlineTranslation) { 930 mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ() 931 + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd, 932 outlineTranslation); 933 } 934 935 public int getBackgroundColorWithoutTint() { 936 return calculateBgColor(false /* withTint */); 937 } 938 939 public interface OnActivatedListener { 940 void onActivated(ActivatableNotificationView view); 941 void onActivationReset(ActivatableNotificationView view); 942 } 943 } 944