FileDocCategorySizeDatePackage
ActivatableNotificationView.javaAPI DocAndroid 5.1 API28723Thu Mar 12 22:22:42 GMT 2015com.android.systemui.statusbar

ActivatableNotificationView

public abstract class ActivatableNotificationView extends ExpandableOutlineView
Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer} to implement dimming/activating on Keyguard for the double-tap gesture

Fields Summary
private static final long
DOUBLETAP_TIMEOUT_MS
private static final int
BACKGROUND_ANIMATION_LENGTH_MS
private static final int
ACTIVATE_ANIMATION_LENGTH
private static final int
DARK_ANIMATION_LENGTH
private static final float
HORIZONTAL_COLLAPSED_REST_PARTIAL
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_ANIMATION_END
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
ALPHA_ANIMATION_END
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
HORIZONTAL_ANIMATION_START
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
VERTICAL_ANIMATION_START
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
DARK_EXIT_SCALE_START
Scale for the background to animate from when exiting dark mode.
private static final android.view.animation.Interpolator
ACTIVATE_INVERSE_INTERPOLATOR
private static final android.view.animation.Interpolator
ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
private final int
mTintedRippleColor
private final int
mLowPriorityRippleColor
private final int
mNormalRippleColor
private boolean
mDimmed
private boolean
mDark
private int
mBgTint
private final int
mRoundedRectCornerRadius
private boolean
mActivated
Flag to indicate that the notification has been touched once and the second touch will click it.
private float
mDownX
private float
mDownY
private final float
mTouchSlop
private OnActivatedListener
mOnActivatedListener
private final android.view.animation.Interpolator
mLinearOutSlowInInterpolator
private final android.view.animation.Interpolator
mFastOutSlowInInterpolator
private final android.view.animation.Interpolator
mSlowOutFastInInterpolator
private final android.view.animation.Interpolator
mSlowOutLinearInInterpolator
private final android.view.animation.Interpolator
mLinearInterpolator
private android.view.animation.Interpolator
mCurrentAppearInterpolator
private android.view.animation.Interpolator
mCurrentAlphaInterpolator
private NotificationBackgroundView
mBackgroundNormal
private NotificationBackgroundView
mBackgroundDimmed
private android.animation.ObjectAnimator
mBackgroundAnimator
private android.graphics.RectF
mAppearAnimationRect
private android.graphics.PorterDuffColorFilter
mAppearAnimationFilter
private float
mAnimationTranslationY
private boolean
mDrawingAppearAnimation
private android.graphics.Paint
mAppearPaint
private android.animation.ValueAnimator
mAppearAnimator
private float
mAppearAnimationFraction
private float
mAppearAnimationTranslation
private boolean
mShowingLegacyBackground
private final int
mLegacyColor
private final int
mNormalColor
private final int
mLowPriorityColor
private boolean
mIsBelowSpeedBump
private final Runnable
mTapTimeoutRunnable
Constructors Summary
public ActivatableNotificationView(android.content.Context context, android.util.AttributeSet attrs)


         
        super(context, attrs);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mFastOutSlowInInterpolator =
                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
        mSlowOutFastInInterpolator = new PathInterpolator(0.8f, 0.0f, 0.6f, 1.0f);
        mLinearOutSlowInInterpolator =
                AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
        mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
        mLinearInterpolator = new LinearInterpolator();
        setClipChildren(false);
        setClipToPadding(false);
        mAppearAnimationFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
        mRoundedRectCornerRadius = getResources().getDimensionPixelSize(
                R.dimen.notification_material_rounded_rect_radius);
        mLegacyColor = getResources().getColor(R.color.notification_legacy_background_color);
        mNormalColor = getResources().getColor(R.color.notification_material_background_color);
        mLowPriorityColor = getResources().getColor(
                R.color.notification_material_background_low_priority_color);
        mTintedRippleColor = context.getResources().getColor(
                R.color.notification_ripple_tinted_color);
        mLowPriorityRippleColor = context.getResources().getColor(
                R.color.notification_ripple_color_low_priority);
        mNormalRippleColor = context.getResources().getColor(
                R.color.notification_ripple_untinted_color);
    
Methods Summary
private voidcancelFadeAnimations()

        if (mBackgroundAnimator != null) {
            mBackgroundAnimator.cancel();
        }
        mBackgroundDimmed.animate().cancel();
        mBackgroundNormal.animate().cancel();
    
protected voiddispatchDraw(android.graphics.Canvas canvas)

        if (!mDrawingAppearAnimation) {
            super.dispatchDraw(canvas);
        } else {
            drawAppearRect(canvas);
        }
    
private voiddrawAppearRect(android.graphics.Canvas canvas)

        canvas.save();
        canvas.translate(0, mAppearAnimationTranslation);
        canvas.drawRoundRect(mAppearAnimationRect, mRoundedRectCornerRadius,
                mRoundedRectCornerRadius, mAppearPaint);
        canvas.restore();
    
public voiddrawableHotspotChanged(float x, float y)

        if (!mDimmed){
            mBackgroundNormal.drawableHotspotChanged(x, y);
        }
    
protected voiddrawableStateChanged()

        super.drawableStateChanged();
        if (mDimmed) {
            mBackgroundDimmed.setState(getDrawableState());
        } else {
            mBackgroundNormal.setState(getDrawableState());
        }
    
private voidenableAppearDrawing(boolean enable)
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.

        if (enable != mDrawingAppearAnimation) {
            if (enable) {
                if (getWidth() == 0 || getActualHeight() == 0) {
                    // TODO: This should not happen, but it can during expansion. Needs
                    // investigation
                    return;
                }
                Bitmap bitmap = Bitmap.createBitmap(getWidth(), getActualHeight(),
                        Bitmap.Config.ARGB_8888);
                Canvas canvas = new Canvas(bitmap);
                draw(canvas);
                mAppearPaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP,
                        Shader.TileMode.CLAMP));
            } else {
                mAppearPaint.setShader(null);
            }
            mDrawingAppearAnimation = enable;
            invalidate();
        }
    
private voidfadeDimmedBackground()
Fades the background when the dimmed state changes.

        mBackgroundDimmed.animate().cancel();
        mBackgroundNormal.animate().cancel();
        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(mFastOutSlowInInterpolator);
        mBackgroundAnimator.setDuration(duration);
        mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (mDimmed) {
                    mBackgroundNormal.setVisibility(View.INVISIBLE);
                } else {
                    mBackgroundDimmed.setVisibility(View.INVISIBLE);
                }
                mBackgroundAnimator = null;
            }
        });
        mBackgroundAnimator.start();
    
private voidfadeInFromDark(long delay)
Fades in the background when exiting dark mode.

        final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
        background.setAlpha(0f);
        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(mLinearOutSlowInInterpolator)
                .setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationCancel(Animator animation) {
                        // Jump state if we are cancelled
                        background.setScaleX(1f);
                        background.setScaleY(1f);
                        background.setAlpha(1f);
                    }
                })
                .start();
    
private intgetBackgroundColor()

        if (mBgTint != 0) {
            return mBgTint;
        } else if (mShowingLegacyBackground) {
            return mLegacyColor;
        } else if (mIsBelowSpeedBump) {
            return mLowPriorityColor;
        } else {
            return mNormalColor;
        }
    
private intgetRippleColor()

        if (mBgTint != 0) {
            return mTintedRippleColor;
        } else if (mShowingLegacyBackground) {
            return mTintedRippleColor;
        } else if (mIsBelowSpeedBump) {
            return mLowPriorityRippleColor;
        } else {
            return mNormalRippleColor;
        }
    
private booleanhandleTouchEventDimmed(android.view.MotionEvent event)

        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                if (mDownY > getActualHeight()) {
                    return false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isWithinTouchSlop(event)) {
                    makeInactive(true /* animate */);
                    return false;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isWithinTouchSlop(event)) {
                    if (!mActivated) {
                        makeActive();
                        postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
                    } else {
                        boolean performed = performClick();
                        if (performed) {
                            removeCallbacks(mTapTimeoutRunnable);
                        }
                    }
                } else {
                    makeInactive(true /* animate */);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                makeInactive(true /* animate */);
                break;
            default:
                break;
        }
        return true;
    
private booleanisWithinTouchSlop(android.view.MotionEvent event)

        return Math.abs(event.getX() - mDownX) < mTouchSlop
                && Math.abs(event.getY() - mDownY) < mTouchSlop;
    
private voidmakeActive()

        startActivateAnimation(false /* reverse */);
        mActivated = true;
        if (mOnActivatedListener != null) {
            mOnActivatedListener.onActivated(this);
        }
    
public voidmakeInactive(boolean animate)
Cancels the hotspot and makes the notification inactive.

        if (mActivated) {
            if (mDimmed) {
                if (animate) {
                    startActivateAnimation(true /* reverse */);
                } else {
                    mBackgroundNormal.setVisibility(View.INVISIBLE);
                }
            }
            mActivated = false;
        }
        if (mOnActivatedListener != null) {
            mOnActivatedListener.onActivationReset(this);
        }
        removeCallbacks(mTapTimeoutRunnable);
    
protected voidonFinishInflate()

        super.onFinishInflate();
        mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal);
        mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
        mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
        mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
        updateBackground();
        updateBackgroundTint();
    
protected voidonLayout(boolean changed, int left, int top, int right, int bottom)

        super.onLayout(changed, left, top, right, bottom);
        setPivotX(getWidth() / 2);
    
public booleanonTouchEvent(android.view.MotionEvent event)


    
        
        if (mDimmed) {
            return handleTouchEventDimmed(event);
        } else {
            return super.onTouchEvent(event);
        }
    
public voidperformAddAnimation(long delay, long duration)

        enableAppearDrawing(true);
        if (mDrawingAppearAnimation) {
            startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null);
        }
    
public voidperformRemoveAnimation(long duration, float translationDirection, java.lang.Runnable onFinishedRunnable)

        enableAppearDrawing(true);
        if (mDrawingAppearAnimation) {
            startAppearAnimation(false /* isAppearing */, translationDirection,
                    0, duration, onFinishedRunnable);
        } else if (onFinishedRunnable != null) {
            onFinishedRunnable.run();
        }
    
public voidreset()

        setTintColor(0);
        setShowingLegacyBackground(false);
        setBelowSpeedBump(false);
    
public voidsetActualHeight(int actualHeight, boolean notifyListeners)

        super.setActualHeight(actualHeight, notifyListeners);
        setPivotY(actualHeight / 2);
        mBackgroundNormal.setActualHeight(actualHeight);
        mBackgroundDimmed.setActualHeight(actualHeight);
    
public voidsetBelowSpeedBump(boolean below)

        super.setBelowSpeedBump(below);
        if (below != mIsBelowSpeedBump) {
            mIsBelowSpeedBump = below;
            updateBackgroundTint();
        }
    
public voidsetClipTopAmount(int clipTopAmount)

        super.setClipTopAmount(clipTopAmount);
        mBackgroundNormal.setClipTopAmount(clipTopAmount);
        mBackgroundDimmed.setClipTopAmount(clipTopAmount);
    
public voidsetDark(boolean dark, boolean fade, long delay)

        super.setDark(dark, fade, delay);
        if (mDark == dark) {
            return;
        }
        mDark = dark;
        if (!dark && fade) {
            if (mActivated) {
                mBackgroundDimmed.setVisibility(View.VISIBLE);
                mBackgroundNormal.setVisibility(View.VISIBLE);
            } else if (mDimmed) {
                mBackgroundDimmed.setVisibility(View.VISIBLE);
                mBackgroundNormal.setVisibility(View.INVISIBLE);
            } else {
                mBackgroundDimmed.setVisibility(View.INVISIBLE);
                mBackgroundNormal.setVisibility(View.VISIBLE);
            }
            fadeInFromDark(delay);
        } else {
            updateBackground();
        }
     
public voidsetDimmed(boolean dimmed, boolean fade)

        if (mDimmed != dimmed) {
            mDimmed = dimmed;
            if (fade) {
                fadeDimmedBackground();
            } else {
                updateBackground();
            }
        }
    
public voidsetOnActivatedListener(com.android.systemui.statusbar.ActivatableNotificationView$OnActivatedListener onActivatedListener)

        mOnActivatedListener = onActivatedListener;
    
public voidsetShowingLegacyBackground(boolean showing)

        mShowingLegacyBackground = showing;
        updateBackgroundTint();
    
public voidsetTintColor(int color)
Sets the tint color of the background

        mBgTint = color;
        updateBackgroundTint();
    
private voidstartActivateAnimation(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 = mLinearOutSlowInInterpolator;
            alphaInterpolator = mLinearOutSlowInInterpolator;
        } 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) {
                    if (mDimmed) {
                        mBackgroundNormal.setVisibility(View.INVISIBLE);
                    }
                }
            });
            animator.start();
        } else {
            mBackgroundNormal.setAlpha(0.4f);
            animator.start();
        }
        mBackgroundNormal.animate()
                .alpha(reverse ? 0f : 1f)
                .setInterpolator(alphaInterpolator)
                .setDuration(ACTIVATE_ANIMATION_LENGTH);
    
private voidstartAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, java.lang.Runnable onFinishedRunnable)

        if (mAppearAnimator != null) {
            mAppearAnimator.cancel();
        }
        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 = mLinearOutSlowInInterpolator;
            targetValue = 1.0f;
        } else {
            mCurrentAppearInterpolator = mFastOutSlowInInterpolator;
            mCurrentAlphaInterpolator = mSlowOutLinearInInterpolator;
            targetValue = 0.0f;
        }
        mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction,
                targetValue);
        mAppearAnimator.setInterpolator(mLinearInterpolator);
        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) {
                    mAppearAnimationFraction = -1;
                    setOutlineRect(null);
                    enableAppearDrawing(false);
                }
            }

            @Override
            public void onAnimationStart(Animator animation) {
                mWasCancelled = false;
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                mWasCancelled = true;
            }
        });
        mAppearAnimator.start();
    
private voidupdateAppearAnimationAlpha()

        int backgroundColor = getBackgroundColor();
        if (backgroundColor != -1) {
            float contentAlphaProgress = mAppearAnimationFraction;
            contentAlphaProgress = contentAlphaProgress / (1.0f - ALPHA_ANIMATION_END);
            contentAlphaProgress = Math.min(1.0f, contentAlphaProgress);
            contentAlphaProgress = mCurrentAlphaInterpolator.getInterpolation(contentAlphaProgress);
            int sourceColor = Color.argb((int) (255 * (1.0f - contentAlphaProgress)),
                    Color.red(backgroundColor), Color.green(backgroundColor),
                    Color.blue(backgroundColor));
            mAppearAnimationFilter.setColor(sourceColor);
            mAppearPaint.setColorFilter(mAppearAnimationFilter);
        }
    
private voidupdateAppearRect()

        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 voidupdateBackground()

        cancelFadeAnimations();
        if (mDark) {
            mBackgroundDimmed.setVisibility(View.INVISIBLE);
            mBackgroundNormal.setVisibility(View.INVISIBLE);
        } else if (mDimmed) {
            mBackgroundDimmed.setVisibility(View.VISIBLE);
            mBackgroundNormal.setVisibility(View.INVISIBLE);
        } else {
            mBackgroundDimmed.setVisibility(View.INVISIBLE);
            mBackgroundNormal.setVisibility(View.VISIBLE);
            mBackgroundNormal.setAlpha(1f);
            removeCallbacks(mTapTimeoutRunnable);
        }
    
private voidupdateBackgroundTint()

        int color = getBackgroundColor();
        int rippleColor = getRippleColor();
        if (color == mNormalColor) {
            // We don't need to tint a normal notification
            color = 0;
        }
        mBackgroundDimmed.setTint(color);
        mBackgroundNormal.setTint(color);
        mBackgroundDimmed.setRippleColor(rippleColor);
        mBackgroundNormal.setRippleColor(rippleColor);