NotificationStackScrollLayout.java revision fab078b01fbad026f006744016272327f7ab116b
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.systemui.statusbar.stack;
18
19import android.content.Context;
20import android.content.res.Configuration;
21
22import android.graphics.Canvas;
23import android.graphics.Outline;
24import android.graphics.Paint;
25import android.util.AttributeSet;
26import android.util.Log;
27
28import android.view.MotionEvent;
29import android.view.VelocityTracker;
30import android.view.View;
31import android.view.ViewConfiguration;
32import android.view.ViewGroup;
33import android.view.ViewParent;
34import android.widget.OverScroller;
35
36import com.android.systemui.ExpandHelper;
37import com.android.systemui.R;
38import com.android.systemui.SwipeHelper;
39import com.android.systemui.statusbar.ExpandableNotificationRow;
40
41/**
42 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
43 */
44public class NotificationStackScrollLayout extends ViewGroup
45        implements SwipeHelper.Callback, ExpandHelper.Callback, ExpandHelper.ScrollAdapter {
46
47    private static final String TAG = "NotificationStackScrollLayout";
48    private static final boolean DEBUG = false;
49
50    /**
51     * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
52     */
53    private static final int INVALID_POINTER = -1;
54
55    private SwipeHelper mSwipeHelper;
56    private boolean mAllowScrolling = true;
57    private int mCurrentStackHeight = Integer.MAX_VALUE;
58    private int mOwnScrollY;
59    private int mMaxLayoutHeight;
60
61    private VelocityTracker mVelocityTracker;
62    private OverScroller mScroller;
63    private int mTouchSlop;
64    private int mMinimumVelocity;
65    private int mMaximumVelocity;
66    private int mOverscrollDistance;
67    private int mOverflingDistance;
68    private boolean mIsBeingDragged;
69    private int mLastMotionY;
70    private int mActivePointerId;
71
72    private int mSidePaddings;
73    private Paint mDebugPaint;
74    private int mBackgroundRoundedRectCornerRadius;
75    private int mContentHeight;
76    private int mCollapsedSize;
77    private int mBottomStackPeekSize;
78    private int mEmptyMarginBottom;
79    private int mPaddingBetweenElements;
80
81    /**
82     * The algorithm which calculates the properties for our children
83     */
84    private StackScrollAlgorithm mStackScrollAlgorithm;
85
86    /**
87     * The current State this Layout is in
88     */
89    private StackScrollState mCurrentStackScrollState;
90
91    public NotificationStackScrollLayout(Context context) {
92        this(context, null);
93    }
94
95    public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
96        this(context, attrs, 0);
97    }
98
99    public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
100        this(context, attrs, defStyleAttr, 0);
101    }
102
103    public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
104            int defStyleRes) {
105        super(context, attrs, defStyleAttr, defStyleRes);
106        initView(context);
107        if (DEBUG) {
108            setWillNotDraw(false);
109            mDebugPaint = new Paint();
110            mDebugPaint.setColor(0xffff0000);
111            mDebugPaint.setStrokeWidth(2);
112            mDebugPaint.setStyle(Paint.Style.STROKE);
113        }
114    }
115
116    @Override
117    protected void onDraw(Canvas canvas) {
118        if (DEBUG) {
119            int y = mCollapsedSize;
120            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
121            y = (int) (getLayoutHeight() - mBottomStackPeekSize - mCollapsedSize);
122            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
123            y = (int) getLayoutHeight();
124            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
125        }
126    }
127
128    private void initView(Context context) {
129        mScroller = new OverScroller(getContext());
130        setFocusable(true);
131        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
132        final ViewConfiguration configuration = ViewConfiguration.get(context);
133        mTouchSlop = configuration.getScaledTouchSlop();
134        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
135        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
136        mOverscrollDistance = configuration.getScaledOverscrollDistance();
137        mOverflingDistance = configuration.getScaledOverflingDistance();
138        float densityScale = getResources().getDisplayMetrics().density;
139        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
140        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
141
142        mSidePaddings = context.getResources()
143                .getDimensionPixelSize(R.dimen.notification_side_padding);
144        mBackgroundRoundedRectCornerRadius = context.getResources()
145                .getDimensionPixelSize(
146                        com.android.internal.R.dimen.notification_quantum_rounded_rect_radius);
147        mCollapsedSize = context.getResources()
148                .getDimensionPixelSize(R.dimen.notification_row_min_height);
149        mBottomStackPeekSize = context.getResources()
150                .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
151        mEmptyMarginBottom = context.getResources().getDimensionPixelSize(
152                R.dimen.notification_stack_margin_bottom);
153        // currently the padding is in the elements themself
154        mPaddingBetweenElements = 0;
155        mStackScrollAlgorithm = new StackScrollAlgorithm(context);
156        mCurrentStackScrollState = null;
157    }
158
159    @Override
160    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
161        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
162        int mode = MeasureSpec.getMode(widthMeasureSpec);
163        int size = MeasureSpec.getSize(widthMeasureSpec);
164        int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
165        measureChildren(childMeasureSpec, heightMeasureSpec);
166    }
167
168    @Override
169    protected void onLayout(boolean changed, int l, int t, int r, int b) {
170
171        // we layout all our children centered on the top
172        float centerX = getWidth() / 2.0f;
173        for (int i = 0; i < getChildCount(); i++) {
174            View child = getChildAt(i);
175            float width = child.getMeasuredWidth();
176            float height = child.getMeasuredHeight();
177            int oldWidth = child.getWidth();
178            int oldHeight = child.getHeight();
179            child.layout((int) (centerX - width / 2.0f),
180                    0,
181                    (int) (centerX + width / 2.0f),
182                    (int) height);
183            updateChildOutline(child, width, height, oldWidth, oldHeight);
184        }
185        setMaxLayoutHeight(getHeight() - mEmptyMarginBottom);
186        updateScrollPositionIfNecessary();
187        updateChildren();
188        updateContentHeight();
189    }
190
191    private void setMaxLayoutHeight(int maxLayoutHeight) {
192        mMaxLayoutHeight = maxLayoutHeight;
193        updateAlgorithmHeight();
194    }
195
196    private void updateAlgorithmHeight() {
197        mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
198    }
199
200    /**
201     * Updates the children views according to the stack scroll algorithm. Call this whenever
202     * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
203     */
204    private void updateChildren() {
205        if (!isCurrentlyAnimating()) {
206            if (mCurrentStackScrollState == null) {
207                mCurrentStackScrollState = new StackScrollState(this);
208            }
209            mCurrentStackScrollState.setScrollY(mOwnScrollY);
210            mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState);
211            mCurrentStackScrollState.apply();
212            mOwnScrollY = mCurrentStackScrollState.getScrollY();
213        } else {
214            // TODO: handle animation
215        }
216    }
217
218    private boolean isCurrentlyAnimating() {
219        return false;
220    }
221
222    private void updateChildOutline(View child,
223                                    float width,
224                                    float height,
225                                    int oldWidth,
226                                    int oldHeight) {
227        // The children currently have paddings inside themselfs because of the expansion
228        // visualization. In order for the shadows to work correctly we have to set the correct
229        // outline.
230        View container = child.findViewById(R.id.container);
231        if (container != null && (oldWidth != width || oldHeight != height)) {
232            Outline outline = getOutlineForSize(container.getLeft(),
233                    container.getTop(),
234                    container.getWidth(),
235                    container.getHeight());
236            child.setOutline(outline);
237        }
238    }
239
240    private Outline getOutlineForSize(int leftInset, int topInset, int width, int height) {
241        Outline result = new Outline();
242        result.setRoundRect(leftInset, topInset, leftInset + width, topInset + height,
243                mBackgroundRoundedRectCornerRadius);
244        return result;
245    }
246
247    private void updateScrollPositionIfNecessary() {
248        int scrollRange = getScrollRange();
249        if (scrollRange < mOwnScrollY) {
250            mOwnScrollY = scrollRange;
251        }
252    }
253
254    public void setCurrentStackHeight(int currentStackHeight) {
255        this.mCurrentStackHeight = currentStackHeight;
256        updateAlgorithmHeight();
257        updateChildren();
258    }
259
260    /**
261     * Get the current height of the view. This is at most the size of the view given by a the
262     * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
263     *
264     * @return either the layout height or the externally defined height, whichever is smaller
265     */
266    private float getLayoutHeight() {
267        return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
268    }
269
270    public void setLongPressListener(View.OnLongClickListener listener) {
271        mSwipeHelper.setLongPressListener(listener);
272    }
273
274    public void onChildDismissed(View v) {
275        if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
276        final View veto = v.findViewById(R.id.veto);
277        if (veto != null && veto.getVisibility() != View.GONE) {
278            veto.performClick();
279        }
280        allowScrolling(true);
281    }
282
283    public void onBeginDrag(View v) {
284        allowScrolling(false);
285    }
286
287    public void onDragCancelled(View v) {
288        allowScrolling(true);
289    }
290
291    public View getChildAtPosition(MotionEvent ev) {
292        return getChildAtPosition(ev.getX(), ev.getY());
293    }
294
295    public View getChildAtRawPosition(float touchX, float touchY) {
296        int[] location = new int[2];
297        getLocationOnScreen(location);
298        return getChildAtPosition(touchX - location[0],touchY - location[1]);
299    }
300
301    public View getChildAtPosition(float touchX, float touchY) {
302        // find the view under the pointer, accounting for GONE views
303        final int count = getChildCount();
304        for (int childIdx = 0; childIdx < count; childIdx++) {
305            View slidingChild = getChildAt(childIdx);
306            if (slidingChild.getVisibility() == GONE) {
307                continue;
308            }
309            float top = slidingChild.getTranslationY();
310            float bottom = top + slidingChild.getMeasuredHeight();
311            int left = slidingChild.getLeft();
312            int right = slidingChild.getRight();
313
314            if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
315                return slidingChild;
316            }
317        }
318        return null;
319    }
320
321    public boolean canChildBeExpanded(View v) {
322        return v instanceof ExpandableNotificationRow
323                && ((ExpandableNotificationRow) v).isExpandable();
324    }
325
326    public void setUserExpandedChild(View v, boolean userExpanded) {
327        if (v instanceof ExpandableNotificationRow) {
328            ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
329        }
330    }
331
332    public void setUserLockedChild(View v, boolean userLocked) {
333        if (v instanceof ExpandableNotificationRow) {
334            ((ExpandableNotificationRow) v).setUserLocked(userLocked);
335        }
336    }
337
338    public View getChildContentView(View v) {
339        return v;
340    }
341
342    public boolean canChildBeDismissed(View v) {
343        final View veto = v.findViewById(R.id.veto);
344        return (veto != null && veto.getVisibility() != View.GONE);
345    }
346
347    private void allowScrolling(boolean allow) {
348        mAllowScrolling = allow;
349    }
350
351    @Override
352    protected void onConfigurationChanged(Configuration newConfig) {
353        super.onConfigurationChanged(newConfig);
354        float densityScale = getResources().getDisplayMetrics().density;
355        mSwipeHelper.setDensityScale(densityScale);
356        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
357        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
358        initView(getContext());
359    }
360
361    public void dismissRowAnimated(View child, int vel) {
362        mSwipeHelper.dismissChild(child, vel);
363    }
364
365    @Override
366    public boolean onTouchEvent(MotionEvent ev) {
367        boolean scrollerWantsIt = false;
368        if (mAllowScrolling) {
369            scrollerWantsIt = onScrollTouch(ev);
370        }
371        boolean horizontalSwipeWantsIt = false;
372        if (!mIsBeingDragged) {
373            horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
374        }
375        return horizontalSwipeWantsIt || scrollerWantsIt || super.onTouchEvent(ev);
376    }
377
378    private boolean onScrollTouch(MotionEvent ev) {
379        initVelocityTrackerIfNotExists();
380        mVelocityTracker.addMovement(ev);
381
382        final int action = ev.getAction();
383
384        switch (action & MotionEvent.ACTION_MASK) {
385            case MotionEvent.ACTION_DOWN: {
386                if (getChildCount() == 0) {
387                    return false;
388                }
389                boolean isBeingDragged = !mScroller.isFinished();
390                setIsBeingDragged(isBeingDragged);
391                if (isBeingDragged) {
392                    final ViewParent parent = getParent();
393                    if (parent != null) {
394                        parent.requestDisallowInterceptTouchEvent(true);
395                    }
396                }
397
398                /*
399                 * If being flinged and user touches, stop the fling. isFinished
400                 * will be false if being flinged.
401                 */
402                if (!mScroller.isFinished()) {
403                    mScroller.abortAnimation();
404                }
405
406                // Remember where the motion event started
407                mLastMotionY = (int) ev.getY();
408                mActivePointerId = ev.getPointerId(0);
409                break;
410            }
411            case MotionEvent.ACTION_MOVE:
412                final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
413                if (activePointerIndex == -1) {
414                    Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
415                    break;
416                }
417
418                final int y = (int) ev.getY(activePointerIndex);
419                int deltaY = mLastMotionY - y;
420                if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
421                    final ViewParent parent = getParent();
422                    if (parent != null) {
423                        parent.requestDisallowInterceptTouchEvent(true);
424                    }
425                    setIsBeingDragged(true);
426                    if (deltaY > 0) {
427                        deltaY -= mTouchSlop;
428                    } else {
429                        deltaY += mTouchSlop;
430                    }
431                }
432                if (mIsBeingDragged) {
433                    // Scroll to follow the motion event
434                    mLastMotionY = y;
435
436                    final int oldX = mScrollX;
437                    final int oldY = mOwnScrollY;
438                    final int range = getScrollRange();
439                    final int overscrollMode = getOverScrollMode();
440                    final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
441                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
442
443                    // Calling overScrollBy will call onOverScrolled, which
444                    // calls onScrollChanged if applicable.
445                    if (overScrollBy(0, deltaY, 0, mOwnScrollY,
446                            0, range, 0, mOverscrollDistance, true)) {
447                        // Break our velocity if we hit a scroll barrier.
448                        mVelocityTracker.clear();
449                    }
450                    // TODO: Overscroll
451//                    if (canOverscroll) {
452//                        final int pulledToY = oldY + deltaY;
453//                        if (pulledToY < 0) {
454//                            mEdgeGlowTop.onPull((float) deltaY / getHeight());
455//                            if (!mEdgeGlowBottom.isFinished()) {
456//                                mEdgeGlowBottom.onRelease();
457//                            }
458//                        } else if (pulledToY > range) {
459//                            mEdgeGlowBottom.onPull((float) deltaY / getHeight());
460//                            if (!mEdgeGlowTop.isFinished()) {
461//                                mEdgeGlowTop.onRelease();
462//                            }
463//                        }
464//                        if (mEdgeGlowTop != null
465//                                && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())){
466//                            postInvalidateOnAnimation();
467//                        }
468//                    }
469                }
470                break;
471            case MotionEvent.ACTION_UP:
472                if (mIsBeingDragged) {
473                    final VelocityTracker velocityTracker = mVelocityTracker;
474                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
475                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
476
477                    if (getChildCount() > 0) {
478                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
479                            fling(-initialVelocity);
480                        } else {
481                            if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
482                                    getScrollRange())) {
483                                postInvalidateOnAnimation();
484                            }
485                        }
486                    }
487
488                    mActivePointerId = INVALID_POINTER;
489                    endDrag();
490                }
491                break;
492            case MotionEvent.ACTION_CANCEL:
493                if (mIsBeingDragged && getChildCount() > 0) {
494                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
495                        postInvalidateOnAnimation();
496                    }
497                    mActivePointerId = INVALID_POINTER;
498                    endDrag();
499                }
500                break;
501            case MotionEvent.ACTION_POINTER_DOWN: {
502                final int index = ev.getActionIndex();
503                mLastMotionY = (int) ev.getY(index);
504                mActivePointerId = ev.getPointerId(index);
505                break;
506            }
507            case MotionEvent.ACTION_POINTER_UP:
508                onSecondaryPointerUp(ev);
509                mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
510                break;
511        }
512        return true;
513    }
514
515    private void onSecondaryPointerUp(MotionEvent ev) {
516        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
517                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
518        final int pointerId = ev.getPointerId(pointerIndex);
519        if (pointerId == mActivePointerId) {
520            // This was our active pointer going up. Choose a new
521            // active pointer and adjust accordingly.
522            // TODO: Make this decision more intelligent.
523            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
524            mLastMotionY = (int) ev.getY(newPointerIndex);
525            mActivePointerId = ev.getPointerId(newPointerIndex);
526            if (mVelocityTracker != null) {
527                mVelocityTracker.clear();
528            }
529        }
530    }
531
532    private void initVelocityTrackerIfNotExists() {
533        if (mVelocityTracker == null) {
534            mVelocityTracker = VelocityTracker.obtain();
535        }
536    }
537
538    private void recycleVelocityTracker() {
539        if (mVelocityTracker != null) {
540            mVelocityTracker.recycle();
541            mVelocityTracker = null;
542        }
543    }
544
545    private void initOrResetVelocityTracker() {
546        if (mVelocityTracker == null) {
547            mVelocityTracker = VelocityTracker.obtain();
548        } else {
549            mVelocityTracker.clear();
550        }
551    }
552
553    @Override
554    public void computeScroll() {
555        if (mScroller.computeScrollOffset()) {
556            // This is called at drawing time by ViewGroup.
557            int oldX = mScrollX;
558            int oldY = mOwnScrollY;
559            int x = mScroller.getCurrX();
560            int y = mScroller.getCurrY();
561
562            if (oldX != x || oldY != y) {
563                final int range = getScrollRange();
564                final int overscrollMode = getOverScrollMode();
565                final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
566                        (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
567
568                overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
569                        0, mOverflingDistance, false);
570                onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
571
572                if (canOverscroll) {
573                    // TODO: Overscroll
574//                    if (y < 0 && oldY >= 0) {
575//                        mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
576//                    } else if (y > range && oldY <= range) {
577//                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
578//                    }
579                }
580                updateChildren();
581            }
582
583            // Keep on drawing until the animation has finished.
584            postInvalidateOnAnimation();
585        }
586    }
587
588    public void customScrollBy(int y) {
589        mOwnScrollY += y;
590        updateChildren();
591    }
592
593    public void customScrollTo(int y) {
594        mOwnScrollY = y;
595        updateChildren();
596    }
597
598    @Override
599    protected void onOverScrolled(int scrollX, int scrollY,
600                                  boolean clampedX, boolean clampedY) {
601        // Treat animating scrolls differently; see #computeScroll() for why.
602        if (!mScroller.isFinished()) {
603            final int oldX = mScrollX;
604            final int oldY = mOwnScrollY;
605            mScrollX = scrollX;
606            mOwnScrollY = scrollY;
607            invalidateParentIfNeeded();
608            onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
609            if (clampedY) {
610                mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange());
611            }
612            updateChildren();
613        } else {
614            customScrollTo(scrollY);
615            scrollTo(scrollX, mScrollY);
616        }
617    }
618
619    private int getScrollRange() {
620        int scrollRange = 0;
621        if (getChildCount() > 0) {
622            int contentHeight = getContentHeight();
623            scrollRange = Math.max(0,
624                    contentHeight - mMaxLayoutHeight + mCollapsedSize);
625        }
626        return scrollRange;
627    }
628
629    private int getContentHeight() {
630        return mContentHeight;
631    }
632
633    private void updateContentHeight() {
634        int height = 0;
635        for (int i = 0; i < getChildCount(); i++) {
636            View child = getChildAt(i);
637            height += child.getHeight();
638            if (i < getChildCount()-1) {
639                height += mPaddingBetweenElements;
640            }
641        }
642        mContentHeight = height;
643    }
644
645    /**
646     * Fling the scroll view
647     *
648     * @param velocityY The initial velocity in the Y direction. Positive
649     *                  numbers mean that the finger/cursor is moving down the screen,
650     *                  which means we want to scroll towards the top.
651     */
652    private void fling(int velocityY) {
653        if (getChildCount() > 0) {
654            int height = (int) getLayoutHeight();
655            int bottom = getContentHeight();
656
657            mScroller.fling(mScrollX, mOwnScrollY, 0, velocityY, 0, 0, 0,
658                    Math.max(0, bottom - height), 0, height/2);
659
660            postInvalidateOnAnimation();
661        }
662    }
663
664    private void endDrag() {
665        setIsBeingDragged(false);
666
667        recycleVelocityTracker();
668
669        // TODO: Overscroll
670//        if (mEdgeGlowTop != null) {
671//            mEdgeGlowTop.onRelease();
672//            mEdgeGlowBottom.onRelease();
673//        }
674    }
675
676    @Override
677    public boolean onInterceptTouchEvent(MotionEvent ev) {
678        boolean scrollWantsIt = false;
679        if (mAllowScrolling) {
680            scrollWantsIt = onInterceptTouchEventScroll(ev);
681        }
682        boolean swipeWantsIt = false;
683        if (!mIsBeingDragged) {
684            swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
685        }
686        return swipeWantsIt || scrollWantsIt ||
687                super.onInterceptTouchEvent(ev);
688    }
689
690    private boolean onInterceptTouchEventScroll(MotionEvent ev) {
691        /*
692         * This method JUST determines whether we want to intercept the motion.
693         * If we return true, onMotionEvent will be called and we do the actual
694         * scrolling there.
695         */
696
697        /*
698        * Shortcut the most recurring case: the user is in the dragging
699        * state and he is moving his finger.  We want to intercept this
700        * motion.
701        */
702        final int action = ev.getAction();
703        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
704            return true;
705        }
706
707        /*
708         * Don't try to intercept touch if we can't scroll anyway.
709         */
710        if (mOwnScrollY == 0 && getScrollRange() == 0) {
711            return false;
712        }
713
714        switch (action & MotionEvent.ACTION_MASK) {
715            case MotionEvent.ACTION_MOVE: {
716                /*
717                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
718                 * whether the user has moved far enough from his original down touch.
719                 */
720
721                /*
722                * Locally do absolute value. mLastMotionY is set to the y value
723                * of the down event.
724                */
725                final int activePointerId = mActivePointerId;
726                if (activePointerId == INVALID_POINTER) {
727                    // If we don't have a valid id, the touch down wasn't on content.
728                    break;
729                }
730
731                final int pointerIndex = ev.findPointerIndex(activePointerId);
732                if (pointerIndex == -1) {
733                    Log.e(TAG, "Invalid pointerId=" + activePointerId
734                            + " in onInterceptTouchEvent");
735                    break;
736                }
737
738                final int y = (int) ev.getY(pointerIndex);
739                final int yDiff = Math.abs(y - mLastMotionY);
740                if (yDiff > mTouchSlop) {
741                    setIsBeingDragged(true);
742                    mLastMotionY = y;
743                    initVelocityTrackerIfNotExists();
744                    mVelocityTracker.addMovement(ev);
745                    final ViewParent parent = getParent();
746                    if (parent != null) {
747                        parent.requestDisallowInterceptTouchEvent(true);
748                    }
749                }
750                break;
751            }
752
753            case MotionEvent.ACTION_DOWN: {
754                final int y = (int) ev.getY();
755                if (getChildAtPosition(ev.getX(), y) == null) {
756                    setIsBeingDragged(false);
757                    recycleVelocityTracker();
758                    break;
759                }
760
761                /*
762                 * Remember location of down touch.
763                 * ACTION_DOWN always refers to pointer index 0.
764                 */
765                mLastMotionY = y;
766                mActivePointerId = ev.getPointerId(0);
767
768                initOrResetVelocityTracker();
769                mVelocityTracker.addMovement(ev);
770                /*
771                * If being flinged and user touches the screen, initiate drag;
772                * otherwise don't.  mScroller.isFinished should be false when
773                * being flinged.
774                */
775                boolean isBeingDragged = !mScroller.isFinished();
776                setIsBeingDragged(isBeingDragged);
777                break;
778            }
779
780            case MotionEvent.ACTION_CANCEL:
781            case MotionEvent.ACTION_UP:
782                /* Release the drag */
783                setIsBeingDragged(false);
784                mActivePointerId = INVALID_POINTER;
785                recycleVelocityTracker();
786                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
787                    postInvalidateOnAnimation();
788                }
789                break;
790            case MotionEvent.ACTION_POINTER_UP:
791                onSecondaryPointerUp(ev);
792                break;
793        }
794
795        /*
796        * The only time we want to intercept motion events is if we are in the
797        * drag mode.
798        */
799        return mIsBeingDragged;
800    }
801
802    private void setIsBeingDragged(boolean isDragged) {
803        mIsBeingDragged = isDragged;
804        if (isDragged) {
805            mSwipeHelper.removeLongPressCallback();
806        }
807    }
808
809    @Override
810    public void onWindowFocusChanged(boolean hasWindowFocus) {
811        super.onWindowFocusChanged(hasWindowFocus);
812        if (!hasWindowFocus) {
813            mSwipeHelper.removeLongPressCallback();
814        }
815    }
816
817    @Override
818    public boolean isScrolledToTop() {
819        return mOwnScrollY == 0;
820    }
821
822    @Override
823    public View getHostView() {
824        return this;
825    }
826}
827