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