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