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.notification.row; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.content.Context; 23 import android.graphics.Canvas; 24 import android.graphics.Point; 25 import android.util.AttributeSet; 26 import android.util.MathUtils; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.accessibility.AccessibilityManager; 30 import android.view.animation.Interpolator; 31 import android.view.animation.PathInterpolator; 32 33 import com.android.internal.jank.InteractionJankMonitor; 34 import com.android.internal.jank.InteractionJankMonitor.Configuration; 35 import com.android.settingslib.Utils; 36 import com.android.systemui.Gefingerpoken; 37 import com.android.systemui.R; 38 import com.android.systemui.animation.Interpolators; 39 import com.android.systemui.statusbar.NotificationShelf; 40 import com.android.systemui.statusbar.notification.FakeShadowView; 41 import com.android.systemui.statusbar.notification.NotificationUtils; 42 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; 43 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 44 45 /** 46 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf} 47 * to implement dimming/activating on Keyguard for the double-tap gesture 48 */ 49 public abstract class ActivatableNotificationView extends ExpandableOutlineView { 50 51 /** 52 * The amount of width, which is kept in the end when performing a disappear animation (also 53 * the amount from which the horizontal appearing begins) 54 */ 55 private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f; 56 57 /** 58 * At which point from [0,1] does the horizontal collapse animation end (or start when 59 * expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated. 60 */ 61 private static final float HORIZONTAL_ANIMATION_END = 0.2f; 62 63 /** 64 * At which point from [0,1] does the horizontal collapse animation start (or start when 65 * expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all. 66 */ 67 private static final float HORIZONTAL_ANIMATION_START = 1.0f; 68 69 /** 70 * At which point from [0,1] does the vertical collapse animation start (or end when 71 * expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all. 72 */ 73 private static final float VERTICAL_ANIMATION_START = 1.0f; 74 75 /** 76 * A sentinel value when no color should be used. Can be used with {@link #setTintColor(int)} 77 * or {@link #setOverrideTintColor(int, float)}. 78 */ 79 protected static final int NO_COLOR = 0; 80 /** 81 * The content of the view should start showing at animation progress value of 82 * #ALPHA_APPEAR_START_FRACTION. 83 */ 84 private static final float ALPHA_APPEAR_START_FRACTION = .4f; 85 /** 86 * The content should show fully with progress at #ALPHA_APPEAR_END_FRACTION 87 * The start of the animation is at #ALPHA_APPEAR_START_FRACTION 88 */ 89 private static final float ALPHA_APPEAR_END_FRACTION = 1; 90 private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR 91 = new PathInterpolator(0.6f, 0, 0.5f, 1); 92 private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR 93 = new PathInterpolator(0, 0, 0.5f, 1); 94 private int mTintedRippleColor; 95 private int mNormalRippleColor; 96 private Gefingerpoken mTouchHandler; 97 98 int mBgTint = NO_COLOR; 99 100 /** 101 * Flag to indicate that the notification has been touched once and the second touch will 102 * click it. 103 */ 104 private boolean mActivated; 105 106 private OnActivatedListener mOnActivatedListener; 107 108 private final Interpolator mSlowOutFastInInterpolator; 109 private final Interpolator mSlowOutLinearInInterpolator; 110 private Interpolator mCurrentAppearInterpolator; 111 112 NotificationBackgroundView mBackgroundNormal; 113 private float mAnimationTranslationY; 114 private boolean mDrawingAppearAnimation; 115 private ValueAnimator mAppearAnimator; 116 private ValueAnimator mBackgroundColorAnimator; 117 private float mAppearAnimationFraction = -1.0f; 118 private float mAppearAnimationTranslation; 119 private int mNormalColor; 120 private boolean mIsBelowSpeedBump; 121 private long mLastActionUpTime; 122 123 private float mNormalBackgroundVisibilityAmount; 124 private FakeShadowView mFakeShadow; 125 private int mCurrentBackgroundTint; 126 private int mTargetTint; 127 private int mStartTint; 128 private int mOverrideTint; 129 private float mOverrideAmount; 130 private boolean mShadowHidden; 131 private boolean mIsHeadsUpAnimation; 132 /* In order to track headsup longpress coorindate. */ 133 protected Point mTargetPoint; 134 private boolean mDismissed; 135 private boolean mRefocusOnDismiss; 136 private AccessibilityManager mAccessibilityManager; 137 ActivatableNotificationView(Context context, AttributeSet attrs)138 public ActivatableNotificationView(Context context, AttributeSet attrs) { 139 super(context, attrs); 140 mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f); 141 mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f); 142 setClipChildren(false); 143 setClipToPadding(false); 144 updateColors(); 145 } 146 updateColors()147 private void updateColors() { 148 mNormalColor = Utils.getColorAttrDefaultColor(mContext, 149 com.android.internal.R.attr.colorSurface); 150 mTintedRippleColor = mContext.getColor( 151 R.color.notification_ripple_tinted_color); 152 mNormalRippleColor = mContext.getColor( 153 R.color.notification_ripple_untinted_color); 154 } 155 156 /** 157 * Reload background colors from resources and invalidate views. 158 */ updateBackgroundColors()159 public void updateBackgroundColors() { 160 updateColors(); 161 initBackground(); 162 updateBackgroundTint(); 163 } 164 165 @Override onFinishInflate()166 protected void onFinishInflate() { 167 super.onFinishInflate(); 168 mBackgroundNormal = findViewById(R.id.backgroundNormal); 169 mFakeShadow = findViewById(R.id.fake_shadow); 170 mShadowHidden = mFakeShadow.getVisibility() != VISIBLE; 171 initBackground(); 172 updateBackgroundTint(); 173 updateOutlineAlpha(); 174 } 175 176 /** 177 * Sets the custom background on {@link #mBackgroundNormal} 178 * This method can also be used to reload the backgrounds on both of those views, which can 179 * be useful in a configuration change. 180 */ initBackground()181 protected void initBackground() { 182 mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg); 183 } 184 hideBackground()185 protected boolean hideBackground() { 186 return false; 187 } 188 updateBackground()189 protected void updateBackground() { 190 mBackgroundNormal.setVisibility(hideBackground() ? INVISIBLE : VISIBLE); 191 } 192 193 194 @Override onInterceptTouchEvent(MotionEvent ev)195 public boolean onInterceptTouchEvent(MotionEvent ev) { 196 if (mTouchHandler != null && mTouchHandler.onInterceptTouchEvent(ev)) { 197 return true; 198 } 199 return super.onInterceptTouchEvent(ev); 200 } 201 202 /** 203 * Called by the TouchHandler when this view is tapped. This will be called for actual taps 204 * only, i.e. taps that have been filtered by the FalsingManager. 205 */ onTap()206 public void onTap() {} 207 208 /** Sets the last action up time this view was touched. */ setLastActionUpTime(long eventTime)209 void setLastActionUpTime(long eventTime) { 210 mLastActionUpTime = eventTime; 211 } 212 213 /** 214 * Returns the last action up time. The last time will also be cleared because the source of 215 * action is not only from touch event. That prevents the caller from utilizing the time with 216 * unrelated event. The time can be 0 if the event is unavailable. 217 */ getAndResetLastActionUpTime()218 public long getAndResetLastActionUpTime() { 219 long lastActionUpTime = mLastActionUpTime; 220 mLastActionUpTime = 0; 221 return lastActionUpTime; 222 } 223 disallowSingleClick(MotionEvent ev)224 protected boolean disallowSingleClick(MotionEvent ev) { 225 return false; 226 } 227 handleSlideBack()228 protected boolean handleSlideBack() { 229 return false; 230 } 231 232 /** 233 * @return whether this view is interactive and can be double tapped 234 */ isInteractive()235 protected boolean isInteractive() { 236 return true; 237 } 238 239 @Override drawableStateChanged()240 protected void drawableStateChanged() { 241 super.drawableStateChanged(); 242 mBackgroundNormal.setState(getDrawableState()); 243 } 244 setRippleAllowed(boolean allowed)245 void setRippleAllowed(boolean allowed) { 246 mBackgroundNormal.setPressedAllowed(allowed); 247 } 248 makeActive()249 void makeActive() { 250 mActivated = true; 251 if (mOnActivatedListener != null) { 252 mOnActivatedListener.onActivated(this); 253 } 254 } 255 isActive()256 public boolean isActive() { 257 return mActivated; 258 } 259 260 /** 261 * Cancels the hotspot and makes the notification inactive. 262 */ makeInactive(boolean animate)263 public void makeInactive(boolean animate) { 264 if (mActivated) { 265 mActivated = false; 266 } 267 if (mOnActivatedListener != null) { 268 mOnActivatedListener.onActivationReset(this); 269 } 270 } 271 updateOutlineAlpha()272 private void updateOutlineAlpha() { 273 float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED; 274 alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount); 275 setOutlineAlpha(alpha); 276 } 277 setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount)278 private void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) { 279 mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount; 280 updateOutlineAlpha(); 281 } 282 283 @Override setBelowSpeedBump(boolean below)284 public void setBelowSpeedBump(boolean below) { 285 super.setBelowSpeedBump(below); 286 if (below != mIsBelowSpeedBump) { 287 mIsBelowSpeedBump = below; 288 updateBackgroundTint(); 289 onBelowSpeedBumpChanged(); 290 } 291 } 292 onBelowSpeedBumpChanged()293 protected void onBelowSpeedBumpChanged() { 294 } 295 296 /** 297 * @return whether we are below the speed bump 298 */ isBelowSpeedBump()299 public boolean isBelowSpeedBump() { 300 return mIsBelowSpeedBump; 301 } 302 303 /** 304 * Sets the tint color of the background 305 */ setTintColor(int color)306 protected void setTintColor(int color) { 307 setTintColor(color, false); 308 } 309 310 /** 311 * Sets the tint color of the background 312 */ setTintColor(int color, boolean animated)313 void setTintColor(int color, boolean animated) { 314 if (color != mBgTint) { 315 mBgTint = color; 316 updateBackgroundTint(animated); 317 } 318 } 319 320 /** 321 * Set an override tint color that is used for the background. 322 * 323 * @param color the color that should be used to tint the background. 324 * This can be {@link #NO_COLOR} if the tint should be normally computed. 325 * @param overrideAmount a value from 0 to 1 how much the override tint should be used. The 326 * background color will then be the interpolation between this and the 327 * regular background color, where 1 means the overrideTintColor is fully 328 * used and the background color not at all. 329 */ setOverrideTintColor(int color, float overrideAmount)330 public void setOverrideTintColor(int color, float overrideAmount) { 331 mOverrideTint = color; 332 mOverrideAmount = overrideAmount; 333 int newColor = calculateBgColor(); 334 setBackgroundTintColor(newColor); 335 } 336 updateBackgroundTint()337 protected void updateBackgroundTint() { 338 updateBackgroundTint(false /* animated */); 339 } 340 updateBackgroundTint(boolean animated)341 private void updateBackgroundTint(boolean animated) { 342 if (mBackgroundColorAnimator != null) { 343 mBackgroundColorAnimator.cancel(); 344 } 345 int rippleColor = getRippleColor(); 346 mBackgroundNormal.setRippleColor(rippleColor); 347 int color = calculateBgColor(); 348 if (!animated) { 349 setBackgroundTintColor(color); 350 } else if (color != mCurrentBackgroundTint) { 351 mStartTint = mCurrentBackgroundTint; 352 mTargetTint = color; 353 mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); 354 mBackgroundColorAnimator.addUpdateListener(animation -> { 355 int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint, 356 animation.getAnimatedFraction()); 357 setBackgroundTintColor(newColor); 358 }); 359 mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 360 mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); 361 mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() { 362 @Override 363 public void onAnimationEnd(Animator animation) { 364 mBackgroundColorAnimator = null; 365 } 366 }); 367 mBackgroundColorAnimator.start(); 368 } 369 } 370 setBackgroundTintColor(int color)371 protected void setBackgroundTintColor(int color) { 372 if (color != mCurrentBackgroundTint) { 373 mCurrentBackgroundTint = color; 374 if (color == mNormalColor) { 375 // We don't need to tint a normal notification 376 color = 0; 377 } 378 mBackgroundNormal.setTint(color); 379 } 380 } 381 updateBackgroundClipping()382 protected void updateBackgroundClipping() { 383 mBackgroundNormal.setBottomAmountClips(!isChildInGroup()); 384 } 385 386 @Override onLayout(boolean changed, int left, int top, int right, int bottom)387 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 388 super.onLayout(changed, left, top, right, bottom); 389 setPivotX(getWidth() / 2); 390 } 391 392 @Override setActualHeight(int actualHeight, boolean notifyListeners)393 public void setActualHeight(int actualHeight, boolean notifyListeners) { 394 super.setActualHeight(actualHeight, notifyListeners); 395 setPivotY(actualHeight / 2); 396 mBackgroundNormal.setActualHeight(actualHeight); 397 } 398 399 @Override setClipTopAmount(int clipTopAmount)400 public void setClipTopAmount(int clipTopAmount) { 401 super.setClipTopAmount(clipTopAmount); 402 mBackgroundNormal.setClipTopAmount(clipTopAmount); 403 } 404 405 @Override setClipBottomAmount(int clipBottomAmount)406 public void setClipBottomAmount(int clipBottomAmount) { 407 super.setClipBottomAmount(clipBottomAmount); 408 mBackgroundNormal.setClipBottomAmount(clipBottomAmount); 409 } 410 411 @Override performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)412 public long performRemoveAnimation(long duration, long delay, 413 float translationDirection, boolean isHeadsUpAnimation, float endLocation, 414 Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { 415 enableAppearDrawing(true); 416 mIsHeadsUpAnimation = isHeadsUpAnimation; 417 if (mDrawingAppearAnimation) { 418 startAppearAnimation(false /* isAppearing */, translationDirection, 419 delay, duration, onFinishedRunnable, animationListener); 420 } else if (onFinishedRunnable != null) { 421 onFinishedRunnable.run(); 422 } 423 return 0; 424 } 425 426 @Override performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)427 public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { 428 enableAppearDrawing(true); 429 mIsHeadsUpAnimation = isHeadsUpAppear; 430 if (mDrawingAppearAnimation) { 431 startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, 432 duration, null, null); 433 } 434 } 435 startAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, final Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)436 private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, 437 long duration, final Runnable onFinishedRunnable, 438 AnimatorListenerAdapter animationListener) { 439 mAnimationTranslationY = translationDirection * getActualHeight(); 440 cancelAppearAnimation(); 441 if (mAppearAnimationFraction == -1.0f) { 442 // not initialized yet, we start anew 443 if (isAppearing) { 444 mAppearAnimationFraction = 0.0f; 445 mAppearAnimationTranslation = mAnimationTranslationY; 446 } else { 447 mAppearAnimationFraction = 1.0f; 448 mAppearAnimationTranslation = 0; 449 } 450 } 451 452 float targetValue; 453 if (isAppearing) { 454 mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN; 455 targetValue = 1.0f; 456 } else { 457 mCurrentAppearInterpolator = mSlowOutFastInInterpolator; 458 targetValue = 0.0f; 459 } 460 mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, 461 targetValue); 462 mAppearAnimator.setInterpolator(Interpolators.LINEAR); 463 mAppearAnimator.setDuration( 464 (long) (duration * Math.abs(mAppearAnimationFraction - targetValue))); 465 mAppearAnimator.addUpdateListener(animation -> { 466 mAppearAnimationFraction = (float) animation.getAnimatedValue(); 467 updateAppearAnimationAlpha(); 468 updateAppearRect(); 469 invalidate(); 470 }); 471 if (animationListener != null) { 472 mAppearAnimator.addListener(animationListener); 473 } 474 if (delay > 0) { 475 // we need to apply the initial state already to avoid drawn frames in the wrong state 476 updateAppearAnimationAlpha(); 477 updateAppearRect(); 478 mAppearAnimator.setStartDelay(delay); 479 } 480 mAppearAnimator.addListener(new AnimatorListenerAdapter() { 481 private boolean mWasCancelled; 482 483 @Override 484 public void onAnimationEnd(Animator animation) { 485 if (onFinishedRunnable != null) { 486 onFinishedRunnable.run(); 487 } 488 if (!mWasCancelled) { 489 enableAppearDrawing(false); 490 onAppearAnimationFinished(isAppearing); 491 InteractionJankMonitor.getInstance().end(getCujType(isAppearing)); 492 } else { 493 InteractionJankMonitor.getInstance().cancel(getCujType(isAppearing)); 494 } 495 } 496 497 @Override 498 public void onAnimationStart(Animator animation) { 499 mWasCancelled = false; 500 Configuration.Builder builder = Configuration.Builder 501 .withView(getCujType(isAppearing), ActivatableNotificationView.this); 502 InteractionJankMonitor.getInstance().begin(builder); 503 } 504 505 @Override 506 public void onAnimationCancel(Animator animation) { 507 mWasCancelled = true; 508 } 509 }); 510 mAppearAnimator.start(); 511 } 512 getCujType(boolean isAppearing)513 private int getCujType(boolean isAppearing) { 514 if (mIsHeadsUpAnimation) { 515 return isAppearing 516 ? InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR 517 : InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR; 518 } else { 519 return isAppearing 520 ? InteractionJankMonitor.CUJ_NOTIFICATION_ADD 521 : InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE; 522 } 523 } 524 onAppearAnimationFinished(boolean wasAppearing)525 protected void onAppearAnimationFinished(boolean wasAppearing) { 526 } 527 cancelAppearAnimation()528 private void cancelAppearAnimation() { 529 if (mAppearAnimator != null) { 530 mAppearAnimator.cancel(); 531 mAppearAnimator = null; 532 } 533 } 534 cancelAppearDrawing()535 public void cancelAppearDrawing() { 536 cancelAppearAnimation(); 537 enableAppearDrawing(false); 538 } 539 updateAppearRect()540 private void updateAppearRect() { 541 float interpolatedFraction = mCurrentAppearInterpolator.getInterpolation( 542 mAppearAnimationFraction); 543 mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY; 544 final int actualHeight = getActualHeight(); 545 float bottom = actualHeight * interpolatedFraction; 546 547 if (mTargetPoint != null) { 548 int width = getWidth(); 549 float fraction = 1 - mAppearAnimationFraction; 550 551 setOutlineRect(mTargetPoint.x * fraction, 552 mAnimationTranslationY 553 + (mAnimationTranslationY - mTargetPoint.y) * fraction, 554 width - (width - mTargetPoint.x) * fraction, 555 actualHeight - (actualHeight - mTargetPoint.y) * fraction); 556 } else { 557 setOutlineRect(0, mAppearAnimationTranslation, getWidth(), 558 bottom + mAppearAnimationTranslation); 559 } 560 } 561 getInterpolatedAppearAnimationFraction()562 private float getInterpolatedAppearAnimationFraction() { 563 if (mAppearAnimationFraction >= 0) { 564 return mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction); 565 } 566 return 1.0f; 567 } 568 updateAppearAnimationAlpha()569 private void updateAppearAnimationAlpha() { 570 float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction, 571 ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION); 572 float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION; 573 float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range; 574 setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha)); 575 } 576 setContentAlpha(float contentAlpha)577 private void setContentAlpha(float contentAlpha) { 578 View contentView = getContentView(); 579 if (contentView.hasOverlappingRendering()) { 580 int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE 581 : LAYER_TYPE_HARDWARE; 582 contentView.setLayerType(layerType, null); 583 } 584 contentView.setAlpha(contentAlpha); 585 // After updating the current view, reset all views. 586 if (contentAlpha == 1f) { 587 resetAllContentAlphas(); 588 } 589 } 590 591 /** 592 * If a subclass's {@link #getContentView()} returns different views depending on state, 593 * this method is an opportunity to reset the alpha of ALL content views, not just the 594 * current one, which may prevent a content view that is temporarily hidden from being reset. 595 * 596 * This should setAlpha(1.0f) and setLayerType(LAYER_TYPE_NONE) for all content views. 597 */ resetAllContentAlphas()598 protected void resetAllContentAlphas() {} 599 600 @Override applyRoundness()601 protected void applyRoundness() { 602 super.applyRoundness(); 603 applyBackgroundRoundness(getCurrentBackgroundRadiusTop(), 604 getCurrentBackgroundRadiusBottom()); 605 } 606 607 @Override getCurrentBackgroundRadiusTop()608 public float getCurrentBackgroundRadiusTop() { 609 float fraction = getInterpolatedAppearAnimationFraction(); 610 return MathUtils.lerp(0, super.getCurrentBackgroundRadiusTop(), fraction); 611 } 612 613 @Override getCurrentBackgroundRadiusBottom()614 public float getCurrentBackgroundRadiusBottom() { 615 float fraction = getInterpolatedAppearAnimationFraction(); 616 return MathUtils.lerp(0, super.getCurrentBackgroundRadiusBottom(), fraction); 617 } 618 applyBackgroundRoundness(float topRadius, float bottomRadius)619 private void applyBackgroundRoundness(float topRadius, float bottomRadius) { 620 mBackgroundNormal.setRadius(topRadius, bottomRadius); 621 } 622 623 @Override setBackgroundTop(int backgroundTop)624 protected void setBackgroundTop(int backgroundTop) { 625 mBackgroundNormal.setBackgroundTop(backgroundTop); 626 } 627 getContentView()628 protected abstract View getContentView(); 629 calculateBgColor()630 public int calculateBgColor() { 631 return calculateBgColor(true /* withTint */, true /* withOverRide */); 632 } 633 634 @Override childNeedsClipping(View child)635 protected boolean childNeedsClipping(View child) { 636 if (child instanceof NotificationBackgroundView && isClippingNeeded()) { 637 return true; 638 } 639 return super.childNeedsClipping(child); 640 } 641 642 /** 643 * @param withTint should a possible tint be factored in? 644 * @param withOverride should the value be interpolated with {@link #mOverrideTint} 645 * @return the calculated background color 646 */ calculateBgColor(boolean withTint, boolean withOverride)647 private int calculateBgColor(boolean withTint, boolean withOverride) { 648 if (withOverride && mOverrideTint != NO_COLOR) { 649 int defaultTint = calculateBgColor(withTint, false); 650 return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount); 651 } 652 if (withTint && mBgTint != NO_COLOR) { 653 return mBgTint; 654 } else { 655 return mNormalColor; 656 } 657 } 658 getRippleColor()659 private int getRippleColor() { 660 if (mBgTint != 0) { 661 return mTintedRippleColor; 662 } else { 663 return mNormalRippleColor; 664 } 665 } 666 667 /** 668 * When we draw the appear animation, we render the view in a bitmap and render this bitmap 669 * as a shader of a rect. This call creates the Bitmap and switches the drawing mode, 670 * such that the normal drawing of the views does not happen anymore. 671 * 672 * @param enable Should it be enabled. 673 */ enableAppearDrawing(boolean enable)674 private void enableAppearDrawing(boolean enable) { 675 if (enable != mDrawingAppearAnimation) { 676 mDrawingAppearAnimation = enable; 677 if (!enable) { 678 setContentAlpha(1.0f); 679 mAppearAnimationFraction = -1; 680 setOutlineRect(null); 681 } 682 invalidate(); 683 } 684 } 685 isDrawingAppearAnimation()686 public boolean isDrawingAppearAnimation() { 687 return mDrawingAppearAnimation; 688 } 689 690 @Override dispatchDraw(Canvas canvas)691 protected void dispatchDraw(Canvas canvas) { 692 if (mDrawingAppearAnimation) { 693 canvas.save(); 694 canvas.translate(0, mAppearAnimationTranslation); 695 } 696 super.dispatchDraw(canvas); 697 if (mDrawingAppearAnimation) { 698 canvas.restore(); 699 } 700 } 701 setOnActivatedListener(OnActivatedListener onActivatedListener)702 public void setOnActivatedListener(OnActivatedListener onActivatedListener) { 703 mOnActivatedListener = onActivatedListener; 704 } 705 706 @Override setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation)707 public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, 708 int outlineTranslation) { 709 boolean hiddenBefore = mShadowHidden; 710 mShadowHidden = shadowIntensity == 0.0f; 711 if (!mShadowHidden || !hiddenBefore) { 712 mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ() 713 + FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd, 714 outlineTranslation); 715 } 716 } 717 getBackgroundColorWithoutTint()718 public int getBackgroundColorWithoutTint() { 719 return calculateBgColor(false /* withTint */, false /* withOverride */); 720 } 721 getCurrentBackgroundTint()722 public int getCurrentBackgroundTint() { 723 return mCurrentBackgroundTint; 724 } 725 isHeadsUp()726 public boolean isHeadsUp() { 727 return false; 728 } 729 730 @Override getHeadsUpHeightWithoutHeader()731 public int getHeadsUpHeightWithoutHeader() { 732 return getHeight(); 733 } 734 735 /** Mark that this view has been dismissed. */ dismiss(boolean refocusOnDismiss)736 public void dismiss(boolean refocusOnDismiss) { 737 mDismissed = true; 738 mRefocusOnDismiss = refocusOnDismiss; 739 } 740 741 /** Mark that this view is no longer dismissed. */ unDismiss()742 public void unDismiss() { 743 mDismissed = false; 744 } 745 746 /** Is this view marked as dismissed? */ isDismissed()747 public boolean isDismissed() { 748 return mDismissed; 749 } 750 751 /** Should a re-focus occur upon dismissing this view? */ shouldRefocusOnDismiss()752 public boolean shouldRefocusOnDismiss() { 753 return mRefocusOnDismiss || isAccessibilityFocused(); 754 } 755 setTouchHandler(Gefingerpoken touchHandler)756 void setTouchHandler(Gefingerpoken touchHandler) { 757 mTouchHandler = touchHandler; 758 } 759 setAccessibilityManager(AccessibilityManager accessibilityManager)760 public void setAccessibilityManager(AccessibilityManager accessibilityManager) { 761 mAccessibilityManager = accessibilityManager; 762 } 763 764 public interface OnActivatedListener { onActivated(ActivatableNotificationView view)765 void onActivated(ActivatableNotificationView view); onActivationReset(ActivatableNotificationView view)766 void onActivationReset(ActivatableNotificationView view); 767 } 768 } 769