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