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