FileDocCategorySizeDatePackage
SlidingChallengeLayout.javaAPI DocAndroid 5.1 API49607Thu Mar 12 22:22:42 GMT 2015com.android.keyguard

SlidingChallengeLayout

public class SlidingChallengeLayout extends android.view.ViewGroup implements ChallengeLayout
This layout handles interaction with the sliding security challenge views that overlay/resize other keyguard contents.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final int
DRAG_HANDLE_CLOSED_ABOVE
private static final int
DRAG_HANDLE_CLOSED_BELOW
private static final int
DRAG_HANDLE_OPEN_ABOVE
private static final int
DRAG_HANDLE_OPEN_BELOW
private static final int
HANDLE_ANIMATE_DURATION
private boolean
mEdgeCaptured
private android.util.DisplayMetrics
mDisplayMetrics
private android.view.View
mExpandChallengeView
private KeyguardSecurityContainer
mChallengeView
private android.view.View
mScrimView
private android.view.View
mWidgetsView
private float
mChallengeOffset
private boolean
mChallengeShowing
private boolean
mChallengeShowingTargetState
private boolean
mWasChallengeShowing
private boolean
mIsBouncing
private final android.widget.Scroller
mScroller
private android.animation.ObjectAnimator
mFader
private int
mScrollState
private OnChallengeScrolledListener
mScrollListener
private OnBouncerStateChangedListener
mBouncerListener
private boolean
mEnableChallengeDragging
public static final int
SCROLL_STATE_IDLE
public static final int
SCROLL_STATE_DRAGGING
public static final int
SCROLL_STATE_SETTLING
public static final int
SCROLL_STATE_FADING
public static final int
CHALLENGE_FADE_OUT_DURATION
public static final int
CHALLENGE_FADE_IN_DURATION
private static final int
MAX_SETTLE_DURATION
private int
mActivePointerId
private static final int
INVALID_POINTER
private boolean
mDragging
private boolean
mBlockDrag
private android.view.VelocityTracker
mVelocityTracker
private int
mMinVelocity
private int
mMaxVelocity
private float
mGestureStartX
private float
mGestureStartY
private int
mGestureStartChallengeBottom
private int
mDragHandleClosedBelow
private int
mDragHandleClosedAbove
private int
mDragHandleOpenBelow
private int
mDragHandleOpenAbove
private int
mDragHandleEdgeSlop
private int
mChallengeBottomBound
private int
mTouchSlop
private int
mTouchSlopSquare
float
mHandleAlpha
float
mFrameAlpha
float
mFrameAnimationTarget
private android.animation.ObjectAnimator
mHandleAnimation
private android.animation.ObjectAnimator
mFrameAnimation
private boolean
mHasGlowpad
private final android.graphics.Rect
mInsets
private boolean
mChallengeInteractiveExternal
private boolean
mChallengeInteractiveInternal
static final android.util.Property
HANDLE_ALPHA
private boolean
mHasLayout
private static final android.view.animation.Interpolator
sMotionInterpolator
private static final android.view.animation.Interpolator
sHandleFadeInterpolator
private final Runnable
mEndScrollRunnable
private final OnClickListener
mScrimClickListener
private final OnClickListener
mExpandChallengeClickListener
Constructors Summary
public SlidingChallengeLayout(android.content.Context context)


                     
       
                                                                                 
           

                                                                                                                                                                        
             
    

       
        this(context, null);
    
public SlidingChallengeLayout(android.content.Context context, android.util.AttributeSet attrs)

        this(context, attrs, 0);
    
public SlidingChallengeLayout(android.content.Context context, android.util.AttributeSet attrs, int defStyle)

        super(context, attrs, defStyle);

        mScroller = new Scroller(context, sMotionInterpolator);

        final ViewConfiguration vc = ViewConfiguration.get(context);
        mMinVelocity = vc.getScaledMinimumFlingVelocity();
        mMaxVelocity = vc.getScaledMaximumFlingVelocity();

        final Resources res = getResources();
        mDragHandleEdgeSlop = res.getDimensionPixelSize(R.dimen.kg_edge_swipe_region_size);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mTouchSlopSquare = mTouchSlop * mTouchSlop;

        mDisplayMetrics = res.getDisplayMetrics();
        final float density = mDisplayMetrics.density;

        // top half of the lock icon, plus another 25% to be sure
        mDragHandleClosedAbove = (int) (DRAG_HANDLE_CLOSED_ABOVE * density + 0.5f);
        mDragHandleClosedBelow = (int) (DRAG_HANDLE_CLOSED_BELOW * density + 0.5f);
        mDragHandleOpenAbove = (int) (DRAG_HANDLE_OPEN_ABOVE * density + 0.5f);
        mDragHandleOpenBelow = (int) (DRAG_HANDLE_OPEN_BELOW * density + 0.5f);

        // how much space to account for in the handle when closed
        mChallengeBottomBound = res.getDimensionPixelSize(R.dimen.kg_widget_pager_bottom_padding);

        setWillNotDraw(false);
        setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
    
Methods Summary
voidanimateChallengeTo(int y, int velocity)
Animate the bottom edge of the challenge view to the given position.

param
y desired final position for the bottom edge of the challenge view in px
param
velocity velocity in

        if (mChallengeView == null) {
            // Nothing to do.
            return;
        }

        cancelTransitionsInProgress();

        mChallengeInteractiveInternal = false;
        enableHardwareLayerForChallengeView();
        final int sy = mChallengeView.getBottom();
        final int dy = y - sy;
        if (dy == 0) {
            completeChallengeScroll();
            return;
        }

        setScrollState(SCROLL_STATE_SETTLING);

        final int childHeight = mChallengeView.getHeight();
        final int halfHeight = childHeight / 2;
        final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dy) / childHeight);
        final float distance = halfHeight + halfHeight *
                distanceInfluenceForSnapDuration(distanceRatio);

        int duration = 0;
        velocity = Math.abs(velocity);
        if (velocity > 0) {
            duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
        } else {
            final float childDelta = (float) Math.abs(dy) / childHeight;
            duration = (int) ((childDelta + 1) * 100);
        }
        duration = Math.min(duration, MAX_SETTLE_DURATION);

        mScroller.startScroll(0, sy, 0, dy, duration);
        postInvalidateOnAnimation();
    
voidanimateHandle(boolean visible)

        if (mHandleAnimation != null) {
            mHandleAnimation.cancel();
            mHandleAnimation = null;
        }
        final float targetAlpha = visible ? 1.f : 0.f;
        if (targetAlpha == mHandleAlpha) {
            return;
        }
        mHandleAnimation = ObjectAnimator.ofFloat(this, HANDLE_ALPHA, targetAlpha);
        mHandleAnimation.setInterpolator(sHandleFadeInterpolator);
        mHandleAnimation.setDuration(HANDLE_ANIMATE_DURATION);
        mHandleAnimation.start();
    
private voidcancelTransitionsInProgress()

        if (!mScroller.isFinished()) {
            mScroller.abortAnimation();
            completeChallengeScroll();
        }
        if (mFader != null) {
            mFader.cancel();
        }
    
protected booleancheckLayoutParams(ViewGroup.LayoutParams p)

        return p instanceof LayoutParams;
    
voidcompleteChallengeScroll()

        setChallengeShowing(mChallengeShowingTargetState);
        mChallengeOffset = mChallengeShowing ? 1.f : 0.f;
        setScrollState(SCROLL_STATE_IDLE);
        mChallengeInteractiveInternal = true;
        mChallengeView.setLayerType(LAYER_TYPE_NONE, null);
    
public voidcomputeScroll()

        super.computeScroll();

        if (!mScroller.isFinished()) {
            if (mChallengeView == null) {
                // Can't scroll if the view is missing.
                Log.e(TAG, "Challenge view missing in computeScroll");
                mScroller.abortAnimation();
                return;
            }

            mScroller.computeScrollOffset();
            moveChallengeTo(mScroller.getCurrY());

            if (mScroller.isFinished()) {
                post(mEndScrollRunnable);
            }
        }
    
private booleancrossedDragHandle(float x, float y, float initialY)


        final int challengeTop = mChallengeView.getTop();
        final boolean horizOk = x >= 0 && x < getWidth();

        final boolean vertOk;
        if (mChallengeShowing) {
            vertOk = initialY < (challengeTop - getDragHandleSizeAbove()) &&
                    y > challengeTop + getDragHandleSizeBelow();
        } else {
            vertOk = initialY > challengeTop + getDragHandleSizeBelow() &&
                    y < challengeTop - getDragHandleSizeAbove();
        }
        return horizOk && vertOk;
    
public booleandispatchTouchEvent(android.view.MotionEvent ev)
The lifecycle of touch events is subtle and it's very easy to do something that will cause bugs that will be nasty to track when overriding this method. Normally one should always override onInterceptTouchEvent instead. To put it another way, don't try this at home.

        final int action = ev.getActionMasked();
        boolean handled = false;
        if (action == MotionEvent.ACTION_DOWN) {
            // Defensive programming: if we didn't get the UP or CANCEL, reset anyway.
            mEdgeCaptured = false;
        }
        if (mWidgetsView != null && !mIsBouncing && (mEdgeCaptured || isEdgeSwipeBeginEvent(ev))) {
            // Normally we would need to do a lot of extra stuff here.
            // We can only get away with this because we haven't padded in
            // the widget pager or otherwise transformed it during layout.
            // We also don't support things like splitting MotionEvents.

            // We set handled to captured even if dispatch is returning false here so that
            // we don't send a different view a busted or incomplete event stream.
            handled = mEdgeCaptured |= mWidgetsView.dispatchTouchEvent(ev);
        }

        if (!handled && !mEdgeCaptured) {
            handled = super.dispatchTouchEvent(ev);
        }

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mEdgeCaptured = false;
        }

        return handled;
    
floatdistanceInfluenceForSnapDuration(float f)

        f -= 0.5f; // center the values about 0.
        f *= 0.3f * Math.PI / 2.0f;
        return (float) Math.sin(f);
    
public voiddraw(android.graphics.Canvas c)

        super.draw(c);
        if (DEBUG) {
            final Paint debugPaint = new Paint();
            debugPaint.setColor(0x40FF00CC);
            // show the isInDragHandle() rect
            c.drawRect(mDragHandleEdgeSlop,
                    mChallengeView.getTop() - getDragHandleSizeAbove(),
                    getWidth() - mDragHandleEdgeSlop,
                    mChallengeView.getTop() + getDragHandleSizeBelow(),
                    debugPaint);
        }
    
private voidenableHardwareLayerForChallengeView()

        if (mChallengeView.isHardwareAccelerated()) {
            mChallengeView.setLayerType(LAYER_TYPE_HARDWARE, null);
        }
    
public voidfadeChallenge(boolean show)

        if (mChallengeView != null) {

            cancelTransitionsInProgress();
            float alpha = show ? 1f : 0f;
            int duration = show ? CHALLENGE_FADE_IN_DURATION : CHALLENGE_FADE_OUT_DURATION;
            mFader = ObjectAnimator.ofFloat(mChallengeView, "alpha", alpha);
            mFader.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    onFadeStart(show);
                }
                @Override
                public void onAnimationEnd(Animator animation) {
                    onFadeEnd(show);
                }
            });
            mFader.setDuration(duration);
            mFader.start();
        }
    
public voidfadeInChallenge()

        fadeChallenge(true);
    
public voidfadeOutChallenge()

        fadeChallenge(false);
    
protected ViewGroup.LayoutParamsgenerateDefaultLayoutParams()

        return new LayoutParams();
    
public ViewGroup.LayoutParamsgenerateLayoutParams(android.util.AttributeSet attrs)

        return new LayoutParams(getContext(), attrs);
    
protected ViewGroup.LayoutParamsgenerateLayoutParams(ViewGroup.LayoutParams p)

        return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) :
                p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) :
                new LayoutParams(p);
    
public intgetBouncerAnimationDuration()

        return HANDLE_ANIMATE_DURATION;
    
private floatgetChallengeAlpha()

        float x = mChallengeOffset - 1;
        return x * x * x + 1.f;
    
private intgetChallengeBottom()
The bottom edge of mChallengeView; essentially, where the sliding challenge 'is'.

        if (mChallengeView == null) return 0;

        return mChallengeView.getBottom();
    
private intgetChallengeMargin(boolean expanded)

        return expanded && mHasGlowpad ? 0 : mDragHandleEdgeSlop;
    
private intgetDragHandleSizeAbove()
We only want to add additional vertical space to the drag handle when the panel is fully closed.

        return isChallengeShowing() ? mDragHandleOpenAbove : mDragHandleClosedAbove;
    
private intgetDragHandleSizeBelow()

        return isChallengeShowing() ? mDragHandleOpenBelow : mDragHandleClosedBelow;
    
private intgetLayoutBottom()
The bottom edge of this SlidingChallengeLayout's coordinate system; will coincide with the bottom edge of mChallengeView when the challenge is fully opened.

        final int bottomMargin = (mChallengeView == null)
                ? 0
                : ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin;
        final int layoutBottom = getMeasuredHeight() - getPaddingBottom() - bottomMargin
                - mInsets.bottom;
        return layoutBottom;
    
private intgetMaxChallengeBottom()

        if (mChallengeView == null) return 0;
        final int layoutBottom = getLayoutBottom();
        final int challengeHeight = mChallengeView.getMeasuredHeight();

        return (layoutBottom + challengeHeight - mChallengeBottomBound);
    
public intgetMaxChallengeTop()

        if (mChallengeView == null) return 0;

        final int layoutBottom = getLayoutBottom();
        final int challengeHeight = mChallengeView.getMeasuredHeight();
        return layoutBottom - challengeHeight - mInsets.top;
    
private intgetMinChallengeBottom()

        return getLayoutBottom();
    
public voidhideBouncer()

        if (!mIsBouncing) return;
        setSystemUiVisibility(getSystemUiVisibility() & ~STATUS_BAR_DISABLE_SEARCH);
        if (!mWasChallengeShowing) showChallenge(false);
        mIsBouncing = false;

        if (mScrimView != null) {
            Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 0f);
            anim.setDuration(HANDLE_ANIMATE_DURATION);
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mScrimView.setVisibility(GONE);
                }
            });
            anim.start();
        }
        if (mChallengeView != null) {
            mChallengeView.hideBouncer(HANDLE_ANIMATE_DURATION);
        }
        if (mBouncerListener != null) {
            mBouncerListener.onBouncerStateChanged(false);
        }
    
public booleanisBouncing()

        return mIsBouncing;
    
private booleanisChallengeInteractionBlocked()

        return !mChallengeInteractiveExternal || !mChallengeInteractiveInternal;
    
public booleanisChallengeOverlapping()

        return mChallengeShowing;
    
public booleanisChallengeShowing()

return
true if the challenge is at all visible.

        return mChallengeShowing;
    
private booleanisEdgeSwipeBeginEvent(android.view.MotionEvent ev)

        if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
            return false;
        }

        final float x = ev.getX();
        return x < mDragHandleEdgeSlop || x >= getWidth() - mDragHandleEdgeSlop;
    
private booleanisInChallengeView(float x, float y)

        return isPointInView(x, y, mChallengeView);
    
private booleanisInDragHandle(float x, float y)

        return isPointInView(x, y, mExpandChallengeView);
    
private booleanisPointInView(float x, float y, android.view.View view)

        if (view == null) {
            return false;
        }
        return x >= view.getLeft() && y >= view.getTop()
                && x < view.getRight() && y < view.getBottom();
    
private intmakeChildMeasureSpec(int maxSize, int childDimen)

        final int mode;
        final int size;
        switch (childDimen) {
            case LayoutParams.WRAP_CONTENT:
                mode = MeasureSpec.AT_MOST;
                size = maxSize;
                break;
            case LayoutParams.MATCH_PARENT:
                mode = MeasureSpec.EXACTLY;
                size = maxSize;
                break;
            default:
                mode = MeasureSpec.EXACTLY;
                size = Math.min(maxSize, childDimen);
                break;
        }
        return MeasureSpec.makeMeasureSpec(size, mode);
    
private booleanmoveChallengeTo(int bottom)
Move the bottom edge of mChallengeView to a new position and notify the listener if it represents a change in position. Changes made through this method will be stable across layout passes. If this method is called before first layout of this SlidingChallengeLayout it will have no effect.

param
bottom New bottom edge in px in this SlidingChallengeLayout's coordinate system.
return
true if the challenge view was moved

        if (mChallengeView == null || !mHasLayout) {
            return false;
        }

        final int layoutBottom = getLayoutBottom();
        final int challengeHeight = mChallengeView.getHeight();

        bottom = Math.max(getMinChallengeBottom(),
                Math.min(bottom, getMaxChallengeBottom()));

        float offset = 1.f - (float) (bottom - layoutBottom) /
                (challengeHeight - mChallengeBottomBound);
        mChallengeOffset = offset;
        if (offset > 0 && !mChallengeShowing) {
            setChallengeShowing(true);
        }

        mChallengeView.layout(mChallengeView.getLeft(),
                bottom - mChallengeView.getHeight(), mChallengeView.getRight(), bottom);

        mChallengeView.setAlpha(getChallengeAlpha());
        if (mScrollListener != null) {
            mScrollListener.onScrollPositionChanged(offset, mChallengeView.getTop());
        }
        postInvalidateOnAnimation();
        return true;
    
public voidonAttachedToWindow()

        super.onAttachedToWindow();

        mHasLayout = false;
    
public voidonDetachedFromWindow()

        super.onDetachedFromWindow();

        removeCallbacks(mEndScrollRunnable);
        mHasLayout = false;
    
private voidonFadeEnd(boolean show)

        mChallengeInteractiveInternal = true;
        setChallengeShowing(show);

        if (!show) {
            moveChallengeTo(getMaxChallengeBottom());
        }

        mChallengeView.setLayerType(LAYER_TYPE_NONE, null);
        mFader = null;
        setScrollState(SCROLL_STATE_IDLE);
    
private voidonFadeStart(boolean show)

        mChallengeInteractiveInternal = false;
        enableHardwareLayerForChallengeView();

        if (show) {
            moveChallengeTo(getMinChallengeBottom());
        }

        setScrollState(SCROLL_STATE_FADING);
    
public booleanonInterceptTouchEvent(android.view.MotionEvent ev)

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mGestureStartX = ev.getX();
                mGestureStartY = ev.getY();
                mBlockDrag = false;
                break;

            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                resetTouch();
                break;

            case MotionEvent.ACTION_MOVE:
                final int count = ev.getPointerCount();
                for (int i = 0; i < count; i++) {
                    final float x = ev.getX(i);
                    final float y = ev.getY(i);
                    if (!mIsBouncing && mActivePointerId == INVALID_POINTER
                                && (crossedDragHandle(x, y, mGestureStartY)
                                        && shouldEnableChallengeDragging()
                                        || (isInChallengeView(x, y) &&
                                        mScrollState == SCROLL_STATE_SETTLING))) {
                        mActivePointerId = ev.getPointerId(i);
                        mGestureStartX = x;
                        mGestureStartY = y;
                        mGestureStartChallengeBottom = getChallengeBottom();
                        mDragging = true;
                        enableHardwareLayerForChallengeView();
                    } else if (mChallengeShowing && isInChallengeView(x, y)
                            && shouldEnableChallengeDragging()) {
                        mBlockDrag = true;
                    }
                }
                break;
        }

        if (mBlockDrag || isChallengeInteractionBlocked()) {
            mActivePointerId = INVALID_POINTER;
            mDragging = false;
        }

        return mDragging;
    
protected voidonLayout(boolean changed, int l, int t, int r, int b)

        final int paddingLeft = getPaddingLeft();
        final int paddingTop = getPaddingTop();
        final int paddingRight = getPaddingRight();
        final int paddingBottom = getPaddingBottom();
        final int width = r - l;
        final int height = b - t;

        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);

            if (child.getVisibility() == GONE) continue;

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
                // Challenge views pin to the bottom, offset by a portion of their height,
                // and center horizontally.
                final int center = (paddingLeft + width - paddingRight) / 2;
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
                final int left = center - childWidth / 2;
                final int layoutBottom = height - paddingBottom - lp.bottomMargin - mInsets.bottom;
                // We use the top of the challenge view to position the handle, so
                // we never want less than the handle size showing at the bottom.
                final int bottom = layoutBottom + (int) ((childHeight - mChallengeBottomBound)
                        * (1 - mChallengeOffset));
                child.setAlpha(getChallengeAlpha());
                child.layout(left, bottom - childHeight, left + childWidth, bottom);
            } else if (lp.childType == LayoutParams.CHILD_TYPE_EXPAND_CHALLENGE_HANDLE) {
                final int center = (paddingLeft + width - paddingRight) / 2;
                final int left = center - child.getMeasuredWidth() / 2;
                final int right = left + child.getMeasuredWidth();
                final int bottom = height - paddingBottom - lp.bottomMargin - mInsets.bottom;
                final int top = bottom - child.getMeasuredHeight();
                child.layout(left, top, right, bottom);
            } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
                // Scrim views use the entire area, including padding & insets
                child.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
            } else {
                // Non-challenge views lay out from the upper left, layered.
                child.layout(paddingLeft + lp.leftMargin,
                        paddingTop + lp.topMargin + mInsets.top,
                        paddingLeft + child.getMeasuredWidth(),
                        paddingTop + child.getMeasuredHeight() + mInsets.top);
            }
        }

        if (!mHasLayout) {
            mHasLayout = true;
        }
    
protected voidonMeasure(int widthSpec, int heightSpec)

        if (MeasureSpec.getMode(widthSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightSpec) != MeasureSpec.EXACTLY) {
            throw new IllegalArgumentException(
                    "SlidingChallengeLayout must be measured with an exact size");
        }
        final int width = MeasureSpec.getSize(widthSpec);
        final int height = MeasureSpec.getSize(heightSpec);
        setMeasuredDimension(width, height);

        final int insetHeight = height - mInsets.top - mInsets.bottom;
        final int insetHeightSpec = MeasureSpec.makeMeasureSpec(insetHeight, MeasureSpec.EXACTLY);

        // Find one and only one challenge view.
        final View oldChallengeView = mChallengeView;
        final View oldExpandChallengeView = mChallengeView;
        mChallengeView = null;
        mExpandChallengeView = null;
        final int count = getChildCount();

        // First iteration through the children finds special children and sets any associated
        // state.
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) {
                if (mChallengeView != null) {
                    throw new IllegalStateException(
                            "There may only be one child with layout_isChallenge=\"true\"");
                }
                if (!(child instanceof KeyguardSecurityContainer)) {
                            throw new IllegalArgumentException(
                                    "Challenge must be a KeyguardSecurityContainer");
                }
                mChallengeView = (KeyguardSecurityContainer) child;
                if (mChallengeView != oldChallengeView) {
                    mChallengeView.setVisibility(mChallengeShowing ? VISIBLE : INVISIBLE);
                }
                // We're going to play silly games with the frame's background drawable later.
                if (!mHasLayout) {
                    // Set up the margin correctly based on our content for the first run.
                    mHasGlowpad = child.findViewById(R.id.keyguard_selector_view) != null;
                    lp.leftMargin = lp.rightMargin = getChallengeMargin(true);
                }
            } else if (lp.childType == LayoutParams.CHILD_TYPE_EXPAND_CHALLENGE_HANDLE) {
                if (mExpandChallengeView != null) {
                    throw new IllegalStateException(
                            "There may only be one child with layout_childType"
                            + "=\"expandChallengeHandle\"");
                }
                mExpandChallengeView = child;
                if (mExpandChallengeView != oldExpandChallengeView) {
                    mExpandChallengeView.setVisibility(mChallengeShowing ? INVISIBLE : VISIBLE);
                    mExpandChallengeView.setOnClickListener(mExpandChallengeClickListener);
                }
            } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
                setScrimView(child);
            } else if (lp.childType == LayoutParams.CHILD_TYPE_WIDGETS) {
                mWidgetsView = child;
            }
        }

        // We want to measure the challenge view first, since the KeyguardWidgetPager
        // needs to do things its measure pass that are dependent on the challenge view
        // having been measured.
        if (mChallengeView != null && mChallengeView.getVisibility() != View.GONE) {
            // This one's a little funny. If the IME is present - reported in the form
            // of insets on the root view - we only give the challenge the space it would
            // have had if the IME wasn't there in order to keep the rest of the layout stable.
            // We base this on the layout_maxHeight on the challenge view. If it comes out
            // negative or zero, either we didn't have a maxHeight or we're totally out of space,
            // so give up and measure as if this rule weren't there.
            int challengeHeightSpec = insetHeightSpec;
            final View root = getRootView();
            if (root != null) {
                final LayoutParams lp = (LayoutParams) mChallengeView.getLayoutParams();
                final int windowHeight = mDisplayMetrics.heightPixels
                        - root.getPaddingTop() - mInsets.top;
                final int diff = windowHeight - insetHeight;
                final int maxChallengeHeight = lp.maxHeight - diff;
                if (maxChallengeHeight > 0) {
                    challengeHeightSpec = makeChildMeasureSpec(maxChallengeHeight, lp.height);
                }
            }
            measureChildWithMargins(mChallengeView, widthSpec, 0, challengeHeightSpec, 0);
        }

        // Measure the rest of the children
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            // Don't measure the challenge view twice!
            if (child == mChallengeView) continue;

            // Measure children. Widget frame measures special, so that we can ignore
            // insets for the IME.
            int parentWidthSpec = widthSpec, parentHeightSpec = insetHeightSpec;
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (lp.childType == LayoutParams.CHILD_TYPE_WIDGETS) {
                final View root = getRootView();
                if (root != null) {
                    // This calculation is super dodgy and relies on several assumptions.
                    // Specifically that the root of the window will be padded in for insets
                    // and that the window is LAYOUT_IN_SCREEN.
                    final int windowWidth = mDisplayMetrics.widthPixels;
                    final int windowHeight = mDisplayMetrics.heightPixels
                            - root.getPaddingTop() - mInsets.top;
                    parentWidthSpec = MeasureSpec.makeMeasureSpec(
                            windowWidth, MeasureSpec.EXACTLY);
                    parentHeightSpec = MeasureSpec.makeMeasureSpec(
                            windowHeight, MeasureSpec.EXACTLY);
                }
            } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
                // Allow scrim views to extend into the insets
                parentWidthSpec = widthSpec;
                parentHeightSpec = heightSpec;
            }
            measureChildWithMargins(child, parentWidthSpec, 0, parentHeightSpec, 0);
        }
    
protected booleanonRequestFocusInDescendants(int direction, android.graphics.Rect previouslyFocusedRect)

        // Focus security fileds before widgets.
        if (mChallengeView != null &&
                mChallengeView.requestFocus(direction, previouslyFocusedRect)) {
            return true;
        }
        return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
    
public booleanonTouchEvent(android.view.MotionEvent ev)

        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);

        final int action = ev.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mBlockDrag = false;
                mGestureStartX = ev.getX();
                mGestureStartY = ev.getY();
                break;

            case MotionEvent.ACTION_CANCEL:
                if (mDragging && !isChallengeInteractionBlocked()) {
                    showChallenge(0);
                }
                resetTouch();
                break;

            case MotionEvent.ACTION_POINTER_UP:
                if (mActivePointerId != ev.getPointerId(ev.getActionIndex())) {
                    break;
                }
            case MotionEvent.ACTION_UP:
                if (mDragging && !isChallengeInteractionBlocked()) {
                    mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
                    showChallenge((int) mVelocityTracker.getYVelocity(mActivePointerId));
                }
                resetTouch();
                break;

            case MotionEvent.ACTION_MOVE:
                if (!mDragging && !mBlockDrag && !mIsBouncing) {
                    final int count = ev.getPointerCount();
                    for (int i = 0; i < count; i++) {
                        final float x = ev.getX(i);
                        final float y = ev.getY(i);

                        if ((isInDragHandle(x, y) || crossedDragHandle(x, y, mGestureStartY) ||
                                (isInChallengeView(x, y) && mScrollState == SCROLL_STATE_SETTLING))
                                && mActivePointerId == INVALID_POINTER
                                && !isChallengeInteractionBlocked()) {
                            mGestureStartX = x;
                            mGestureStartY = y;
                            mActivePointerId = ev.getPointerId(i);
                            mGestureStartChallengeBottom = getChallengeBottom();
                            mDragging = true;
                            enableHardwareLayerForChallengeView();
                            break;
                        }
                    }
                }
                // Not an else; this can be set above.
                if (mDragging) {
                    // No-op if already in this state, but set it here in case we arrived
                    // at this point from either intercept or the above.
                    setScrollState(SCROLL_STATE_DRAGGING);

                    final int index = ev.findPointerIndex(mActivePointerId);
                    if (index < 0) {
                        // Oops, bogus state. We lost some touch events somewhere.
                        // Just drop it with no velocity and let things settle.
                        resetTouch();
                        showChallenge(0);
                        return true;
                    }
                    final float y = ev.getY(index);
                    final float pos = Math.min(y - mGestureStartY,
                            getLayoutBottom() - mChallengeBottomBound);

                    moveChallengeTo(mGestureStartChallengeBottom + (int) pos);
                }
                break;
        }
        return true;
    
public voidrequestChildFocus(android.view.View child, android.view.View focused)

        if (mIsBouncing && child != mChallengeView) {
            // Clear out of the bouncer if the user tries to move focus outside of
            // the security challenge view.
            hideBouncer();
        }
        super.requestChildFocus(child, focused);
    
public voidrequestDisallowInterceptTouchEvent(boolean allowIntercept)

        // We'll intercept whoever we feel like! ...as long as it isn't a challenge view.
        // If there are one or more pointers in the challenge view before we take over
        // touch events, onInterceptTouchEvent will set mBlockDrag.
    
private voidresetTouch()

        mVelocityTracker.recycle();
        mVelocityTracker = null;
        mActivePointerId = INVALID_POINTER;
        mDragging = mBlockDrag = false;
    
private voidsendInitialListenerUpdates()

        if (mScrollListener != null) {
            int challengeTop = mChallengeView != null ? mChallengeView.getTop() : 0;
            mScrollListener.onScrollPositionChanged(mChallengeOffset, challengeTop);
            mScrollListener.onScrollStateChanged(mScrollState);
        }
    
public voidsetChallengeInteractive(boolean interactive)

        mChallengeInteractiveExternal = interactive;
        if (mExpandChallengeView != null) {
            mExpandChallengeView.setEnabled(interactive);
        }
    
private voidsetChallengeShowing(boolean showChallenge)

        if (mChallengeShowing == showChallenge) {
            return;
        }
        mChallengeShowing = showChallenge;

        if (mExpandChallengeView == null || mChallengeView == null) {
            // These might not be here yet if we haven't been through layout.
            // If we haven't, the first layout pass will set everything up correctly
            // based on mChallengeShowing as set above.
            return;
        }

        if (mChallengeShowing) {
            mExpandChallengeView.setVisibility(View.INVISIBLE);
            mChallengeView.setVisibility(View.VISIBLE);
            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                mChallengeView.requestAccessibilityFocus();
                mChallengeView.announceForAccessibility(mContext.getString(
                        R.string.keyguard_accessibility_unlock_area_expanded));
            }
        } else {
            mExpandChallengeView.setVisibility(View.VISIBLE);
            mChallengeView.setVisibility(View.INVISIBLE);
            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
                mExpandChallengeView.requestAccessibilityFocus();
                mChallengeView.announceForAccessibility(mContext.getString(
                        R.string.keyguard_accessibility_unlock_area_collapsed));
            }
        }
    
public voidsetEnableChallengeDragging(boolean enabled)

        mEnableChallengeDragging = enabled;
    
public voidsetHandleAlpha(float alpha)

        if (mExpandChallengeView != null) {
            mExpandChallengeView.setAlpha(alpha);
        }
    
public voidsetInsets(android.graphics.Rect insets)

        mInsets.set(insets);
    
public voidsetOnBouncerStateChangedListener(OnBouncerStateChangedListener listener)

        mBouncerListener = listener;
    
public voidsetOnChallengeScrolledListener(com.android.keyguard.SlidingChallengeLayout$OnChallengeScrolledListener listener)

        mScrollListener = listener;
        if (mHasLayout) {
            sendInitialListenerUpdates();
        }
    
voidsetScrimView(android.view.View scrim)

        if (mScrimView != null) {
            mScrimView.setOnClickListener(null);
        }
        mScrimView = scrim;
        if (mScrimView != null) {
            mScrimView.setVisibility(mIsBouncing ? VISIBLE : GONE);
            mScrimView.setFocusable(true);
            mScrimView.setOnClickListener(mScrimClickListener);
        }
    
voidsetScrollState(int state)

        if (mScrollState != state) {
            mScrollState = state;

            animateHandle(state == SCROLL_STATE_IDLE && !mChallengeShowing);
            if (mScrollListener != null) {
                mScrollListener.onScrollStateChanged(state);
            }
        }
    
private booleanshouldEnableChallengeDragging()

        return mEnableChallengeDragging || !mChallengeShowing;
    
public voidshowBouncer()

        if (mIsBouncing) return;
        setSystemUiVisibility(getSystemUiVisibility() | STATUS_BAR_DISABLE_SEARCH);
        mWasChallengeShowing = mChallengeShowing;
        mIsBouncing = true;
        showChallenge(true);
        if (mScrimView != null) {
            Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 1f);
            anim.setDuration(HANDLE_ANIMATE_DURATION);
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationStart(Animator animation) {
                    mScrimView.setVisibility(VISIBLE);
                }
            });
            anim.start();
        }
        if (mChallengeView != null) {
            mChallengeView.showBouncer(HANDLE_ANIMATE_DURATION);
        }

        if (mBouncerListener != null) {
            mBouncerListener.onBouncerStateChanged(true);
        }
    
public voidshowChallenge(boolean show)
Show or hide the challenge view, animating it if necessary.

param
show true to show, false to hide

        showChallenge(show, 0);
        if (!show) {
            // Block any drags in progress so that callers can use this to disable dragging
            // for other touch interactions.
            mBlockDrag = true;
        }
    
private voidshowChallenge(int velocity)

        boolean show = false;
        if (Math.abs(velocity) > mMinVelocity) {
            show = velocity < 0;
        } else {
            show = mChallengeOffset >= 0.5f;
        }
        showChallenge(show, velocity);
    
private voidshowChallenge(boolean show, int velocity)

        if (mChallengeView == null) {
            setChallengeShowing(false);
            return;
        }

        if (mHasLayout) {
            mChallengeShowingTargetState = show;
            final int layoutBottom = getLayoutBottom();
            animateChallengeTo(show ? layoutBottom :
                    layoutBottom + mChallengeView.getHeight() - mChallengeBottomBound, velocity);
        }