FileDocCategorySizeDatePackage
PanelView.javaAPI DocAndroid 5.1 API37353Thu Mar 12 22:22:42 GMT 2015com.android.systemui.statusbar.phone

PanelView

public abstract class PanelView extends android.widget.FrameLayout

Fields Summary
public static final boolean
DEBUG
public static final String
TAG
protected PhoneStatusBar
mStatusBar
private float
mPeekHeight
private float
mHintDistance
private int
mEdgeTapAreaWidth
private float
mInitialOffsetOnTouch
private float
mExpandedFraction
protected float
mExpandedHeight
private boolean
mPanelClosedOnDown
private boolean
mHasLayoutedSinceDown
private float
mUpdateFlingVelocity
private boolean
mUpdateFlingOnLayout
private boolean
mPeekTouching
private boolean
mJustPeeked
private boolean
mClosing
protected boolean
mTracking
private boolean
mTouchSlopExceeded
private int
mTrackingPointer
protected int
mTouchSlop
protected boolean
mHintAnimationRunning
private boolean
mOverExpandedBeforeFling
private boolean
mTouchAboveFalsingThreshold
private int
mUnlockFalsingThreshold
private boolean
mTouchStartedInEmptyArea
private android.animation.ValueAnimator
mHeightAnimator
private android.animation.ObjectAnimator
mPeekAnimator
private VelocityTrackerInterface
mVelocityTracker
private com.android.systemui.statusbar.FlingAnimationUtils
mFlingAnimationUtils
private boolean
mInstantExpanding
Whether an instant expand request is currently pending and we are just waiting for layout.
PanelBar
mBar
private String
mViewName
private float
mInitialTouchY
private float
mInitialTouchX
private boolean
mTouchDisabled
private android.view.animation.Interpolator
mLinearOutSlowInInterpolator
private android.view.animation.Interpolator
mFastOutSlowInInterpolator
private android.view.animation.Interpolator
mBounceInterpolator
protected KeyguardBottomAreaView
mKeyguardBottomArea
private boolean
mPeekPending
private boolean
mCollapseAfterPeek
private boolean
mExpanding
private boolean
mGestureWaitForTouchSlop
private boolean
mDozingOnDown
private Runnable
mPeekRunnable
private final Runnable
mFlingCollapseRunnable
private final Runnable
mPostCollapseRunnable
Constructors Summary
public PanelView(android.content.Context context, android.util.AttributeSet attrs)

        super(context, attrs);
        mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
        mFastOutSlowInInterpolator =
                AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
        mLinearOutSlowInInterpolator =
                AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
        mBounceInterpolator = new BounceInterpolator();
    
Methods Summary
private voidabortAnimations()

        cancelPeek();
        cancelHeightAnimator();
        removeCallbacks(mPostCollapseRunnable);
        removeCallbacks(mFlingCollapseRunnable);
    
private voidcancelHeightAnimator()

        if (mHeightAnimator != null) {
            mHeightAnimator.cancel();
        }
        endClosing();
    
public voidcancelPeek()

        if (mPeekAnimator != null) {
            mPeekAnimator.cancel();
        }
        removeCallbacks(mPeekRunnable);
        mPeekPending = false;

        // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
        // notify mBar that we might have closed ourselves.
        notifyBarPanelExpansionChanged();
    
public voidcollapse(boolean delayed)

        if (DEBUG) logf("collapse: " + this);
        if (mPeekPending || mPeekAnimator != null) {
            mCollapseAfterPeek = true;
            if (mPeekPending) {

                // We know that the whole gesture is just a peek triggered by a simple click, so
                // better start it now.
                removeCallbacks(mPeekRunnable);
                mPeekRunnable.run();
            }
        } else if (!isFullyCollapsed() && !mTracking && !mClosing) {
            cancelHeightAnimator();
            mClosing = true;
            notifyExpandingStarted();
            if (delayed) {
                postDelayed(mFlingCollapseRunnable, 120);
            } else {
                fling(0, false /* expand */);
            }
        }
    
private android.animation.ValueAnimatorcreateHeightAnimator(float targetHeight)

        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                setExpandedHeightInternal((Float) animation.getAnimatedValue());
            }
        });
        return animator;
    
public voiddump(java.io.FileDescriptor fd, java.io.PrintWriter pw, java.lang.String[] args)

        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
                + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s touchDisabled=%s"
                + "]",
                this.getClass().getSimpleName(),
                getExpandedHeight(),
                getMaxPanelHeight(),
                mClosing?"T":"f",
                mTracking?"T":"f",
                mJustPeeked?"T":"f",
                mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
                mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":""),
                mTouchDisabled?"T":"f"
        ));
    
private voidendClosing()

        if (mClosing) {
            mClosing = false;
            onClosingFinished();
        }
    
public voidexpand()


       
        if (DEBUG) logf("expand: " + this);
        if (isFullyCollapsed()) {
            mBar.startOpeningPanel(this);
            notifyExpandingStarted();
            fling(0, true /* expand */);
        } else if (DEBUG) {
            if (DEBUG) logf("skipping expansion: is expanded");
        }
    
protected voidfling(float vel, boolean expand)

        cancelPeek();
        float target = expand ? getMaxPanelHeight() : 0.0f;

        // Hack to make the expand transition look nice when clear all button is visible - we make
        // the animation only to the last notification, and then jump to the maximum panel height so
        // clear all just fades in and the decelerating motion is towards the last notification.
        final boolean clearAllExpandHack = expand && fullyExpandedClearAllVisible()
                && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
                && !isClearAllVisible();
        if (clearAllExpandHack) {
            target = getMaxPanelHeight() - getClearAllHeight();
        }
        if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
            notifyExpandingFinished();
            return;
        }
        mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
        ValueAnimator animator = createHeightAnimator(target);
        if (expand) {
            boolean belowFalsingThreshold = isBelowFalsingThreshold();
            if (belowFalsingThreshold) {
                vel = 0;
            }
            mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
            if (belowFalsingThreshold) {
                animator.setDuration(350);
            }
        } else {
            mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel,
                    getHeight());

            // Make it shorter if we run a canned animation
            if (vel == 0) {
                animator.setDuration((long)
                        (animator.getDuration() * getCannedFlingDurationFactor()));
            }
        }
        animator.addListener(new AnimatorListenerAdapter() {
            private boolean mCancelled;

            @Override
            public void onAnimationCancel(Animator animation) {
                mCancelled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (clearAllExpandHack && !mCancelled) {
                    setExpandedHeightInternal(getMaxPanelHeight());
                }
                mHeightAnimator = null;
                if (!mCancelled) {
                    notifyExpandingFinished();
                }
            }
        });
        mHeightAnimator = animator;
        animator.start();
    
protected booleanflingExpands(float vel, float vectorVel)

param
vel the current vertical velocity of the motion
param
vectorVel the length of the vectorial velocity
return
whether a fling should expands the panel; contracts otherwise

        if (isBelowFalsingThreshold()) {
            return true;
        }
        if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
            return getExpandedFraction() > 0.5f;
        } else {
            return vel > 0;
        }
    
protected abstract booleanfullyExpandedClearAllVisible()

return
whether "Clear all" button will be visible when the panel is fully expanded

protected abstract floatgetCannedFlingDurationFactor()

protected abstract intgetClearAllHeight()

return
the height of the clear all button, in pixels

protected floatgetContentHeight()

        return mExpandedHeight;
    
public floatgetExpandedFraction()

        return mExpandedFraction;
    
public floatgetExpandedHeight()

        return mExpandedHeight;
    
private intgetFalsingThreshold()

        float factor = mStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
        return (int) (mUnlockFalsingThreshold * factor);
    
protected abstract intgetMaxPanelHeight()
This returns the maximum height of the panel. Children should override this if their desired height is not the full height.

return
the default implementation simply returns the maximum height.

public java.lang.StringgetName()

        return mViewName;
    
protected abstract floatgetOverExpansionAmount()

protected abstract floatgetOverExpansionPixels()

protected abstract floatgetPeekHeight()

protected abstract booleanhasConflictingGestures()

private voidinitVelocityTracker()

        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
        }
        mVelocityTracker = VelocityTrackerFactory.obtain(getContext());
    
public voidinstantCollapse()

        abortAnimations();
        setExpandedFraction(0f);
        if (mExpanding) {
            notifyExpandingFinished();
        }
    
public voidinstantExpand()

        mInstantExpanding = true;
        mUpdateFlingOnLayout = false;
        abortAnimations();
        cancelPeek();
        if (mTracking) {
            onTrackingStopped(true /* expands */); // The panel is expanded after this call.
        }
        if (mExpanding) {
            notifyExpandingFinished();
        }
        setVisibility(VISIBLE);

        // Wait for window manager to pickup the change, so we know the maximum height of the panel
        // then.
        getViewTreeObserver().addOnGlobalLayoutListener(
                new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        if (mStatusBar.getStatusBarWindow().getHeight()
                                != mStatusBar.getStatusBarHeight()) {
                            getViewTreeObserver().removeOnGlobalLayoutListener(this);
                            setExpandedFraction(1f);
                            mInstantExpanding = false;
                        }
                    }
                });

        // Make sure a layout really happens.
        requestLayout();
    
private booleanisBelowFalsingThreshold()

        return !mTouchAboveFalsingThreshold && mStatusBar.isFalsingThresholdNeeded();
    
protected abstract booleanisClearAllVisible()

public booleanisCollapsing()

        return mClosing;
    
protected abstract booleanisDozing()

public booleanisFullyCollapsed()

        return mExpandedHeight <= 0;
    
public booleanisFullyExpanded()

        return mExpandedHeight >= getMaxPanelHeight();
    
protected abstract booleanisInContentBounds(float x, float y)

return
Whether a pair of coordinates are inside the visible view content bounds.

protected booleanisScrolledToBottom()

        return true;
    
public booleanisTracking()

        return mTracking;
    
protected abstract booleanisTrackingBlocked()

return
true if the panel tracking should be temporarily blocked; this is used when a conflicting gesture (opening QS) is happening

protected voidloadDimens()

        final Resources res = getContext().getResources();
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mHintDistance = res.getDimension(R.dimen.hint_move_distance);
        mEdgeTapAreaWidth = res.getDimensionPixelSize(R.dimen.edge_tap_area_width);
        mUnlockFalsingThreshold = res.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
    
private final voidlogf(java.lang.String fmt, java.lang.Object args)


           
        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
    
private voidnotifyBarPanelExpansionChanged()

        mBar.panelExpansionChanged(this, mExpandedFraction, mExpandedFraction > 0f || mPeekPending
                || mPeekAnimator != null);
    
private voidnotifyExpandingFinished()

        if (mExpanding) {
            mExpanding = false;
            onExpandingFinished();
        }
    
private voidnotifyExpandingStarted()

        if (!mExpanding) {
            mExpanding = true;
            onExpandingStarted();
        }
    
protected voidonAttachedToWindow()

        super.onAttachedToWindow();
        mViewName = getResources().getResourceName(getId());
    
protected voidonClosingFinished()

        mBar.onClosingFinished();
    
protected voidonConfigurationChanged(android.content.res.Configuration newConfig)

        super.onConfigurationChanged(newConfig);
        loadDimens();
    
protected abstract voidonEdgeClicked(boolean right)

protected booleanonEmptySpaceClick(float x)
Gets called when the user performs a click anywhere in the empty area of the panel.

return
whether the panel will be expanded after the action performed by this method

        if (mHintAnimationRunning) {
            return true;
        }
        if (x < mEdgeTapAreaWidth
                && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
            onEdgeClicked(false /* right */);
            return true;
        } else if (x > getWidth() - mEdgeTapAreaWidth
                && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
            onEdgeClicked(true /* right */);
            return true;
        } else {
            return onMiddleClicked();
        }
    
protected voidonExpandingFinished()


       
        endClosing();
        mBar.onExpandingFinished();
    
protected voidonExpandingStarted()

    
protected voidonFinishInflate()

        super.onFinishInflate();
        loadDimens();
    
protected abstract voidonHeightUpdated(float expandedHeight)

public booleanonInterceptTouchEvent(android.view.MotionEvent event)

        if (mInstantExpanding) {
            return false;
        }

        /*
         * If the user drags anywhere inside the panel we intercept it if he moves his finger
         * upwards. This allows closing the shade from anywhere inside the panel.
         *
         * We only do this if the current content is scrolled to the bottom,
         * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
         * possible.
         */
        int pointerIndex = event.findPointerIndex(mTrackingPointer);
        if (pointerIndex < 0) {
            pointerIndex = 0;
            mTrackingPointer = event.getPointerId(pointerIndex);
        }
        final float x = event.getX(pointerIndex);
        final float y = event.getY(pointerIndex);
        boolean scrolledToBottom = isScrolledToBottom();

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mStatusBar.userActivity();
                if (mHeightAnimator != null && !mHintAnimationRunning ||
                        mPeekPending || mPeekAnimator != null) {
                    cancelHeightAnimator();
                    cancelPeek();
                    mTouchSlopExceeded = true;
                    return true;
                }
                mInitialTouchY = y;
                mInitialTouchX = x;
                mTouchStartedInEmptyArea = !isInContentBounds(x, y);
                mTouchSlopExceeded = false;
                mJustPeeked = false;
                mPanelClosedOnDown = mExpandedHeight == 0.0f;
                mHasLayoutedSinceDown = false;
                mUpdateFlingOnLayout = false;
                mTouchAboveFalsingThreshold = false;
                mDozingOnDown = isDozing();
                initVelocityTracker();
                trackMovement(event);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                final int upPointer = event.getPointerId(event.getActionIndex());
                if (mTrackingPointer == upPointer) {
                    // gesture is ongoing, find a new pointer to track
                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
                    mTrackingPointer = event.getPointerId(newIndex);
                    mInitialTouchX = event.getX(newIndex);
                    mInitialTouchY = event.getY(newIndex);
                }
                break;

            case MotionEvent.ACTION_MOVE:
                final float h = y - mInitialTouchY;
                trackMovement(event);
                if (scrolledToBottom || mTouchStartedInEmptyArea) {
                    if (h < -mTouchSlop && h < -Math.abs(x - mInitialTouchX)) {
                        cancelHeightAnimator();
                        mInitialOffsetOnTouch = mExpandedHeight;
                        mInitialTouchY = y;
                        mInitialTouchX = x;
                        mTracking = true;
                        mTouchSlopExceeded = true;
                        onTrackingStarted();
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                break;
        }
        return false;
    
protected voidonLayout(boolean changed, int left, int top, int right, int bottom)

        super.onLayout(changed, left, top, right, bottom);
        requestPanelHeightUpdate();
        mHasLayoutedSinceDown = true;
        if (mUpdateFlingOnLayout) {
            abortAnimations();
            fling(mUpdateFlingVelocity, true);
            mUpdateFlingOnLayout = false;
        }
    
private booleanonMiddleClicked()

       
        switch (mStatusBar.getBarState()) {
            case StatusBarState.KEYGUARD:
                if (!mDozingOnDown) {
                    EventLogTags.writeSysuiLockscreenGesture(
                            EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT,
                            0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
                    startUnlockHintAnimation();
                }
                return true;
            case StatusBarState.SHADE_LOCKED:
                mStatusBar.goToKeyguard();
                return true;
            case StatusBarState.SHADE:

                // This gets called in the middle of the touch handling, where the state is still
                // that we are tracking the panel. Collapse the panel after this is done.
                post(mPostCollapseRunnable);
                return false;
            default:
                return true;
        }
    
public booleanonTouchEvent(android.view.MotionEvent event)

        if (mInstantExpanding || mTouchDisabled) {
            return false;
        }

        /*
         * We capture touch events here and update the expand height here in case according to
         * the users fingers. This also handles multi-touch.
         *
         * If the user just clicks shortly, we give him a quick peek of the shade.
         *
         * Flinging is also enabled in order to open or close the shade.
         */

        int pointerIndex = event.findPointerIndex(mTrackingPointer);
        if (pointerIndex < 0) {
            pointerIndex = 0;
            mTrackingPointer = event.getPointerId(pointerIndex);
        }
        final float y = event.getY(pointerIndex);
        final float x = event.getX(pointerIndex);

        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            mGestureWaitForTouchSlop = mExpandedHeight == 0f;
        }
        boolean waitForTouchSlop = hasConflictingGestures() || mGestureWaitForTouchSlop;

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mInitialTouchY = y;
                mInitialTouchX = x;
                mInitialOffsetOnTouch = mExpandedHeight;
                mTouchSlopExceeded = false;
                mJustPeeked = false;
                mPanelClosedOnDown = mExpandedHeight == 0.0f;
                mHasLayoutedSinceDown = false;
                mUpdateFlingOnLayout = false;
                mPeekTouching = mPanelClosedOnDown;
                mTouchAboveFalsingThreshold = false;
                mDozingOnDown = isDozing();
                if (mVelocityTracker == null) {
                    initVelocityTracker();
                }
                trackMovement(event);
                if (!waitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
                        mPeekPending || mPeekAnimator != null) {
                    cancelHeightAnimator();
                    cancelPeek();
                    mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
                            || mPeekPending || mPeekAnimator != null;
                    onTrackingStarted();
                }
                if (mExpandedHeight == 0) {
                    schedulePeek();
                }
                break;

            case MotionEvent.ACTION_POINTER_UP:
                final int upPointer = event.getPointerId(event.getActionIndex());
                if (mTrackingPointer == upPointer) {
                    // gesture is ongoing, find a new pointer to track
                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
                    final float newY = event.getY(newIndex);
                    final float newX = event.getX(newIndex);
                    mTrackingPointer = event.getPointerId(newIndex);
                    mInitialOffsetOnTouch = mExpandedHeight;
                    mInitialTouchY = newY;
                    mInitialTouchX = newX;
                }
                break;

            case MotionEvent.ACTION_MOVE:
                float h = y - mInitialTouchY;

                // If the panel was collapsed when touching, we only need to check for the
                // y-component of the gesture, as we have no conflicting horizontal gesture.
                if (Math.abs(h) > mTouchSlop
                        && (Math.abs(h) > Math.abs(x - mInitialTouchX)
                                || mInitialOffsetOnTouch == 0f)) {
                    mTouchSlopExceeded = true;
                    if (waitForTouchSlop && !mTracking) {
                        if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
                            mInitialOffsetOnTouch = mExpandedHeight;
                            mInitialTouchX = x;
                            mInitialTouchY = y;
                            h = 0;
                        }
                        cancelHeightAnimator();
                        removeCallbacks(mPeekRunnable);
                        mPeekPending = false;
                        onTrackingStarted();
                    }
                }
                final float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
                if (newHeight > mPeekHeight) {
                    if (mPeekAnimator != null) {
                        mPeekAnimator.cancel();
                    }
                    mJustPeeked = false;
                }
                if (-h >= getFalsingThreshold()) {
                    mTouchAboveFalsingThreshold = true;
                }
                if (!mJustPeeked && (!waitForTouchSlop || mTracking) && !isTrackingBlocked()) {
                    setExpandedHeightInternal(newHeight);
                }

                trackMovement(event);
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mTrackingPointer = -1;
                trackMovement(event);
                if ((mTracking && mTouchSlopExceeded)
                        || Math.abs(x - mInitialTouchX) > mTouchSlop
                        || Math.abs(y - mInitialTouchY) > mTouchSlop
                        || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
                    float vel = 0f;
                    float vectorVel = 0f;
                    if (mVelocityTracker != null) {
                        mVelocityTracker.computeCurrentVelocity(1000);
                        vel = mVelocityTracker.getYVelocity();
                        vectorVel = (float) Math.hypot(
                                mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
                    }
                    boolean expand = flingExpands(vel, vectorVel)
                            || event.getActionMasked() == MotionEvent.ACTION_CANCEL;
                    onTrackingStopped(expand);
                    DozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
                            mStatusBar.isFalsingThresholdNeeded(),
                            mStatusBar.isScreenOnComingFromTouch());
                    // Log collapse gesture if on lock screen.
                    if (!expand && mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
                        float displayDensity = mStatusBar.getDisplayDensity();
                        int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
                        int velocityDp = (int) Math.abs(vel / displayDensity);
                        EventLogTags.writeSysuiLockscreenGesture(
                                EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_UP_UNLOCK,
                                heightDp, velocityDp);
                    }
                    fling(vel, expand);
                    mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
                    if (mUpdateFlingOnLayout) {
                        mUpdateFlingVelocity = vel;
                    }
                } else {
                    boolean expands = onEmptySpaceClick(mInitialTouchX);
                    onTrackingStopped(expands);
                }

                if (mVelocityTracker != null) {
                    mVelocityTracker.recycle();
                    mVelocityTracker = null;
                }
                mPeekTouching = false;
                break;
        }
        return !waitForTouchSlop || mTracking;
    
protected voidonTrackingStarted()

        endClosing();
        mTracking = true;
        mCollapseAfterPeek = false;
        mBar.onTrackingStarted(PanelView.this);
        notifyExpandingStarted();
    
protected voidonTrackingStopped(boolean expand)

        mTracking = false;
        mBar.onTrackingStopped(PanelView.this, expand);
    
protected voidrequestPanelHeightUpdate()

        float currentMaxPanelHeight = getMaxPanelHeight();

        // If the user isn't actively poking us, let's update the height
        if ((!mTracking || isTrackingBlocked())
                && mHeightAnimator == null
                && mExpandedHeight > 0
                && currentMaxPanelHeight != mExpandedHeight
                && !mPeekPending
                && mPeekAnimator == null
                && !mPeekTouching) {
            setExpandedHeight(currentMaxPanelHeight);
        }
    
public abstract voidresetViews()

private voidrunPeekAnimation()

        mPeekHeight = getPeekHeight();
        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
        if (mHeightAnimator != null) {
            return;
        }
        mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight)
                .setDuration(250);
        mPeekAnimator.setInterpolator(mLinearOutSlowInInterpolator);
        mPeekAnimator.addListener(new AnimatorListenerAdapter() {
            private boolean mCancelled;

            @Override
            public void onAnimationCancel(Animator animation) {
                mCancelled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                mPeekAnimator = null;
                if (mCollapseAfterPeek && !mCancelled) {
                    postOnAnimation(new Runnable() {
                        @Override
                        public void run() {
                            collapse(false /* delayed */);
                        }
                    });
                }
                mCollapseAfterPeek = false;
            }
        });
        notifyExpandingStarted();
        mPeekAnimator.start();
        mJustPeeked = true;
    
private voidschedulePeek()

        mPeekPending = true;
        long timeout = ViewConfiguration.getTapTimeout();
        postOnAnimationDelayed(mPeekRunnable, timeout);
        notifyBarPanelExpansionChanged();
    
public voidsetBar(PanelBar panelBar)

        mBar = panelBar;
    
public voidsetExpandedFraction(float frac)

        setExpandedHeight(getMaxPanelHeight() * frac);
    
public voidsetExpandedHeight(float height)

        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
        setExpandedHeightInternal(height + getOverExpansionPixels());
    
public voidsetExpandedHeightInternal(float h)

        float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
        if (mHeightAnimator == null) {
            float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
            if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
                setOverExpansion(overExpansionPixels, true /* isPixels */);
            }
            mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
        } else {
            mExpandedHeight = h;
            if (mOverExpandedBeforeFling) {
                setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
            }
        }

        mExpandedHeight = Math.max(0, mExpandedHeight);
        mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
                ? 0
                : mExpandedHeight / fhWithoutOverExpansion);
        onHeightUpdated(mExpandedHeight);
        notifyBarPanelExpansionChanged();
    
protected abstract voidsetOverExpansion(float overExpansion, boolean isPixels)

public voidsetTouchDisabled(boolean disabled)

        mTouchDisabled = disabled;
    
protected voidstartUnlockHintAnimation()


        // We don't need to hint the user if an animation is already running or the user is changing
        // the expansion.
        if (mHeightAnimator != null || mTracking) {
            return;
        }
        cancelPeek();
        notifyExpandingStarted();
        startUnlockHintAnimationPhase1(new Runnable() {
            @Override
            public void run() {
                notifyExpandingFinished();
                mStatusBar.onHintFinished();
                mHintAnimationRunning = false;
            }
        });
        mStatusBar.onUnlockHintStarted();
        mHintAnimationRunning = true;
    
private voidstartUnlockHintAnimationPhase1(java.lang.Runnable onAnimationFinished)
Phase 1: Move everything upwards.

        float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
        ValueAnimator animator = createHeightAnimator(target);
        animator.setDuration(250);
        animator.setInterpolator(mFastOutSlowInInterpolator);
        animator.addListener(new AnimatorListenerAdapter() {
            private boolean mCancelled;

            @Override
            public void onAnimationCancel(Animator animation) {
                mCancelled = true;
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (mCancelled) {
                    mHeightAnimator = null;
                    onAnimationFinished.run();
                } else {
                    startUnlockHintAnimationPhase2(onAnimationFinished);
                }
            }
        });
        animator.start();
        mHeightAnimator = animator;
        mKeyguardBottomArea.getIndicationView().animate()
                .translationY(-mHintDistance)
                .setDuration(250)
                .setInterpolator(mFastOutSlowInInterpolator)
                .withEndAction(new Runnable() {
                    @Override
                    public void run() {
                        mKeyguardBottomArea.getIndicationView().animate()
                                .translationY(0)
                                .setDuration(450)
                                .setInterpolator(mBounceInterpolator)
                                .start();
                    }
                })
                .start();
    
private voidstartUnlockHintAnimationPhase2(java.lang.Runnable onAnimationFinished)
Phase 2: Bounce down.

        ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
        animator.setDuration(450);
        animator.setInterpolator(mBounceInterpolator);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mHeightAnimator = null;
                onAnimationFinished.run();
            }
        });
        animator.start();
        mHeightAnimator = animator;
    
private voidtrackMovement(android.view.MotionEvent event)

        // Add movement to velocity tracker using raw screen X and Y coordinates instead
        // of window coordinates because the window frame may be moving at the same time.
        float deltaX = event.getRawX() - event.getX();
        float deltaY = event.getRawY() - event.getY();
        event.offsetLocation(deltaX, deltaY);
        if (mVelocityTracker != null) mVelocityTracker.addMovement(event);
        event.offsetLocation(-deltaX, -deltaY);