blob: bc4654823f777b17d3612ce686aa18d7c05d5578 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.statusbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.TimeAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewConfiguration;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
* to implement dimming/activating on Keyguard for the double-tap gesture
*/
public abstract class ActivatableNotificationView extends ExpandableOutlineView {
private static final long DOUBLETAP_TIMEOUT_MS = 1200;
private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
private static final int ACTIVATE_ANIMATION_LENGTH = 220;
private static final int DARK_ANIMATION_LENGTH = 170;
/**
* The amount of width, which is kept in the end when performing a disappear animation (also
* the amount from which the horizontal appearing begins)
*/
private static final float HORIZONTAL_COLLAPSED_REST_PARTIAL = 0.05f;
/**
* At which point from [0,1] does the horizontal collapse animation end (or start when
* expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
*/
private static final float HORIZONTAL_ANIMATION_END = 0.2f;
/**
* At which point from [0,1] does the alpha animation end (or start when
* expanding)? 1.0 meaning that it ends immediately and 0.0 that it is continuously animated.
*/
private static final float ALPHA_ANIMATION_END = 0.0f;
/**
* At which point from [0,1] does the horizontal collapse animation start (or start when
* expanding)? 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
*/
private static final float HORIZONTAL_ANIMATION_START = 1.0f;
/**
* At which point from [0,1] does the vertical collapse animation start (or end when
* expanding) 1.0 meaning that it starts immediately and 0.0 that it is animated at all.
*/
private static final float VERTICAL_ANIMATION_START = 1.0f;
/**
* Scale for the background to animate from when exiting dark mode.
*/
private static final float DARK_EXIT_SCALE_START = 0.93f;
private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR
= new PathInterpolator(0.6f, 0, 0.5f, 1);
private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
= new PathInterpolator(0, 0, 0.5f, 1);
private final int mTintedRippleColor;
private final int mLowPriorityRippleColor;
protected final int mNormalRippleColor;
private boolean mDimmed;
private boolean mDark;
private int mBgTint = 0;
private float mBgAlpha = 1f;
/**
* Flag to indicate that the notification has been touched once and the second touch will
* click it.
*/
private boolean mActivated;
private float mDownX;
private float mDownY;
private final float mTouchSlop;
private OnActivatedListener mOnActivatedListener;
private final Interpolator mSlowOutFastInInterpolator;
private final Interpolator mSlowOutLinearInInterpolator;
private Interpolator mCurrentAppearInterpolator;
private Interpolator mCurrentAlphaInterpolator;
private NotificationBackgroundView mBackgroundNormal;
private NotificationBackgroundView mBackgroundDimmed;
private ObjectAnimator mBackgroundAnimator;
private RectF mAppearAnimationRect = new RectF();
private float mAnimationTranslationY;
private boolean mDrawingAppearAnimation;
private ValueAnimator mAppearAnimator;
private ValueAnimator mBackgroundColorAnimator;
private float mAppearAnimationFraction = -1.0f;
private float mAppearAnimationTranslation;
private boolean mShowingLegacyBackground;
private final int mLegacyColor;
private final int mNormalColor;
private final int mLowPriorityColor;
private boolean mIsBelowSpeedBump;
private FalsingManager mFalsingManager;
private boolean mTrackTouch;
private float mNormalBackgroundVisibilityAmount;
private ValueAnimator mFadeInFromDarkAnimator;
private float mDimmedBackgroundFadeInAmount = -1;
private ValueAnimator.AnimatorUpdateListener mBackgroundVisibilityUpdater
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setNormalBackgroundVisibilityAmount(mBackgroundNormal.getAlpha());
mDimmedBackgroundFadeInAmount = mBackgroundDimmed.getAlpha();
}
};
private AnimatorListenerAdapter mFadeInEndListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mFadeInFromDarkAnimator = null;
mDimmedBackgroundFadeInAmount = -1;
updateBackground();
}
};
private ValueAnimator.AnimatorUpdateListener mUpdateOutlineListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updateOutlineAlpha();
}
};
private float mShadowAlpha = 1.0f;
private FakeShadowView mFakeShadow;
private int mCurrentBackgroundTint;
private int mTargetTint;
private int mStartTint;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
setClipChildren(false);
setClipToPadding(false);
mLegacyColor = context.getColor(R.color.notification_legacy_background_color);
mNormalColor = context.getColor(R.color.notification_material_background_color);
mLowPriorityColor = context.getColor(
R.color.notification_material_background_low_priority_color);
mTintedRippleColor = context.getColor(
R.color.notification_ripple_tinted_color);
mLowPriorityRippleColor = context.getColor(
R.color.notification_ripple_color_low_priority);
mNormalRippleColor = context.getColor(
R.color.notification_ripple_untinted_color);
mFalsingManager = FalsingManager.getInstance(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal);
mFakeShadow = (FakeShadowView) findViewById(R.id.fake_shadow);
mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
updateBackground();
updateBackgroundTint();
updateOutlineAlpha();
}
private final Runnable mTapTimeoutRunnable = new Runnable() {
@Override
public void run() {
makeInactive(true /* animate */);
}
};
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mDimmed && !mActivated
&& ev.getActionMasked() == MotionEvent.ACTION_DOWN && disallowSingleClick(ev)) {
return true;
}
return super.onInterceptTouchEvent(ev);
}
protected boolean disallowSingleClick(MotionEvent ev) {
return false;
}
protected boolean handleSlideBack() {
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result;
if (mDimmed) {
boolean wasActivated = mActivated;
result = handleTouchEventDimmed(event);
if (wasActivated && result && event.getAction() == MotionEvent.ACTION_UP) {
mFalsingManager.onNotificationDoubleTap();
removeCallbacks(mTapTimeoutRunnable);
}
} else {
result = super.onTouchEvent(event);
}
return result;
}
@Override
public void drawableHotspotChanged(float x, float y) {
if (!mDimmed){
mBackgroundNormal.drawableHotspotChanged(x, y);
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mDimmed) {
mBackgroundDimmed.setState(getDrawableState());
} else {
mBackgroundNormal.setState(getDrawableState());
}
}
private boolean handleTouchEventDimmed(MotionEvent event) {
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
mTrackTouch = true;
if (mDownY > getActualHeight()) {
mTrackTouch = false;
}
break;
case MotionEvent.ACTION_MOVE:
if (!isWithinTouchSlop(event)) {
makeInactive(true /* animate */);
mTrackTouch = false;
}
break;
case MotionEvent.ACTION_UP:
if (isWithinTouchSlop(event)) {
if (handleSlideBack()) {
return true;
}
if (!mActivated) {
makeActive();
postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
} else {
if (!performClick()) {
return false;
}
}
} else {
makeInactive(true /* animate */);
mTrackTouch = false;
}
break;
case MotionEvent.ACTION_CANCEL:
makeInactive(true /* animate */);
mTrackTouch = false;
break;
default:
break;
}
return mTrackTouch;
}
private void makeActive() {
mFalsingManager.onNotificationActive();
startActivateAnimation(false /* reverse */);
mActivated = true;
if (mOnActivatedListener != null) {
mOnActivatedListener.onActivated(this);
}
}
private void startActivateAnimation(final boolean reverse) {
if (!isAttachedToWindow()) {
return;
}
int widthHalf = mBackgroundNormal.getWidth()/2;
int heightHalf = mBackgroundNormal.getActualHeight()/2;
float radius = (float) Math.sqrt(widthHalf*widthHalf + heightHalf*heightHalf);
Animator animator;
if (reverse) {
animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
widthHalf, heightHalf, radius, 0);
} else {
animator = ViewAnimationUtils.createCircularReveal(mBackgroundNormal,
widthHalf, heightHalf, 0, radius);
}
mBackgroundNormal.setVisibility(View.VISIBLE);
Interpolator interpolator;
Interpolator alphaInterpolator;
if (!reverse) {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
alphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
} else {
interpolator = ACTIVATE_INVERSE_INTERPOLATOR;
alphaInterpolator = ACTIVATE_INVERSE_ALPHA_INTERPOLATOR;
}
animator.setInterpolator(interpolator);
animator.setDuration(ACTIVATE_ANIMATION_LENGTH);
if (reverse) {
mBackgroundNormal.setAlpha(1f);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateBackground();
}
});
animator.start();
} else {
mBackgroundNormal.setAlpha(0.4f);
animator.start();
}
mBackgroundNormal.animate()
.alpha(reverse ? 0f : 1f)
.setInterpolator(alphaInterpolator)
.setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedFraction = animation.getAnimatedFraction();
if (reverse) {
animatedFraction = 1.0f - animatedFraction;
}
setNormalBackgroundVisibilityAmount(animatedFraction);
}
})
.setDuration(ACTIVATE_ANIMATION_LENGTH);
}
/**
* Cancels the hotspot and makes the notification inactive.
*/
public void makeInactive(boolean animate) {
if (mActivated) {
mActivated = false;
if (mDimmed) {
if (animate) {
startActivateAnimation(true /* reverse */);
} else {
updateBackground();
}
}
}
if (mOnActivatedListener != null) {
mOnActivatedListener.onActivationReset(this);
}
removeCallbacks(mTapTimeoutRunnable);
}
private boolean isWithinTouchSlop(MotionEvent event) {
return Math.abs(event.getX() - mDownX) < mTouchSlop
&& Math.abs(event.getY() - mDownY) < mTouchSlop;
}
public void setDimmed(boolean dimmed, boolean fade) {
if (mDimmed != dimmed) {
mDimmed = dimmed;
resetBackgroundAlpha();
if (fade) {
fadeDimmedBackground();
} else {
updateBackground();
}
}
}
public void setDark(boolean dark, boolean fade, long delay) {
super.setDark(dark, fade, delay);
if (mDark == dark) {
return;
}
mDark = dark;
updateBackground();
if (!dark && fade && !shouldHideBackground()) {
fadeInFromDark(delay);
}
updateOutlineAlpha();
}
private void updateOutlineAlpha() {
if (mDark) {
setOutlineAlpha(0f);
return;
}
float alpha = NotificationStackScrollLayout.BACKGROUND_ALPHA_DIMMED;
alpha = (alpha + (1.0f - alpha) * mNormalBackgroundVisibilityAmount);
alpha *= mShadowAlpha;
if (mFadeInFromDarkAnimator != null) {
alpha *= mFadeInFromDarkAnimator.getAnimatedFraction();
}
setOutlineAlpha(alpha);
}
public void setNormalBackgroundVisibilityAmount(float normalBackgroundVisibilityAmount) {
mNormalBackgroundVisibilityAmount = normalBackgroundVisibilityAmount;
updateOutlineAlpha();
}
public void setShowingLegacyBackground(boolean showing) {
mShowingLegacyBackground = showing;
updateBackgroundTint();
}
@Override
public void setBelowSpeedBump(boolean below) {
super.setBelowSpeedBump(below);
if (below != mIsBelowSpeedBump) {
mIsBelowSpeedBump = below;
updateBackgroundTint();
}
}
/**
* Sets the tint color of the background
*/
public void setTintColor(int color) {
setTintColor(color, false);
}
/**
* Sets the tint color of the background
*/
public void setTintColor(int color, boolean animated) {
mBgTint = color;
updateBackgroundTint(animated);
}
protected void updateBackgroundTint() {
updateBackgroundTint(false /* animated */);
}
private void updateBackgroundTint(boolean animated) {
if (mBackgroundColorAnimator != null) {
mBackgroundColorAnimator.cancel();
}
int rippleColor = getRippleColor();
mBackgroundDimmed.setRippleColor(rippleColor);
mBackgroundNormal.setRippleColor(rippleColor);
int color = calculateBgColor();
if (!animated) {
setBackgroundTintColor(color);
} else if (color != mCurrentBackgroundTint) {
mStartTint = mCurrentBackgroundTint;
mTargetTint = color;
mBackgroundColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mBackgroundColorAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int newColor = NotificationUtils.interpolateColors(mStartTint, mTargetTint,
animation.getAnimatedFraction());
setBackgroundTintColor(newColor);
}
});
mBackgroundColorAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
mBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR);
mBackgroundColorAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBackgroundColorAnimator = null;
}
});
mBackgroundColorAnimator.start();
}
}
private void setBackgroundTintColor(int color) {
mCurrentBackgroundTint = color;
if (color == mNormalColor) {
// We don't need to tint a normal notification
color = 0;
}
mBackgroundDimmed.setTint(color);
mBackgroundNormal.setTint(color);
}
/**
* Fades in the background when exiting dark mode.
*/
private void fadeInFromDark(long delay) {
final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
background.setAlpha(0f);
mBackgroundVisibilityUpdater.onAnimationUpdate(null);
background.setPivotX(mBackgroundDimmed.getWidth() / 2f);
background.setPivotY(getActualHeight() / 2f);
background.setScaleX(DARK_EXIT_SCALE_START);
background.setScaleY(DARK_EXIT_SCALE_START);
background.animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(DARK_ANIMATION_LENGTH)
.setStartDelay(delay)
.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
// Jump state if we are cancelled
background.setScaleX(1f);
background.setScaleY(1f);
background.setAlpha(1f);
}
})
.setUpdateListener(mBackgroundVisibilityUpdater)
.start();
mFadeInFromDarkAnimator = TimeAnimator.ofFloat(0.0f, 1.0f);
mFadeInFromDarkAnimator.setDuration(DARK_ANIMATION_LENGTH);
mFadeInFromDarkAnimator.setStartDelay(delay);
mFadeInFromDarkAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
mFadeInFromDarkAnimator.addListener(mFadeInEndListener);
mFadeInFromDarkAnimator.addUpdateListener(mUpdateOutlineListener);
mFadeInFromDarkAnimator.start();
}
/**
* Fades the background when the dimmed state changes.
*/
private void fadeDimmedBackground() {
mBackgroundDimmed.animate().cancel();
mBackgroundNormal.animate().cancel();
if (mActivated) {
updateBackground();
return;
}
if (!shouldHideBackground()) {
if (mDimmed) {
mBackgroundDimmed.setVisibility(View.VISIBLE);
} else {
mBackgroundNormal.setVisibility(View.VISIBLE);
}
}
float startAlpha = mDimmed ? 1f : 0;
float endAlpha = mDimmed ? 0 : 1f;
int duration = BACKGROUND_ANIMATION_LENGTH_MS;
// Check whether there is already a background animation running.
if (mBackgroundAnimator != null) {
startAlpha = (Float) mBackgroundAnimator.getAnimatedValue();
duration = (int) mBackgroundAnimator.getCurrentPlayTime();
mBackgroundAnimator.removeAllListeners();
mBackgroundAnimator.cancel();
if (duration <= 0) {
updateBackground();
return;
}
}
mBackgroundNormal.setAlpha(startAlpha);
mBackgroundAnimator =
ObjectAnimator.ofFloat(mBackgroundNormal, View.ALPHA, startAlpha, endAlpha);
mBackgroundAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
mBackgroundAnimator.setDuration(duration);
mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateBackground();
mBackgroundAnimator = null;
if (mFadeInFromDarkAnimator == null) {
mDimmedBackgroundFadeInAmount = -1;
}
}
});
mBackgroundAnimator.addUpdateListener(mBackgroundVisibilityUpdater);
mBackgroundAnimator.start();
}
protected void updateBackgroundAlpha(float transformationAmount) {
mBgAlpha = isChildInGroup() && mDimmed ? transformationAmount : 1f;
if (mDimmedBackgroundFadeInAmount != -1) {
mBgAlpha *= mDimmedBackgroundFadeInAmount;
}
mBackgroundDimmed.setAlpha(mBgAlpha);
}
protected void resetBackgroundAlpha() {
updateBackgroundAlpha(0f /* transformationAmount */);
}
protected void updateBackground() {
cancelFadeAnimations();
if (shouldHideBackground()) {
mBackgroundDimmed.setVisibility(View.INVISIBLE);
mBackgroundNormal.setVisibility(View.INVISIBLE);
} else if (mDimmed) {
// When groups are animating to the expanded state from the lockscreen, show the
// normal background instead of the dimmed background
final boolean dontShowDimmed = isGroupExpansionChanging() && isChildInGroup();
mBackgroundDimmed.setVisibility(dontShowDimmed ? View.INVISIBLE : View.VISIBLE);
mBackgroundNormal.setVisibility((mActivated || dontShowDimmed)
? View.VISIBLE
: View.INVISIBLE);
} else {
mBackgroundDimmed.setVisibility(View.INVISIBLE);
mBackgroundNormal.setVisibility(View.VISIBLE);
mBackgroundNormal.setAlpha(1f);
removeCallbacks(mTapTimeoutRunnable);
// make in inactive to avoid it sticking around active
makeInactive(false /* animate */);
}
setNormalBackgroundVisibilityAmount(
mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
}
protected boolean shouldHideBackground() {
return mDark;
}
private void cancelFadeAnimations() {
if (mBackgroundAnimator != null) {
mBackgroundAnimator.cancel();
}
mBackgroundDimmed.animate().cancel();
mBackgroundNormal.animate().cancel();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
setPivotX(getWidth() / 2);
}
@Override
public void setActualHeight(int actualHeight, boolean notifyListeners) {
super.setActualHeight(actualHeight, notifyListeners);
setPivotY(actualHeight / 2);
mBackgroundNormal.setActualHeight(actualHeight);
mBackgroundDimmed.setActualHeight(actualHeight);
}
@Override
public void setClipTopAmount(int clipTopAmount) {
super.setClipTopAmount(clipTopAmount);
mBackgroundNormal.setClipTopAmount(clipTopAmount);
mBackgroundDimmed.setClipTopAmount(clipTopAmount);
}
@Override
public void performRemoveAnimation(long duration, float translationDirection,
Runnable onFinishedRunnable) {
enableAppearDrawing(true);
if (mDrawingAppearAnimation) {
startAppearAnimation(false /* isAppearing */, translationDirection,
0, duration, onFinishedRunnable);
} else if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
}
@Override
public void performAddAnimation(long delay, long duration) {
enableAppearDrawing(true);
if (mDrawingAppearAnimation) {
startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null);
}
}
private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay,
long duration, final Runnable onFinishedRunnable) {
cancelAppearAnimation();
mAnimationTranslationY = translationDirection * getActualHeight();
if (mAppearAnimationFraction == -1.0f) {
// not initialized yet, we start anew
if (isAppearing) {
mAppearAnimationFraction = 0.0f;
mAppearAnimationTranslation = mAnimationTranslationY;
} else {
mAppearAnimationFraction = 1.0f;
mAppearAnimationTranslation = 0;
}
}
float targetValue;
if (isAppearing) {
mCurrentAppearInterpolator = mSlowOutFastInInterpolator;
mCurrentAlphaInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
targetValue = 1.0f;
} else {
mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN;
mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator;
targetValue = 0.0f;
}
mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
targetValue);
mAppearAnimator.setInterpolator(Interpolators.LINEAR);
mAppearAnimator.setDuration(
(long) (duration * Math.abs(mAppearAnimationFraction - targetValue)));
mAppearAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mAppearAnimationFraction = (float) animation.getAnimatedValue();
updateAppearAnimationAlpha();
updateAppearRect();
invalidate();
}
});
if (delay > 0) {
// we need to apply the initial state already to avoid drawn frames in the wrong state
updateAppearAnimationAlpha();
updateAppearRect();
mAppearAnimator.setStartDelay(delay);
}
mAppearAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mWasCancelled;
@Override
public void onAnimationEnd(Animator animation) {
if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
if (!mWasCancelled) {
enableAppearDrawing(false);
onAppearAnimationFinished(isAppearing);
}
}
@Override
public void onAnimationStart(Animator animation) {
mWasCancelled = false;
}
@Override
public void onAnimationCancel(Animator animation) {
mWasCancelled = true;
}
});
mAppearAnimator.start();
}
protected void onAppearAnimationFinished(boolean wasAppearing) {
}
private void cancelAppearAnimation() {
if (mAppearAnimator != null) {
mAppearAnimator.cancel();
mAppearAnimator = null;
}
}
public void cancelAppearDrawing() {
cancelAppearAnimation();
enableAppearDrawing(false);
}
private void updateAppearRect() {
float inverseFraction = (1.0f - mAppearAnimationFraction);
float translationFraction = mCurrentAppearInterpolator.getInterpolation(inverseFraction);
float translateYTotalAmount = translationFraction * mAnimationTranslationY;
mAppearAnimationTranslation = translateYTotalAmount;
// handle width animation
float widthFraction = (inverseFraction - (1.0f - HORIZONTAL_ANIMATION_START))
/ (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END);
widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction));
widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction);
float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) *
widthFraction);
float right = getWidth() - left;
// handle top animation
float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) /
VERTICAL_ANIMATION_START;
heightFraction = Math.max(0.0f, heightFraction);
heightFraction = mCurrentAppearInterpolator.getInterpolation(heightFraction);
float top;
float bottom;
final int actualHeight = getActualHeight();
if (mAnimationTranslationY > 0.0f) {
bottom = actualHeight - heightFraction * mAnimationTranslationY * 0.1f
- translateYTotalAmount;
top = bottom * heightFraction;
} else {
top = heightFraction * (actualHeight + mAnimationTranslationY) * 0.1f -
translateYTotalAmount;
bottom = actualHeight * (1 - heightFraction) + top * heightFraction;
}
mAppearAnimationRect.set(left, top, right, bottom);
setOutlineRect(left, top + mAppearAnimationTranslation, right,
bottom + mAppearAnimationTranslation);
}
private void updateAppearAnimationAlpha() {
float contentAlphaProgress = mAppearAnimationFraction;
contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
setContentAlpha(contentAlphaProgress);
}
private void setContentAlpha(float contentAlpha) {
View contentView = getContentView();
if (contentView.hasOverlappingRendering()) {
int layerType = contentAlpha == 0.0f || contentAlpha == 1.0f ? LAYER_TYPE_NONE
: LAYER_TYPE_HARDWARE;
int currentLayerType = contentView.getLayerType();
if (currentLayerType != layerType) {
contentView.setLayerType(layerType, null);
}
}
contentView.setAlpha(contentAlpha);
}
protected abstract View getContentView();
public int calculateBgColor() {
return calculateBgColor(true /* withTint */);
}
private int calculateBgColor(boolean withTint) {
if (withTint && mBgTint != 0) {
return mBgTint;
} else if (mShowingLegacyBackground) {
return mLegacyColor;
} else if (mIsBelowSpeedBump) {
return mLowPriorityColor;
} else {
return mNormalColor;
}
}
protected int getRippleColor() {
if (mBgTint != 0) {
return mTintedRippleColor;
} else if (mShowingLegacyBackground) {
return mTintedRippleColor;
} else if (mIsBelowSpeedBump) {
return mLowPriorityRippleColor;
} else {
return mNormalRippleColor;
}
}
/**
* When we draw the appear animation, we render the view in a bitmap and render this bitmap
* as a shader of a rect. This call creates the Bitmap and switches the drawing mode,
* such that the normal drawing of the views does not happen anymore.
*
* @param enable Should it be enabled.
*/
private void enableAppearDrawing(boolean enable) {
if (enable != mDrawingAppearAnimation) {
mDrawingAppearAnimation = enable;
if (!enable) {
setContentAlpha(1.0f);
mAppearAnimationFraction = -1;
setOutlineRect(null);
}
invalidate();
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mDrawingAppearAnimation) {
canvas.save();
canvas.translate(0, mAppearAnimationTranslation);
}
super.dispatchDraw(canvas);
if (mDrawingAppearAnimation) {
canvas.restore();
}
}
public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
mOnActivatedListener = onActivatedListener;
}
public void reset() {
setTintColor(0);
resetBackgroundAlpha();
setShowingLegacyBackground(false);
setBelowSpeedBump(false);
}
public boolean hasSameBgColor(ActivatableNotificationView otherView) {
return calculateBgColor() == otherView.calculateBgColor();
}
@Override
public float getShadowAlpha() {
return mShadowAlpha;
}
@Override
public void setShadowAlpha(float shadowAlpha) {
if (shadowAlpha != mShadowAlpha) {
mShadowAlpha = shadowAlpha;
updateOutlineAlpha();
}
}
@Override
public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
int outlineTranslation) {
mFakeShadow.setFakeShadowTranslationZ(shadowIntensity * (getTranslationZ()
+ FakeShadowView.SHADOW_SIBLING_TRESHOLD), outlineAlpha, shadowYEnd,
outlineTranslation);
}
public int getBackgroundColorWithoutTint() {
return calculateBgColor(false /* withTint */);
}
public interface OnActivatedListener {
void onActivated(ActivatableNotificationView view);
void onActivationReset(ActivatableNotificationView view);
}
}