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 
17 package com.android.systemui.statusbar.stack;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.graphics.Canvas;
23 import android.graphics.Paint;
24 import android.graphics.PointF;
25 import android.util.AttributeSet;
26 import android.util.Log;
27 import android.view.MotionEvent;
28 import android.view.VelocityTracker;
29 import android.view.View;
30 import android.view.ViewConfiguration;
31 import android.view.ViewGroup;
32 import android.view.ViewTreeObserver;
33 import android.view.animation.AnimationUtils;
34 import android.widget.OverScroller;
35 
36 import com.android.systemui.ExpandHelper;
37 import com.android.systemui.R;
38 import com.android.systemui.SwipeHelper;
39 import com.android.systemui.statusbar.ActivatableNotificationView;
40 import com.android.systemui.statusbar.DismissView;
41 import com.android.systemui.statusbar.EmptyShadeView;
42 import com.android.systemui.statusbar.ExpandableNotificationRow;
43 import com.android.systemui.statusbar.ExpandableView;
44 import com.android.systemui.statusbar.SpeedBumpView;
45 import com.android.systemui.statusbar.StackScrollerDecorView;
46 import com.android.systemui.statusbar.StatusBarState;
47 import com.android.systemui.statusbar.phone.PhoneStatusBar;
48 import com.android.systemui.statusbar.policy.ScrollAdapter;
49 import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
50 
51 import java.util.ArrayList;
52 import java.util.HashSet;
53 
54 /**
55  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
56  */
57 public class NotificationStackScrollLayout extends ViewGroup
58         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
59         ExpandableView.OnHeightChangedListener {
60 
61     private static final String TAG = "NotificationStackScrollLayout";
62     private static final boolean DEBUG = false;
63     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
64     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
65     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
66 
67     /**
68      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
69      */
70     private static final int INVALID_POINTER = -1;
71 
72     private ExpandHelper mExpandHelper;
73     private SwipeHelper mSwipeHelper;
74     private boolean mSwipingInProgress;
75     private int mCurrentStackHeight = Integer.MAX_VALUE;
76 
77     /**
78      * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
79      * externally from {@link #setStackHeight}
80      */
81     private float mLastSetStackHeight;
82     private int mOwnScrollY;
83     private int mMaxLayoutHeight;
84 
85     private VelocityTracker mVelocityTracker;
86     private OverScroller mScroller;
87     private int mTouchSlop;
88     private int mMinimumVelocity;
89     private int mMaximumVelocity;
90     private int mOverflingDistance;
91     private float mMaxOverScroll;
92     private boolean mIsBeingDragged;
93     private int mLastMotionY;
94     private int mDownX;
95     private int mActivePointerId;
96     private boolean mTouchIsClick;
97     private float mInitialTouchX;
98     private float mInitialTouchY;
99 
100     private int mSidePaddings;
101     private Paint mDebugPaint;
102     private int mContentHeight;
103     private int mCollapsedSize;
104     private int mBottomStackSlowDownHeight;
105     private int mBottomStackPeekSize;
106     private int mPaddingBetweenElements;
107     private int mPaddingBetweenElementsDimmed;
108     private int mPaddingBetweenElementsNormal;
109     private int mTopPadding;
110     private int mCollapseSecondCardPadding;
111 
112     /**
113      * The algorithm which calculates the properties for our children
114      */
115     private StackScrollAlgorithm mStackScrollAlgorithm;
116 
117     /**
118      * The current State this Layout is in
119      */
120     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
121     private AmbientState mAmbientState = new AmbientState();
122     private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
123     private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
124     private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
125     private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
126     private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
127     private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
128     private ArrayList<AnimationEvent> mAnimationEvents
129             = new ArrayList<AnimationEvent>();
130     private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
131     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
132     private boolean mAnimationsEnabled;
133     private boolean mChangePositionInProgress;
134 
135     /**
136      * The raw amount of the overScroll on the top, which is not rubber-banded.
137      */
138     private float mOverScrolledTopPixels;
139 
140     /**
141      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
142      */
143     private float mOverScrolledBottomPixels;
144 
145     private OnChildLocationsChangedListener mListener;
146     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
147     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
148     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
149     private boolean mNeedsAnimation;
150     private boolean mTopPaddingNeedsAnimation;
151     private boolean mDimmedNeedsAnimation;
152     private boolean mHideSensitiveNeedsAnimation;
153     private boolean mDarkNeedsAnimation;
154     private int mDarkAnimationOriginIndex;
155     private boolean mActivateNeedsAnimation;
156     private boolean mGoToFullShadeNeedsAnimation;
157     private boolean mIsExpanded = true;
158     private boolean mChildrenUpdateRequested;
159     private SpeedBumpView mSpeedBumpView;
160     private boolean mIsExpansionChanging;
161     private boolean mExpandingNotification;
162     private boolean mExpandedInThisMotion;
163     private boolean mScrollingEnabled;
164     private DismissView mDismissView;
165     private EmptyShadeView mEmptyShadeView;
166     private boolean mDismissAllInProgress;
167 
168     /**
169      * Was the scroller scrolled to the top when the down motion was observed?
170      */
171     private boolean mScrolledToTopOnFirstDown;
172 
173     /**
174      * The minimal amount of over scroll which is needed in order to switch to the quick settings
175      * when over scrolling on a expanded card.
176      */
177     private float mMinTopOverScrollToEscape;
178     private int mIntrinsicPadding;
179     private int mNotificationTopPadding;
180     private float mTopPaddingOverflow;
181     private boolean mDontReportNextOverScroll;
182     private boolean mRequestViewResizeAnimationOnLayout;
183     private boolean mNeedViewResizeAnimation;
184     private boolean mEverythingNeedsAnimation;
185 
186     /**
187      * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
188      * This is needed to avoid scrolling too far after the notification was collapsed in the same
189      * motion.
190      */
191     private int mMaxScrollAfterExpand;
192     private SwipeHelper.LongPressListener mLongPressListener;
193 
194     /**
195      * Should in this touch motion only be scrolling allowed? It's true when the scroller was
196      * animating.
197      */
198     private boolean mOnlyScrollingInThisMotion;
199     private ViewGroup mScrollView;
200     private boolean mInterceptDelegateEnabled;
201     private boolean mDelegateToScrollView;
202     private boolean mDisallowScrollingInThisMotion;
203     private long mGoToFullShadeDelay;
204 
205     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
206             = new ViewTreeObserver.OnPreDrawListener() {
207         @Override
208         public boolean onPreDraw() {
209             updateChildren();
210             mChildrenUpdateRequested = false;
211             getViewTreeObserver().removeOnPreDrawListener(this);
212             return true;
213         }
214     };
215     private PhoneStatusBar mPhoneStatusBar;
216     private int[] mTempInt2 = new int[2];
217 
NotificationStackScrollLayout(Context context)218     public NotificationStackScrollLayout(Context context) {
219         this(context, null);
220     }
221 
NotificationStackScrollLayout(Context context, AttributeSet attrs)222     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
223         this(context, attrs, 0);
224     }
225 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr)226     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
227         this(context, attrs, defStyleAttr, 0);
228     }
229 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)230     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
231             int defStyleRes) {
232         super(context, attrs, defStyleAttr, defStyleRes);
233         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
234         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
235         mExpandHelper = new ExpandHelper(getContext(), this,
236                 minHeight, maxHeight);
237         mExpandHelper.setEventSource(this);
238         mExpandHelper.setScrollAdapter(this);
239 
240         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
241         mSwipeHelper.setLongPressListener(mLongPressListener);
242         initView(context);
243         if (DEBUG) {
244             setWillNotDraw(false);
245             mDebugPaint = new Paint();
246             mDebugPaint.setColor(0xffff0000);
247             mDebugPaint.setStrokeWidth(2);
248             mDebugPaint.setStyle(Paint.Style.STROKE);
249         }
250     }
251 
252     @Override
onDraw(Canvas canvas)253     protected void onDraw(Canvas canvas) {
254         if (DEBUG) {
255             int y = mCollapsedSize;
256             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
257             y = (int) (getLayoutHeight() - mBottomStackPeekSize
258                     - mBottomStackSlowDownHeight);
259             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
260             y = (int) (getLayoutHeight() - mBottomStackPeekSize);
261             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
262             y = (int) getLayoutHeight();
263             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
264             y = getHeight() - getEmptyBottomMargin();
265             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
266         }
267     }
268 
initView(Context context)269     private void initView(Context context) {
270         mScroller = new OverScroller(getContext());
271         setFocusable(true);
272         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
273         setClipChildren(false);
274         final ViewConfiguration configuration = ViewConfiguration.get(context);
275         mTouchSlop = configuration.getScaledTouchSlop();
276         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
277         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
278         mOverflingDistance = configuration.getScaledOverflingDistance();
279 
280         mSidePaddings = context.getResources()
281                 .getDimensionPixelSize(R.dimen.notification_side_padding);
282         mCollapsedSize = context.getResources()
283                 .getDimensionPixelSize(R.dimen.notification_min_height);
284         mBottomStackPeekSize = context.getResources()
285                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
286         mStackScrollAlgorithm = new StackScrollAlgorithm(context);
287         mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed());
288         mPaddingBetweenElementsDimmed = context.getResources()
289                 .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
290         mPaddingBetweenElementsNormal = context.getResources()
291                 .getDimensionPixelSize(R.dimen.notification_padding);
292         updatePadding(mAmbientState.isDimmed());
293         mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
294                 R.dimen.min_top_overscroll_to_qs);
295         mNotificationTopPadding = getResources().getDimensionPixelSize(
296                 R.dimen.notifications_top_padding);
297         mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
298                 R.dimen.notification_collapse_second_card_padding);
299     }
300 
updatePadding(boolean dimmed)301     private void updatePadding(boolean dimmed) {
302         mPaddingBetweenElements = dimmed && mStackScrollAlgorithm.shouldScaleDimmed()
303                 ? mPaddingBetweenElementsDimmed
304                 : mPaddingBetweenElementsNormal;
305         mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
306         updateContentHeight();
307         notifyHeightChangeListener(null);
308     }
309 
notifyHeightChangeListener(ExpandableView view)310     private void notifyHeightChangeListener(ExpandableView view) {
311         if (mOnHeightChangedListener != null) {
312             mOnHeightChangedListener.onHeightChanged(view);
313         }
314     }
315 
316     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)317     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
318         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
319         int mode = MeasureSpec.getMode(widthMeasureSpec);
320         int size = MeasureSpec.getSize(widthMeasureSpec);
321         int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
322         measureChildren(childMeasureSpec, heightMeasureSpec);
323     }
324 
325     @Override
onLayout(boolean changed, int l, int t, int r, int b)326     protected void onLayout(boolean changed, int l, int t, int r, int b) {
327 
328         // we layout all our children centered on the top
329         float centerX = getWidth() / 2.0f;
330         for (int i = 0; i < getChildCount(); i++) {
331             View child = getChildAt(i);
332             float width = child.getMeasuredWidth();
333             float height = child.getMeasuredHeight();
334             child.layout((int) (centerX - width / 2.0f),
335                     0,
336                     (int) (centerX + width / 2.0f),
337                     (int) height);
338         }
339         setMaxLayoutHeight(getHeight());
340         updateContentHeight();
341         clampScrollPosition();
342         requestAnimationOnViewResize();
343         requestChildrenUpdate();
344     }
345 
requestAnimationOnViewResize()346     private void requestAnimationOnViewResize() {
347         if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) {
348             mNeedViewResizeAnimation = true;
349             mNeedsAnimation = true;
350         }
351         mRequestViewResizeAnimationOnLayout = false;
352     }
353 
updateSpeedBumpIndex(int newIndex)354     public void updateSpeedBumpIndex(int newIndex) {
355         int currentIndex = indexOfChild(mSpeedBumpView);
356 
357         // If we are currently layouted before the new speed bump index, we have to decrease it.
358         boolean validIndex = newIndex > 0;
359         if (newIndex > getChildCount() - 1) {
360             validIndex = false;
361             newIndex = -1;
362         }
363         if (validIndex && currentIndex != newIndex) {
364             changeViewPosition(mSpeedBumpView, newIndex);
365         }
366         updateSpeedBump(validIndex);
367         mAmbientState.setSpeedBumpIndex(newIndex);
368     }
369 
setChildLocationsChangedListener(OnChildLocationsChangedListener listener)370     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
371         mListener = listener;
372     }
373 
374     /**
375      * Returns the location the given child is currently rendered at.
376      *
377      * @param child the child to get the location for
378      * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants
379      */
getChildLocation(View child)380     public int getChildLocation(View child) {
381         ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
382         if (childViewState == null) {
383             return ViewState.LOCATION_UNKNOWN;
384         }
385         if (childViewState.gone) {
386             return ViewState.LOCATION_GONE;
387         }
388         return childViewState.location;
389     }
390 
setMaxLayoutHeight(int maxLayoutHeight)391     private void setMaxLayoutHeight(int maxLayoutHeight) {
392         mMaxLayoutHeight = maxLayoutHeight;
393         updateAlgorithmHeightAndPadding();
394     }
395 
updateAlgorithmHeightAndPadding()396     private void updateAlgorithmHeightAndPadding() {
397         mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
398         mStackScrollAlgorithm.setTopPadding(mTopPadding);
399     }
400 
401     /**
402      * @return whether the height of the layout needs to be adapted, in order to ensure that the
403      *         last child is not in the bottom stack.
404      */
needsHeightAdaption()405     private boolean needsHeightAdaption() {
406         return getNotGoneChildCount() > 1;
407     }
408 
409     /**
410      * Updates the children views according to the stack scroll algorithm. Call this whenever
411      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
412      */
updateChildren()413     private void updateChildren() {
414         mAmbientState.setScrollY(mOwnScrollY);
415         mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
416         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
417             applyCurrentState();
418         } else {
419             startAnimationToState();
420         }
421     }
422 
requestChildrenUpdate()423     private void requestChildrenUpdate() {
424         if (!mChildrenUpdateRequested) {
425             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
426             mChildrenUpdateRequested = true;
427             invalidate();
428         }
429     }
430 
isCurrentlyAnimating()431     private boolean isCurrentlyAnimating() {
432         return mStateAnimator.isRunning();
433     }
434 
clampScrollPosition()435     private void clampScrollPosition() {
436         int scrollRange = getScrollRange();
437         if (scrollRange < mOwnScrollY) {
438             mOwnScrollY = scrollRange;
439         }
440     }
441 
getTopPadding()442     public int getTopPadding() {
443         return mTopPadding;
444     }
445 
setTopPadding(int topPadding, boolean animate)446     private void setTopPadding(int topPadding, boolean animate) {
447         if (mTopPadding != topPadding) {
448             mTopPadding = topPadding;
449             updateAlgorithmHeightAndPadding();
450             updateContentHeight();
451             if (animate && mAnimationsEnabled && mIsExpanded) {
452                 mTopPaddingNeedsAnimation = true;
453                 mNeedsAnimation =  true;
454             }
455             requestChildrenUpdate();
456             notifyHeightChangeListener(null);
457         }
458     }
459 
460     /**
461      * Update the height of the stack to a new height.
462      *
463      * @param height the new height of the stack
464      */
setStackHeight(float height)465     public void setStackHeight(float height) {
466         mLastSetStackHeight = height;
467         setIsExpanded(height > 0.0f);
468         int newStackHeight = (int) height;
469         int minStackHeight = getMinStackHeight();
470         int stackHeight;
471         if (newStackHeight - mTopPadding - mTopPaddingOverflow >= minStackHeight
472                 || getNotGoneChildCount() == 0) {
473             setTranslationY(mTopPaddingOverflow);
474             stackHeight = newStackHeight;
475         } else {
476 
477             // We did not reach the position yet where we actually start growing,
478             // so we translate the stack upwards.
479             int translationY = (newStackHeight - minStackHeight);
480             // A slight parallax effect is introduced in order for the stack to catch up with
481             // the top card.
482             float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
483                     / minStackHeight;
484             partiallyThere = Math.max(0, partiallyThere);
485             translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
486                     mCollapseSecondCardPadding);
487             setTranslationY(translationY - mTopPadding);
488             stackHeight = (int) (height - (translationY - mTopPadding));
489         }
490         if (stackHeight != mCurrentStackHeight) {
491             mCurrentStackHeight = stackHeight;
492             updateAlgorithmHeightAndPadding();
493             requestChildrenUpdate();
494         }
495     }
496 
497     /**
498      * Get the current height of the view. This is at most the msize of the view given by a the
499      * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
500      *
501      * @return either the layout height or the externally defined height, whichever is smaller
502      */
getLayoutHeight()503     private int getLayoutHeight() {
504         return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
505     }
506 
getItemHeight()507     public int getItemHeight() {
508         return mCollapsedSize;
509     }
510 
getBottomStackPeekSize()511     public int getBottomStackPeekSize() {
512         return mBottomStackPeekSize;
513     }
514 
getCollapseSecondCardPadding()515     public int getCollapseSecondCardPadding() {
516         return mCollapseSecondCardPadding;
517     }
518 
setLongPressListener(SwipeHelper.LongPressListener listener)519     public void setLongPressListener(SwipeHelper.LongPressListener listener) {
520         mSwipeHelper.setLongPressListener(listener);
521         mLongPressListener = listener;
522     }
523 
setScrollView(ViewGroup scrollView)524     public void setScrollView(ViewGroup scrollView) {
525         mScrollView = scrollView;
526     }
527 
setInterceptDelegateEnabled(boolean interceptDelegateEnabled)528     public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
529         mInterceptDelegateEnabled = interceptDelegateEnabled;
530     }
531 
onChildDismissed(View v)532     public void onChildDismissed(View v) {
533         if (mDismissAllInProgress) {
534             return;
535         }
536         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
537         final View veto = v.findViewById(R.id.veto);
538         if (veto != null && veto.getVisibility() != View.GONE) {
539             veto.performClick();
540         }
541         setSwipingInProgress(false);
542         if (mDragAnimPendingChildren.contains(v)) {
543             // We start the swipe and finish it in the same frame, we don't want any animation
544             // for the drag
545             mDragAnimPendingChildren.remove(v);
546         }
547         mSwipedOutViews.add(v);
548         mAmbientState.onDragFinished(v);
549     }
550 
551     @Override
onChildSnappedBack(View animView)552     public void onChildSnappedBack(View animView) {
553         mAmbientState.onDragFinished(animView);
554         if (!mDragAnimPendingChildren.contains(animView)) {
555             if (mAnimationsEnabled) {
556                 mSnappedBackChildren.add(animView);
557                 mNeedsAnimation = true;
558             }
559             requestChildrenUpdate();
560         } else {
561             // We start the swipe and snap back in the same frame, we don't want any animation
562             mDragAnimPendingChildren.remove(animView);
563         }
564     }
565 
566     @Override
updateSwipeProgress(View animView, boolean dismissable, float swipeProgress)567     public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
568         return false;
569     }
570 
571     @Override
getFalsingThresholdFactor()572     public float getFalsingThresholdFactor() {
573         return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
574     }
575 
onBeginDrag(View v)576     public void onBeginDrag(View v) {
577         setSwipingInProgress(true);
578         mAmbientState.onBeginDrag(v);
579         if (mAnimationsEnabled) {
580             mDragAnimPendingChildren.add(v);
581             mNeedsAnimation = true;
582         }
583         requestChildrenUpdate();
584     }
585 
onDragCancelled(View v)586     public void onDragCancelled(View v) {
587         setSwipingInProgress(false);
588     }
589 
getChildAtPosition(MotionEvent ev)590     public View getChildAtPosition(MotionEvent ev) {
591         return getChildAtPosition(ev.getX(), ev.getY());
592     }
593 
getClosestChildAtRawPosition(float touchX, float touchY)594     public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
595         getLocationOnScreen(mTempInt2);
596         float localTouchY = touchY - mTempInt2[1];
597 
598         ExpandableView closestChild = null;
599         float minDist = Float.MAX_VALUE;
600 
601         // find the view closest to the location, accounting for GONE views
602         final int count = getChildCount();
603         for (int childIdx = 0; childIdx < count; childIdx++) {
604             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
605             if (slidingChild.getVisibility() == GONE
606                     || slidingChild instanceof StackScrollerDecorView
607                     || slidingChild == mSpeedBumpView) {
608                 continue;
609             }
610             float childTop = slidingChild.getTranslationY();
611             float top = childTop + slidingChild.getClipTopAmount();
612             float bottom = childTop + slidingChild.getActualHeight();
613 
614             float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
615             if (dist < minDist) {
616                 closestChild = slidingChild;
617                 minDist = dist;
618             }
619         }
620         return closestChild;
621     }
622 
getChildAtRawPosition(float touchX, float touchY)623     public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
624         getLocationOnScreen(mTempInt2);
625         return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
626     }
627 
getChildAtPosition(float touchX, float touchY)628     public ExpandableView getChildAtPosition(float touchX, float touchY) {
629         // find the view under the pointer, accounting for GONE views
630         final int count = getChildCount();
631         for (int childIdx = 0; childIdx < count; childIdx++) {
632             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
633             if (slidingChild.getVisibility() == GONE
634                     || slidingChild instanceof StackScrollerDecorView
635                     || slidingChild == mSpeedBumpView) {
636                 continue;
637             }
638             float childTop = slidingChild.getTranslationY();
639             float top = childTop + slidingChild.getClipTopAmount();
640             float bottom = childTop + slidingChild.getActualHeight();
641 
642             // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
643             // camera affordance).
644             int left = 0;
645             int right = getWidth();
646 
647             if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
648                 return slidingChild;
649             }
650         }
651         return null;
652     }
653 
canChildBeExpanded(View v)654     public boolean canChildBeExpanded(View v) {
655         return v instanceof ExpandableNotificationRow
656                 && ((ExpandableNotificationRow) v).isExpandable();
657     }
658 
setUserExpandedChild(View v, boolean userExpanded)659     public void setUserExpandedChild(View v, boolean userExpanded) {
660         if (v instanceof ExpandableNotificationRow) {
661             ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
662         }
663     }
664 
setUserLockedChild(View v, boolean userLocked)665     public void setUserLockedChild(View v, boolean userLocked) {
666         if (v instanceof ExpandableNotificationRow) {
667             ((ExpandableNotificationRow) v).setUserLocked(userLocked);
668         }
669         removeLongPressCallback();
670         requestDisallowInterceptTouchEvent(true);
671     }
672 
673     @Override
expansionStateChanged(boolean isExpanding)674     public void expansionStateChanged(boolean isExpanding) {
675         mExpandingNotification = isExpanding;
676         if (!mExpandedInThisMotion) {
677             mMaxScrollAfterExpand = mOwnScrollY;
678             mExpandedInThisMotion = true;
679         }
680     }
681 
setScrollingEnabled(boolean enable)682     public void setScrollingEnabled(boolean enable) {
683         mScrollingEnabled = enable;
684     }
685 
setExpandingEnabled(boolean enable)686     public void setExpandingEnabled(boolean enable) {
687         mExpandHelper.setEnabled(enable);
688     }
689 
isScrollingEnabled()690     private boolean isScrollingEnabled() {
691         return mScrollingEnabled;
692     }
693 
getChildContentView(View v)694     public View getChildContentView(View v) {
695         return v;
696     }
697 
canChildBeDismissed(View v)698     public boolean canChildBeDismissed(View v) {
699         final View veto = v.findViewById(R.id.veto);
700         return (veto != null && veto.getVisibility() != View.GONE);
701     }
702 
703     @Override
isAntiFalsingNeeded()704     public boolean isAntiFalsingNeeded() {
705         return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
706     }
707 
setSwipingInProgress(boolean isSwiped)708     private void setSwipingInProgress(boolean isSwiped) {
709         mSwipingInProgress = isSwiped;
710         if(isSwiped) {
711             requestDisallowInterceptTouchEvent(true);
712         }
713     }
714 
715     @Override
onConfigurationChanged(Configuration newConfig)716     protected void onConfigurationChanged(Configuration newConfig) {
717         super.onConfigurationChanged(newConfig);
718         float densityScale = getResources().getDisplayMetrics().density;
719         mSwipeHelper.setDensityScale(densityScale);
720         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
721         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
722         initView(getContext());
723     }
724 
dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration)725     public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
726         child.setClipBounds(null);
727         mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
728     }
729 
730     @Override
onTouchEvent(MotionEvent ev)731     public boolean onTouchEvent(MotionEvent ev) {
732         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
733                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
734         if (mDelegateToScrollView) {
735             if (isCancelOrUp) {
736                 mDelegateToScrollView = false;
737             }
738             transformTouchEvent(ev, this, mScrollView);
739             return mScrollView.onTouchEvent(ev);
740         }
741         handleEmptySpaceClick(ev);
742         boolean expandWantsIt = false;
743         if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
744             if (isCancelOrUp) {
745                 mExpandHelper.onlyObserveMovements(false);
746             }
747             boolean wasExpandingBefore = mExpandingNotification;
748             expandWantsIt = mExpandHelper.onTouchEvent(ev);
749             if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
750                     && !mDisallowScrollingInThisMotion) {
751                 dispatchDownEventToScroller(ev);
752             }
753         }
754         boolean scrollerWantsIt = false;
755         if (!mSwipingInProgress && !mExpandingNotification && !mDisallowScrollingInThisMotion) {
756             scrollerWantsIt = onScrollTouch(ev);
757         }
758         boolean horizontalSwipeWantsIt = false;
759         if (!mIsBeingDragged
760                 && !mExpandingNotification
761                 && !mExpandedInThisMotion
762                 && !mOnlyScrollingInThisMotion) {
763             horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
764         }
765         return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
766     }
767 
dispatchDownEventToScroller(MotionEvent ev)768     private void dispatchDownEventToScroller(MotionEvent ev) {
769         MotionEvent downEvent = MotionEvent.obtain(ev);
770         downEvent.setAction(MotionEvent.ACTION_DOWN);
771         onScrollTouch(downEvent);
772         downEvent.recycle();
773     }
774 
onScrollTouch(MotionEvent ev)775     private boolean onScrollTouch(MotionEvent ev) {
776         if (!isScrollingEnabled()) {
777             return false;
778         }
779         initVelocityTrackerIfNotExists();
780         mVelocityTracker.addMovement(ev);
781 
782         final int action = ev.getAction();
783 
784         switch (action & MotionEvent.ACTION_MASK) {
785             case MotionEvent.ACTION_DOWN: {
786                 if (getChildCount() == 0 || !isInContentBounds(ev)) {
787                     return false;
788                 }
789                 boolean isBeingDragged = !mScroller.isFinished();
790                 setIsBeingDragged(isBeingDragged);
791 
792                 /*
793                  * If being flinged and user touches, stop the fling. isFinished
794                  * will be false if being flinged.
795                  */
796                 if (!mScroller.isFinished()) {
797                     mScroller.forceFinished(true);
798                 }
799 
800                 // Remember where the motion event started
801                 mLastMotionY = (int) ev.getY();
802                 mDownX = (int) ev.getX();
803                 mActivePointerId = ev.getPointerId(0);
804                 break;
805             }
806             case MotionEvent.ACTION_MOVE:
807                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
808                 if (activePointerIndex == -1) {
809                     Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
810                     break;
811                 }
812 
813                 final int y = (int) ev.getY(activePointerIndex);
814                 final int x = (int) ev.getX(activePointerIndex);
815                 int deltaY = mLastMotionY - y;
816                 final int xDiff = Math.abs(x - mDownX);
817                 final int yDiff = Math.abs(deltaY);
818                 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
819                     setIsBeingDragged(true);
820                     if (deltaY > 0) {
821                         deltaY -= mTouchSlop;
822                     } else {
823                         deltaY += mTouchSlop;
824                     }
825                 }
826                 if (mIsBeingDragged) {
827                     // Scroll to follow the motion event
828                     mLastMotionY = y;
829                     int range = getScrollRange();
830                     if (mExpandedInThisMotion) {
831                         range = Math.min(range, mMaxScrollAfterExpand);
832                     }
833 
834                     float scrollAmount;
835                     if (deltaY < 0) {
836                         scrollAmount = overScrollDown(deltaY);
837                     } else {
838                         scrollAmount = overScrollUp(deltaY, range);
839                     }
840 
841                     // Calling overScrollBy will call onOverScrolled, which
842                     // calls onScrollChanged if applicable.
843                     if (scrollAmount != 0.0f) {
844                         // The scrolling motion could not be compensated with the
845                         // existing overScroll, we have to scroll the view
846                         overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
847                                 0, range, 0, getHeight() / 2, true);
848                     }
849                 }
850                 break;
851             case MotionEvent.ACTION_UP:
852                 if (mIsBeingDragged) {
853                     final VelocityTracker velocityTracker = mVelocityTracker;
854                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
855                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
856 
857                     if (shouldOverScrollFling(initialVelocity)) {
858                         onOverScrollFling(true, initialVelocity);
859                     } else {
860                         if (getChildCount() > 0) {
861                             if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
862                                 float currentOverScrollTop = getCurrentOverScrollAmount(true);
863                                 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
864                                     fling(-initialVelocity);
865                                 } else {
866                                     onOverScrollFling(false, initialVelocity);
867                                 }
868                             } else {
869                                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
870                                         getScrollRange())) {
871                                     postInvalidateOnAnimation();
872                                 }
873                             }
874                         }
875                     }
876 
877                     mActivePointerId = INVALID_POINTER;
878                     endDrag();
879                 }
880 
881                 break;
882             case MotionEvent.ACTION_CANCEL:
883                 if (mIsBeingDragged && getChildCount() > 0) {
884                     if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
885                         postInvalidateOnAnimation();
886                     }
887                     mActivePointerId = INVALID_POINTER;
888                     endDrag();
889                 }
890                 break;
891             case MotionEvent.ACTION_POINTER_DOWN: {
892                 final int index = ev.getActionIndex();
893                 mLastMotionY = (int) ev.getY(index);
894                 mDownX = (int) ev.getX(index);
895                 mActivePointerId = ev.getPointerId(index);
896                 break;
897             }
898             case MotionEvent.ACTION_POINTER_UP:
899                 onSecondaryPointerUp(ev);
900                 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
901                 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
902                 break;
903         }
904         return true;
905     }
906 
onOverScrollFling(boolean open, int initialVelocity)907     private void onOverScrollFling(boolean open, int initialVelocity) {
908         if (mOverscrollTopChangedListener != null) {
909             mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
910         }
911         mDontReportNextOverScroll = true;
912         setOverScrollAmount(0.0f, true, false);
913     }
914 
915     /**
916      * Perform a scroll upwards and adapt the overscroll amounts accordingly
917      *
918      * @param deltaY The amount to scroll upwards, has to be positive.
919      * @return The amount of scrolling to be performed by the scroller,
920      *         not handled by the overScroll amount.
921      */
overScrollUp(int deltaY, int range)922     private float overScrollUp(int deltaY, int range) {
923         deltaY = Math.max(deltaY, 0);
924         float currentTopAmount = getCurrentOverScrollAmount(true);
925         float newTopAmount = currentTopAmount - deltaY;
926         if (currentTopAmount > 0) {
927             setOverScrollAmount(newTopAmount, true /* onTop */,
928                     false /* animate */);
929         }
930         // Top overScroll might not grab all scrolling motion,
931         // we have to scroll as well.
932         float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
933         float newScrollY = mOwnScrollY + scrollAmount;
934         if (newScrollY > range) {
935             if (!mExpandedInThisMotion) {
936                 float currentBottomPixels = getCurrentOverScrolledPixels(false);
937                 // We overScroll on the top
938                 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
939                         false /* onTop */,
940                         false /* animate */);
941             }
942             mOwnScrollY = range;
943             scrollAmount = 0.0f;
944         }
945         return scrollAmount;
946     }
947 
948     /**
949      * Perform a scroll downward and adapt the overscroll amounts accordingly
950      *
951      * @param deltaY The amount to scroll downwards, has to be negative.
952      * @return The amount of scrolling to be performed by the scroller,
953      *         not handled by the overScroll amount.
954      */
overScrollDown(int deltaY)955     private float overScrollDown(int deltaY) {
956         deltaY = Math.min(deltaY, 0);
957         float currentBottomAmount = getCurrentOverScrollAmount(false);
958         float newBottomAmount = currentBottomAmount + deltaY;
959         if (currentBottomAmount > 0) {
960             setOverScrollAmount(newBottomAmount, false /* onTop */,
961                     false /* animate */);
962         }
963         // Bottom overScroll might not grab all scrolling motion,
964         // we have to scroll as well.
965         float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
966         float newScrollY = mOwnScrollY + scrollAmount;
967         if (newScrollY < 0) {
968             float currentTopPixels = getCurrentOverScrolledPixels(true);
969             // We overScroll on the top
970             setOverScrolledPixels(currentTopPixels - newScrollY,
971                     true /* onTop */,
972                     false /* animate */);
973             mOwnScrollY = 0;
974             scrollAmount = 0.0f;
975         }
976         return scrollAmount;
977     }
978 
979     private void onSecondaryPointerUp(MotionEvent ev) {
980         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
981                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
982         final int pointerId = ev.getPointerId(pointerIndex);
983         if (pointerId == mActivePointerId) {
984             // This was our active pointer going up. Choose a new
985             // active pointer and adjust accordingly.
986             // TODO: Make this decision more intelligent.
987             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
988             mLastMotionY = (int) ev.getY(newPointerIndex);
989             mActivePointerId = ev.getPointerId(newPointerIndex);
990             if (mVelocityTracker != null) {
991                 mVelocityTracker.clear();
992             }
993         }
994     }
995 
initVelocityTrackerIfNotExists()996     private void initVelocityTrackerIfNotExists() {
997         if (mVelocityTracker == null) {
998             mVelocityTracker = VelocityTracker.obtain();
999         }
1000     }
1001 
recycleVelocityTracker()1002     private void recycleVelocityTracker() {
1003         if (mVelocityTracker != null) {
1004             mVelocityTracker.recycle();
1005             mVelocityTracker = null;
1006         }
1007     }
1008 
initOrResetVelocityTracker()1009     private void initOrResetVelocityTracker() {
1010         if (mVelocityTracker == null) {
1011             mVelocityTracker = VelocityTracker.obtain();
1012         } else {
1013             mVelocityTracker.clear();
1014         }
1015     }
1016 
1017     @Override
computeScroll()1018     public void computeScroll() {
1019         if (mScroller.computeScrollOffset()) {
1020             // This is called at drawing time by ViewGroup.
1021             int oldX = mScrollX;
1022             int oldY = mOwnScrollY;
1023             int x = mScroller.getCurrX();
1024             int y = mScroller.getCurrY();
1025 
1026             if (oldX != x || oldY != y) {
1027                 final int range = getScrollRange();
1028                 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1029                     float currVelocity = mScroller.getCurrVelocity();
1030                     if (currVelocity >= mMinimumVelocity) {
1031                         mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1032                     }
1033                 }
1034 
1035                 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
1036                         0, (int) (mMaxOverScroll), false);
1037                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1038             }
1039 
1040             // Keep on drawing until the animation has finished.
1041             postInvalidateOnAnimation();
1042         }
1043     }
1044 
1045     @Override
overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)1046     protected boolean overScrollBy(int deltaX, int deltaY,
1047             int scrollX, int scrollY,
1048             int scrollRangeX, int scrollRangeY,
1049             int maxOverScrollX, int maxOverScrollY,
1050             boolean isTouchEvent) {
1051 
1052         int newScrollY = scrollY + deltaY;
1053 
1054         final int top = -maxOverScrollY;
1055         final int bottom = maxOverScrollY + scrollRangeY;
1056 
1057         boolean clampedY = false;
1058         if (newScrollY > bottom) {
1059             newScrollY = bottom;
1060             clampedY = true;
1061         } else if (newScrollY < top) {
1062             newScrollY = top;
1063             clampedY = true;
1064         }
1065 
1066         onOverScrolled(0, newScrollY, false, clampedY);
1067 
1068         return clampedY;
1069     }
1070 
1071     /**
1072      * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1073      * overscroll effect based on numPixels. By default this will also cancel animations on the
1074      * same overScroll edge.
1075      *
1076      * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1077      *                  the rubber-banding logic.
1078      * @param onTop Should the effect be applied on top of the scroller.
1079      * @param animate Should an animation be performed.
1080      */
setOverScrolledPixels(float numPixels, boolean onTop, boolean animate)1081     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
1082         setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
1083     }
1084 
1085     /**
1086      * Set the effective overScroll amount which will be directly reflected in the layout.
1087      * By default this will also cancel animations on the same overScroll edge.
1088      *
1089      * @param amount The amount to overScroll by.
1090      * @param onTop Should the effect be applied on top of the scroller.
1091      * @param animate Should an animation be performed.
1092      */
setOverScrollAmount(float amount, boolean onTop, boolean animate)1093     public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1094         setOverScrollAmount(amount, onTop, animate, true);
1095     }
1096 
1097     /**
1098      * Set the effective overScroll amount which will be directly reflected in the layout.
1099      *
1100      * @param amount The amount to overScroll by.
1101      * @param onTop Should the effect be applied on top of the scroller.
1102      * @param animate Should an animation be performed.
1103      * @param cancelAnimators Should running animations be cancelled.
1104      */
setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators)1105     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1106             boolean cancelAnimators) {
1107         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1108     }
1109 
1110     /**
1111      * Set the effective overScroll amount which will be directly reflected in the layout.
1112      *
1113      * @param amount The amount to overScroll by.
1114      * @param onTop Should the effect be applied on top of the scroller.
1115      * @param animate Should an animation be performed.
1116      * @param cancelAnimators Should running animations be cancelled.
1117      * @param isRubberbanded The value which will be passed to
1118      *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1119      */
setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators, boolean isRubberbanded)1120     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1121             boolean cancelAnimators, boolean isRubberbanded) {
1122         if (cancelAnimators) {
1123             mStateAnimator.cancelOverScrollAnimators(onTop);
1124         }
1125         setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
1126     }
1127 
setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, boolean isRubberbanded)1128     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1129             boolean isRubberbanded) {
1130         amount = Math.max(0, amount);
1131         if (animate) {
1132             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
1133         } else {
1134             setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
1135             mAmbientState.setOverScrollAmount(amount, onTop);
1136             if (onTop) {
1137                 notifyOverscrollTopListener(amount, isRubberbanded);
1138             }
1139             requestChildrenUpdate();
1140         }
1141     }
1142 
notifyOverscrollTopListener(float amount, boolean isRubberbanded)1143     private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
1144         mExpandHelper.onlyObserveMovements(amount > 1.0f);
1145         if (mDontReportNextOverScroll) {
1146             mDontReportNextOverScroll = false;
1147             return;
1148         }
1149         if (mOverscrollTopChangedListener != null) {
1150             mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
1151         }
1152     }
1153 
setOverscrollTopChangedListener( OnOverscrollTopChangedListener overscrollTopChangedListener)1154     public void setOverscrollTopChangedListener(
1155             OnOverscrollTopChangedListener overscrollTopChangedListener) {
1156         mOverscrollTopChangedListener = overscrollTopChangedListener;
1157     }
1158 
getCurrentOverScrollAmount(boolean top)1159     public float getCurrentOverScrollAmount(boolean top) {
1160         return mAmbientState.getOverScrollAmount(top);
1161     }
1162 
getCurrentOverScrolledPixels(boolean top)1163     public float getCurrentOverScrolledPixels(boolean top) {
1164         return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1165     }
1166 
setOverScrolledPixels(float amount, boolean onTop)1167     private void setOverScrolledPixels(float amount, boolean onTop) {
1168         if (onTop) {
1169             mOverScrolledTopPixels = amount;
1170         } else {
1171             mOverScrolledBottomPixels = amount;
1172         }
1173     }
1174 
customScrollTo(int y)1175     private void customScrollTo(int y) {
1176         mOwnScrollY = y;
1177         updateChildren();
1178     }
1179 
1180     @Override
onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)1181     protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
1182         // Treat animating scrolls differently; see #computeScroll() for why.
1183         if (!mScroller.isFinished()) {
1184             final int oldX = mScrollX;
1185             final int oldY = mOwnScrollY;
1186             mScrollX = scrollX;
1187             mOwnScrollY = scrollY;
1188             if (clampedY) {
1189                 springBack();
1190             } else {
1191                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1192                 invalidateParentIfNeeded();
1193                 updateChildren();
1194                 float overScrollTop = getCurrentOverScrollAmount(true);
1195                 if (mOwnScrollY < 0) {
1196                     notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
1197                 } else {
1198                     notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
1199                 }
1200             }
1201         } else {
1202             customScrollTo(scrollY);
1203             scrollTo(scrollX, mScrollY);
1204         }
1205     }
1206 
springBack()1207     private void springBack() {
1208         int scrollRange = getScrollRange();
1209         boolean overScrolledTop = mOwnScrollY <= 0;
1210         boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1211         if (overScrolledTop || overScrolledBottom) {
1212             boolean onTop;
1213             float newAmount;
1214             if (overScrolledTop) {
1215                 onTop = true;
1216                 newAmount = -mOwnScrollY;
1217                 mOwnScrollY = 0;
1218                 mDontReportNextOverScroll = true;
1219             } else {
1220                 onTop = false;
1221                 newAmount = mOwnScrollY - scrollRange;
1222                 mOwnScrollY = scrollRange;
1223             }
1224             setOverScrollAmount(newAmount, onTop, false);
1225             setOverScrollAmount(0.0f, onTop, true);
1226             mScroller.forceFinished(true);
1227         }
1228     }
1229 
getScrollRange()1230     private int getScrollRange() {
1231         int scrollRange = 0;
1232         ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
1233         if (firstChild != null) {
1234             int contentHeight = getContentHeight();
1235             int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
1236             scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1237                     + mBottomStackSlowDownHeight);
1238             if (scrollRange > 0) {
1239                 View lastChild = getLastChildNotGone();
1240                 // We want to at least be able collapse the first item and not ending in a weird
1241                 // end state.
1242                 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1243             }
1244         }
1245         return scrollRange;
1246     }
1247 
1248     /**
1249      * @return the first child which has visibility unequal to GONE
1250      */
getFirstChildNotGone()1251     private View getFirstChildNotGone() {
1252         int childCount = getChildCount();
1253         for (int i = 0; i < childCount; i++) {
1254             View child = getChildAt(i);
1255             if (child.getVisibility() != View.GONE) {
1256                 return child;
1257             }
1258         }
1259         return null;
1260     }
1261 
1262     /**
1263      * @return The first child which has visibility unequal to GONE which is currently below the
1264      *         given translationY or equal to it.
1265      */
getFirstChildBelowTranlsationY(float translationY)1266     private View getFirstChildBelowTranlsationY(float translationY) {
1267         int childCount = getChildCount();
1268         for (int i = 0; i < childCount; i++) {
1269             View child = getChildAt(i);
1270             if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1271                 return child;
1272             }
1273         }
1274         return null;
1275     }
1276 
1277     /**
1278      * @return the last child which has visibility unequal to GONE
1279      */
getLastChildNotGone()1280     public View getLastChildNotGone() {
1281         int childCount = getChildCount();
1282         for (int i = childCount - 1; i >= 0; i--) {
1283             View child = getChildAt(i);
1284             if (child.getVisibility() != View.GONE) {
1285                 return child;
1286             }
1287         }
1288         return null;
1289     }
1290 
1291     /**
1292      * @return the number of children which have visibility unequal to GONE
1293      */
getNotGoneChildCount()1294     public int getNotGoneChildCount() {
1295         int childCount = getChildCount();
1296         int count = 0;
1297         for (int i = 0; i < childCount; i++) {
1298             View child = getChildAt(i);
1299             if (child.getVisibility() != View.GONE) {
1300                 count++;
1301             }
1302         }
1303         if (mDismissView.willBeGone()) {
1304             count--;
1305         }
1306         if (mEmptyShadeView.willBeGone()) {
1307             count--;
1308         }
1309         return count;
1310     }
1311 
getMaxExpandHeight(View view)1312     private int getMaxExpandHeight(View view) {
1313         if (view instanceof ExpandableNotificationRow) {
1314             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1315             return row.getIntrinsicHeight();
1316         }
1317         return view.getHeight();
1318     }
1319 
getContentHeight()1320     public int getContentHeight() {
1321         return mContentHeight;
1322     }
1323 
updateContentHeight()1324     private void updateContentHeight() {
1325         int height = 0;
1326         for (int i = 0; i < getChildCount(); i++) {
1327             View child = getChildAt(i);
1328             if (child.getVisibility() != View.GONE) {
1329                 if (height != 0) {
1330                     // add the padding before this element
1331                     height += mPaddingBetweenElements;
1332                 }
1333                 if (child instanceof ExpandableNotificationRow) {
1334                     ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1335                     height += row.getIntrinsicHeight();
1336                 } else if (child instanceof ExpandableView) {
1337                     ExpandableView expandableView = (ExpandableView) child;
1338                     height += expandableView.getActualHeight();
1339                 }
1340             }
1341         }
1342         mContentHeight = height + mTopPadding;
1343     }
1344 
1345     /**
1346      * Fling the scroll view
1347      *
1348      * @param velocityY The initial velocity in the Y direction. Positive
1349      *                  numbers mean that the finger/cursor is moving down the screen,
1350      *                  which means we want to scroll towards the top.
1351      */
fling(int velocityY)1352     private void fling(int velocityY) {
1353         if (getChildCount() > 0) {
1354             int scrollRange = getScrollRange();
1355 
1356             float topAmount = getCurrentOverScrollAmount(true);
1357             float bottomAmount = getCurrentOverScrollAmount(false);
1358             if (velocityY < 0 && topAmount > 0) {
1359                 mOwnScrollY -= (int) topAmount;
1360                 mDontReportNextOverScroll = true;
1361                 setOverScrollAmount(0, true, false);
1362                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
1363                         * mOverflingDistance + topAmount;
1364             } else if (velocityY > 0 && bottomAmount > 0) {
1365                 mOwnScrollY += bottomAmount;
1366                 setOverScrollAmount(0, false, false);
1367                 mMaxOverScroll = Math.abs(velocityY) / 1000f
1368                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1369                         +  bottomAmount;
1370             } else {
1371                 // it will be set once we reach the boundary
1372                 mMaxOverScroll = 0.0f;
1373             }
1374             mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
1375                     Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
1376 
1377             postInvalidateOnAnimation();
1378         }
1379     }
1380 
1381     /**
1382      * @return Whether a fling performed on the top overscroll edge lead to the expanded
1383      * overScroll view (i.e QS).
1384      */
shouldOverScrollFling(int initialVelocity)1385     private boolean shouldOverScrollFling(int initialVelocity) {
1386         float topOverScroll = getCurrentOverScrollAmount(true);
1387         return mScrolledToTopOnFirstDown
1388                 && !mExpandedInThisMotion
1389                 && topOverScroll > mMinTopOverScrollToEscape
1390                 && initialVelocity > 0;
1391     }
1392 
1393     /**
1394      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1395      * account.
1396      *
1397      * @param qsHeight the top padding imposed by the quick settings panel
1398      * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
1399      *                container
1400      * @param animate whether to animate the change
1401      * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1402      *                               {@code qsHeight} is the final top padding
1403      */
updateTopPadding(float qsHeight, int scrollY, boolean animate, boolean ignoreIntrinsicPadding)1404     public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
1405             boolean ignoreIntrinsicPadding) {
1406         float start = qsHeight - scrollY + mNotificationTopPadding;
1407         float stackHeight = getHeight() - start;
1408         int minStackHeight = getMinStackHeight();
1409         if (stackHeight <= minStackHeight) {
1410             float overflow = minStackHeight - stackHeight;
1411             stackHeight = minStackHeight;
1412             start = getHeight() - stackHeight;
1413             mTopPaddingOverflow = overflow;
1414         } else {
1415             mTopPaddingOverflow = 0;
1416         }
1417         setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1418                 animate);
1419         setStackHeight(mLastSetStackHeight);
1420     }
1421 
getNotificationTopPadding()1422     public int getNotificationTopPadding() {
1423         return mNotificationTopPadding;
1424     }
1425 
getMinStackHeight()1426     public int getMinStackHeight() {
1427         return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
1428     }
1429 
getTopPaddingOverflow()1430     public float getTopPaddingOverflow() {
1431         return mTopPaddingOverflow;
1432     }
1433 
getPeekHeight()1434     public int getPeekHeight() {
1435         return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
1436                 + mCollapseSecondCardPadding;
1437     }
1438 
clampPadding(int desiredPadding)1439     private int clampPadding(int desiredPadding) {
1440         return Math.max(desiredPadding, mIntrinsicPadding);
1441     }
1442 
getRubberBandFactor(boolean onTop)1443     private float getRubberBandFactor(boolean onTop) {
1444         if (!onTop) {
1445             return RUBBER_BAND_FACTOR_NORMAL;
1446         }
1447         if (mExpandedInThisMotion) {
1448             return RUBBER_BAND_FACTOR_AFTER_EXPAND;
1449         } else if (mIsExpansionChanging) {
1450             return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1451         } else if (mScrolledToTopOnFirstDown) {
1452             return 1.0f;
1453         }
1454         return RUBBER_BAND_FACTOR_NORMAL;
1455     }
1456 
1457     /**
1458      * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1459      * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1460      * overscroll view (e.g. expand QS).
1461      */
isRubberbanded(boolean onTop)1462     private boolean isRubberbanded(boolean onTop) {
1463         return !onTop || mExpandedInThisMotion || mIsExpansionChanging
1464                 || !mScrolledToTopOnFirstDown;
1465     }
1466 
endDrag()1467     private void endDrag() {
1468         setIsBeingDragged(false);
1469 
1470         recycleVelocityTracker();
1471 
1472         if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1473             setOverScrollAmount(0, true /* onTop */, true /* animate */);
1474         }
1475         if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1476             setOverScrollAmount(0, false /* onTop */, true /* animate */);
1477         }
1478     }
1479 
transformTouchEvent(MotionEvent ev, View sourceView, View targetView)1480     private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1481         ev.offsetLocation(sourceView.getX(), sourceView.getY());
1482         ev.offsetLocation(-targetView.getX(), -targetView.getY());
1483     }
1484 
1485     @Override
onInterceptTouchEvent(MotionEvent ev)1486     public boolean onInterceptTouchEvent(MotionEvent ev) {
1487         if (mInterceptDelegateEnabled) {
1488             transformTouchEvent(ev, this, mScrollView);
1489             if (mScrollView.onInterceptTouchEvent(ev)) {
1490                 mDelegateToScrollView = true;
1491                 removeLongPressCallback();
1492                 return true;
1493             }
1494             transformTouchEvent(ev, mScrollView, this);
1495         }
1496         initDownStates(ev);
1497         handleEmptySpaceClick(ev);
1498         boolean expandWantsIt = false;
1499         if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
1500             expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1501         }
1502         boolean scrollWantsIt = false;
1503         if (!mSwipingInProgress && !mExpandingNotification) {
1504             scrollWantsIt = onInterceptTouchEventScroll(ev);
1505         }
1506         boolean swipeWantsIt = false;
1507         if (!mIsBeingDragged
1508                 && !mExpandingNotification
1509                 && !mExpandedInThisMotion
1510                 && !mOnlyScrollingInThisMotion) {
1511             swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1512         }
1513         return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1514     }
1515 
handleEmptySpaceClick(MotionEvent ev)1516     private void handleEmptySpaceClick(MotionEvent ev) {
1517         switch (ev.getActionMasked()) {
1518             case MotionEvent.ACTION_MOVE:
1519                 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1520                         || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1521                     mTouchIsClick = false;
1522                 }
1523                 break;
1524             case MotionEvent.ACTION_UP:
1525                 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1526                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1527                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1528                 }
1529                 break;
1530         }
1531     }
1532 
initDownStates(MotionEvent ev)1533     private void initDownStates(MotionEvent ev) {
1534         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1535             mExpandedInThisMotion = false;
1536             mOnlyScrollingInThisMotion = !mScroller.isFinished();
1537             mDisallowScrollingInThisMotion = false;
1538             mTouchIsClick = true;
1539             mInitialTouchX = ev.getX();
1540             mInitialTouchY = ev.getY();
1541         }
1542     }
1543 
1544     @Override
onViewRemoved(View child)1545     protected void onViewRemoved(View child) {
1546         super.onViewRemoved(child);
1547         mStackScrollAlgorithm.notifyChildrenChanged(this);
1548         if (mChangePositionInProgress) {
1549             // This is only a position change, don't do anything special
1550             return;
1551         }
1552         ((ExpandableView) child).setOnHeightChangedListener(null);
1553         mCurrentStackScrollState.removeViewStateForView(child);
1554         updateScrollStateForRemovedChild(child);
1555         boolean animationGenerated = generateRemoveAnimation(child);
1556         if (animationGenerated && !mSwipedOutViews.contains(child)) {
1557             // Add this view to an overlay in order to ensure that it will still be temporary
1558             // drawn when removed
1559             getOverlay().add(child);
1560         }
1561         updateAnimationState(false, child);
1562 
1563         // Make sure the clipRect we might have set is removed
1564         child.setClipBounds(null);
1565     }
1566 
1567     /**
1568      * Generate a remove animation for a child view.
1569      *
1570      * @param child The view to generate the remove animation for.
1571      * @return Whether an animation was generated.
1572      */
generateRemoveAnimation(View child)1573     private boolean generateRemoveAnimation(View child) {
1574         if (mIsExpanded && mAnimationsEnabled) {
1575             if (!mChildrenToAddAnimated.contains(child)) {
1576                 // Generate Animations
1577                 mChildrenToRemoveAnimated.add(child);
1578                 mNeedsAnimation = true;
1579                 return true;
1580             } else {
1581                 mChildrenToAddAnimated.remove(child);
1582                 mFromMoreCardAdditions.remove(child);
1583                 return false;
1584             }
1585         }
1586         return false;
1587     }
1588 
1589     /**
1590      * Updates the scroll position when a child was removed
1591      *
1592      * @param removedChild the removed child
1593      */
updateScrollStateForRemovedChild(View removedChild)1594     private void updateScrollStateForRemovedChild(View removedChild) {
1595         int startingPosition = getPositionInLinearLayout(removedChild);
1596         int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
1597         int endPosition = startingPosition + childHeight;
1598         if (endPosition <= mOwnScrollY) {
1599             // This child is fully scrolled of the top, so we have to deduct its height from the
1600             // scrollPosition
1601             mOwnScrollY -= childHeight;
1602         } else if (startingPosition < mOwnScrollY) {
1603             // This child is currently being scrolled into, set the scroll position to the start of
1604             // this child
1605             mOwnScrollY = startingPosition;
1606         }
1607     }
1608 
getIntrinsicHeight(View view)1609     private int getIntrinsicHeight(View view) {
1610         if (view instanceof ExpandableView) {
1611             ExpandableView expandableView = (ExpandableView) view;
1612             return expandableView.getIntrinsicHeight();
1613         }
1614         return view.getHeight();
1615     }
1616 
getPositionInLinearLayout(View requestedChild)1617     private int getPositionInLinearLayout(View requestedChild) {
1618         int position = 0;
1619         for (int i = 0; i < getChildCount(); i++) {
1620             View child = getChildAt(i);
1621             if (child == requestedChild) {
1622                 return position;
1623             }
1624             if (child.getVisibility() != View.GONE) {
1625                 position += getIntrinsicHeight(child);
1626                 if (i < getChildCount()-1) {
1627                     position += mPaddingBetweenElements;
1628                 }
1629             }
1630         }
1631         return 0;
1632     }
1633 
1634     @Override
onViewAdded(View child)1635     protected void onViewAdded(View child) {
1636         super.onViewAdded(child);
1637         mStackScrollAlgorithm.notifyChildrenChanged(this);
1638         ((ExpandableView) child).setOnHeightChangedListener(this);
1639         generateAddAnimation(child, false /* fromMoreCard */);
1640         updateAnimationState(child);
1641         if (canChildBeDismissed(child)) {
1642             // Make sure the dismissButton is visible and not in the animated state.
1643             // We need to do this to avoid a race where a clearable notification is added after the
1644             // dismiss animation is finished
1645             mDismissView.showClearButton();
1646         }
1647     }
1648 
setAnimationsEnabled(boolean animationsEnabled)1649     public void setAnimationsEnabled(boolean animationsEnabled) {
1650         mAnimationsEnabled = animationsEnabled;
1651         updateNotificationAnimationStates();
1652     }
1653 
updateNotificationAnimationStates()1654     private void updateNotificationAnimationStates() {
1655         boolean running = mIsExpanded && mAnimationsEnabled;
1656         int childCount = getChildCount();
1657         for (int i = 0; i < childCount; i++) {
1658             View child = getChildAt(i);
1659             updateAnimationState(running, child);
1660         }
1661     }
1662 
updateAnimationState(View child)1663     private void updateAnimationState(View child) {
1664         updateAnimationState(mAnimationsEnabled && mIsExpanded, child);
1665     }
1666 
1667 
updateAnimationState(boolean running, View child)1668     private void updateAnimationState(boolean running, View child) {
1669         if (child instanceof ExpandableNotificationRow) {
1670             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1671             row.setIconAnimationRunning(running);
1672         }
1673     }
1674 
isAddOrRemoveAnimationPending()1675     public boolean isAddOrRemoveAnimationPending() {
1676         return mNeedsAnimation
1677                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1678     }
1679     /**
1680      * Generate an animation for an added child view.
1681      *
1682      * @param child The view to be added.
1683      * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
1684      */
generateAddAnimation(View child, boolean fromMoreCard)1685     public void generateAddAnimation(View child, boolean fromMoreCard) {
1686         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
1687             // Generate Animations
1688             mChildrenToAddAnimated.add(child);
1689             if (fromMoreCard) {
1690                 mFromMoreCardAdditions.add(child);
1691             }
1692             mNeedsAnimation = true;
1693         }
1694     }
1695 
1696     /**
1697      * Change the position of child to a new location
1698      *
1699      * @param child the view to change the position for
1700      * @param newIndex the new index
1701      */
changeViewPosition(View child, int newIndex)1702     public void changeViewPosition(View child, int newIndex) {
1703         int currentIndex = indexOfChild(child);
1704         if (child != null && child.getParent() == this && currentIndex != newIndex) {
1705             mChangePositionInProgress = true;
1706             removeView(child);
1707             addView(child, newIndex);
1708             mChangePositionInProgress = false;
1709             if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
1710                 mChildrenChangingPositions.add(child);
1711                 mNeedsAnimation = true;
1712             }
1713         }
1714     }
1715 
startAnimationToState()1716     private void startAnimationToState() {
1717         if (mNeedsAnimation) {
1718             generateChildHierarchyEvents();
1719             mNeedsAnimation = false;
1720         }
1721         if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
1722             mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1723                     mGoToFullShadeDelay);
1724             mAnimationEvents.clear();
1725         } else {
1726             applyCurrentState();
1727         }
1728         mGoToFullShadeDelay = 0;
1729     }
1730 
generateChildHierarchyEvents()1731     private void generateChildHierarchyEvents() {
1732         generateChildRemovalEvents();
1733         generateChildAdditionEvents();
1734         generatePositionChangeEvents();
1735         generateSnapBackEvents();
1736         generateDragEvents();
1737         generateTopPaddingEvent();
1738         generateActivateEvent();
1739         generateDimmedEvent();
1740         generateHideSensitiveEvent();
1741         generateDarkEvent();
1742         generateGoToFullShadeEvent();
1743         generateViewResizeEvent();
1744         generateAnimateEverythingEvent();
1745         mNeedsAnimation = false;
1746     }
1747 
generateViewResizeEvent()1748     private void generateViewResizeEvent() {
1749         if (mNeedViewResizeAnimation) {
1750             mAnimationEvents.add(
1751                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
1752         }
1753         mNeedViewResizeAnimation = false;
1754     }
1755 
generateSnapBackEvents()1756     private void generateSnapBackEvents() {
1757         for (View child : mSnappedBackChildren) {
1758             mAnimationEvents.add(new AnimationEvent(child,
1759                     AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1760         }
1761         mSnappedBackChildren.clear();
1762     }
1763 
generateDragEvents()1764     private void generateDragEvents() {
1765         for (View child : mDragAnimPendingChildren) {
1766             mAnimationEvents.add(new AnimationEvent(child,
1767                     AnimationEvent.ANIMATION_TYPE_START_DRAG));
1768         }
1769         mDragAnimPendingChildren.clear();
1770     }
1771 
generateChildRemovalEvents()1772     private void generateChildRemovalEvents() {
1773         for (View child : mChildrenToRemoveAnimated) {
1774             boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1775             int animationType = childWasSwipedOut
1776                     ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1777                     : AnimationEvent.ANIMATION_TYPE_REMOVE;
1778             AnimationEvent event = new AnimationEvent(child, animationType);
1779 
1780             // we need to know the view after this one
1781             event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1782             mAnimationEvents.add(event);
1783         }
1784         mSwipedOutViews.clear();
1785         mChildrenToRemoveAnimated.clear();
1786     }
1787 
generatePositionChangeEvents()1788     private void generatePositionChangeEvents() {
1789         for (View child : mChildrenChangingPositions) {
1790             mAnimationEvents.add(new AnimationEvent(child,
1791                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1792         }
1793         mChildrenChangingPositions.clear();
1794     }
1795 
generateChildAdditionEvents()1796     private void generateChildAdditionEvents() {
1797         for (View child : mChildrenToAddAnimated) {
1798             if (mFromMoreCardAdditions.contains(child)) {
1799                 mAnimationEvents.add(new AnimationEvent(child,
1800                         AnimationEvent.ANIMATION_TYPE_ADD,
1801                         StackStateAnimator.ANIMATION_DURATION_STANDARD));
1802             } else {
1803                 mAnimationEvents.add(new AnimationEvent(child,
1804                         AnimationEvent.ANIMATION_TYPE_ADD));
1805             }
1806         }
1807         mChildrenToAddAnimated.clear();
1808         mFromMoreCardAdditions.clear();
1809     }
1810 
generateTopPaddingEvent()1811     private void generateTopPaddingEvent() {
1812         if (mTopPaddingNeedsAnimation) {
1813             mAnimationEvents.add(
1814                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
1815         }
1816         mTopPaddingNeedsAnimation = false;
1817     }
1818 
generateActivateEvent()1819     private void generateActivateEvent() {
1820         if (mActivateNeedsAnimation) {
1821             mAnimationEvents.add(
1822                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
1823         }
1824         mActivateNeedsAnimation = false;
1825     }
1826 
generateAnimateEverythingEvent()1827     private void generateAnimateEverythingEvent() {
1828         if (mEverythingNeedsAnimation) {
1829             mAnimationEvents.add(
1830                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
1831         }
1832         mEverythingNeedsAnimation = false;
1833     }
1834 
generateDimmedEvent()1835     private void generateDimmedEvent() {
1836         if (mDimmedNeedsAnimation) {
1837             mAnimationEvents.add(
1838                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
1839         }
1840         mDimmedNeedsAnimation = false;
1841     }
1842 
generateHideSensitiveEvent()1843     private void generateHideSensitiveEvent() {
1844         if (mHideSensitiveNeedsAnimation) {
1845             mAnimationEvents.add(
1846                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
1847         }
1848         mHideSensitiveNeedsAnimation = false;
1849     }
1850 
generateDarkEvent()1851     private void generateDarkEvent() {
1852         if (mDarkNeedsAnimation) {
1853             AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
1854             ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
1855             mAnimationEvents.add(ev);
1856         }
1857         mDarkNeedsAnimation = false;
1858     }
1859 
generateGoToFullShadeEvent()1860     private void generateGoToFullShadeEvent() {
1861         if (mGoToFullShadeNeedsAnimation) {
1862             mAnimationEvents.add(
1863                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
1864         }
1865         mGoToFullShadeNeedsAnimation = false;
1866     }
1867 
onInterceptTouchEventScroll(MotionEvent ev)1868     private boolean onInterceptTouchEventScroll(MotionEvent ev) {
1869         if (!isScrollingEnabled()) {
1870             return false;
1871         }
1872         /*
1873          * This method JUST determines whether we want to intercept the motion.
1874          * If we return true, onMotionEvent will be called and we do the actual
1875          * scrolling there.
1876          */
1877 
1878         /*
1879         * Shortcut the most recurring case: the user is in the dragging
1880         * state and he is moving his finger.  We want to intercept this
1881         * motion.
1882         */
1883         final int action = ev.getAction();
1884         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
1885             return true;
1886         }
1887 
1888         switch (action & MotionEvent.ACTION_MASK) {
1889             case MotionEvent.ACTION_MOVE: {
1890                 /*
1891                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1892                  * whether the user has moved far enough from his original down touch.
1893                  */
1894 
1895                 /*
1896                 * Locally do absolute value. mLastMotionY is set to the y value
1897                 * of the down event.
1898                 */
1899                 final int activePointerId = mActivePointerId;
1900                 if (activePointerId == INVALID_POINTER) {
1901                     // If we don't have a valid id, the touch down wasn't on content.
1902                     break;
1903                 }
1904 
1905                 final int pointerIndex = ev.findPointerIndex(activePointerId);
1906                 if (pointerIndex == -1) {
1907                     Log.e(TAG, "Invalid pointerId=" + activePointerId
1908                             + " in onInterceptTouchEvent");
1909                     break;
1910                 }
1911 
1912                 final int y = (int) ev.getY(pointerIndex);
1913                 final int x = (int) ev.getX(pointerIndex);
1914                 final int yDiff = Math.abs(y - mLastMotionY);
1915                 final int xDiff = Math.abs(x - mDownX);
1916                 if (yDiff > mTouchSlop && yDiff > xDiff) {
1917                     setIsBeingDragged(true);
1918                     mLastMotionY = y;
1919                     mDownX = x;
1920                     initVelocityTrackerIfNotExists();
1921                     mVelocityTracker.addMovement(ev);
1922                 }
1923                 break;
1924             }
1925 
1926             case MotionEvent.ACTION_DOWN: {
1927                 final int y = (int) ev.getY();
1928                 if (getChildAtPosition(ev.getX(), y) == null) {
1929                     setIsBeingDragged(false);
1930                     recycleVelocityTracker();
1931                     break;
1932                 }
1933 
1934                 /*
1935                  * Remember location of down touch.
1936                  * ACTION_DOWN always refers to pointer index 0.
1937                  */
1938                 mLastMotionY = y;
1939                 mDownX = (int) ev.getX();
1940                 mActivePointerId = ev.getPointerId(0);
1941                 mScrolledToTopOnFirstDown = isScrolledToTop();
1942 
1943                 initOrResetVelocityTracker();
1944                 mVelocityTracker.addMovement(ev);
1945                 /*
1946                 * If being flinged and user touches the screen, initiate drag;
1947                 * otherwise don't.  mScroller.isFinished should be false when
1948                 * being flinged.
1949                 */
1950                 boolean isBeingDragged = !mScroller.isFinished();
1951                 setIsBeingDragged(isBeingDragged);
1952                 break;
1953             }
1954 
1955             case MotionEvent.ACTION_CANCEL:
1956             case MotionEvent.ACTION_UP:
1957                 /* Release the drag */
1958                 setIsBeingDragged(false);
1959                 mActivePointerId = INVALID_POINTER;
1960                 recycleVelocityTracker();
1961                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1962                     postInvalidateOnAnimation();
1963                 }
1964                 break;
1965             case MotionEvent.ACTION_POINTER_UP:
1966                 onSecondaryPointerUp(ev);
1967                 break;
1968         }
1969 
1970         /*
1971         * The only time we want to intercept motion events is if we are in the
1972         * drag mode.
1973         */
1974         return mIsBeingDragged;
1975     }
1976 
1977     /**
1978      * @return Whether the specified motion event is actually happening over the content.
1979      */
isInContentBounds(MotionEvent event)1980     private boolean isInContentBounds(MotionEvent event) {
1981         return isInContentBounds(event.getY());
1982     }
1983 
1984     /**
1985      * @return Whether a y coordinate is inside the content.
1986      */
isInContentBounds(float y)1987     public boolean isInContentBounds(float y) {
1988         return y < getHeight() - getEmptyBottomMargin();
1989     }
1990 
setIsBeingDragged(boolean isDragged)1991     private void setIsBeingDragged(boolean isDragged) {
1992         mIsBeingDragged = isDragged;
1993         if (isDragged) {
1994             requestDisallowInterceptTouchEvent(true);
1995             removeLongPressCallback();
1996         }
1997     }
1998 
1999     @Override
onWindowFocusChanged(boolean hasWindowFocus)2000     public void onWindowFocusChanged(boolean hasWindowFocus) {
2001         super.onWindowFocusChanged(hasWindowFocus);
2002         if (!hasWindowFocus) {
2003             removeLongPressCallback();
2004         }
2005     }
2006 
removeLongPressCallback()2007     public void removeLongPressCallback() {
2008         mSwipeHelper.removeLongPressCallback();
2009     }
2010 
2011     @Override
isScrolledToTop()2012     public boolean isScrolledToTop() {
2013         return mOwnScrollY == 0;
2014     }
2015 
2016     @Override
isScrolledToBottom()2017     public boolean isScrolledToBottom() {
2018         return mOwnScrollY >= getScrollRange();
2019     }
2020 
2021     @Override
getHostView()2022     public View getHostView() {
2023         return this;
2024     }
2025 
getEmptyBottomMargin()2026     public int getEmptyBottomMargin() {
2027         int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
2028         if (needsHeightAdaption()) {
2029             emptyMargin -= mBottomStackSlowDownHeight;
2030         } else {
2031             emptyMargin -= mCollapseSecondCardPadding;
2032         }
2033         return Math.max(emptyMargin, 0);
2034     }
2035 
onExpansionStarted()2036     public void onExpansionStarted() {
2037         mIsExpansionChanging = true;
2038         mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2039     }
2040 
onExpansionStopped()2041     public void onExpansionStopped() {
2042         mIsExpansionChanging = false;
2043         mStackScrollAlgorithm.onExpansionStopped();
2044         if (!mIsExpanded) {
2045             mOwnScrollY = 0;
2046 
2047             // lets make sure nothing is in the overlay anymore
2048             getOverlay().clear();
2049         }
2050     }
2051 
setIsExpanded(boolean isExpanded)2052     private void setIsExpanded(boolean isExpanded) {
2053         boolean changed = isExpanded != mIsExpanded;
2054         mIsExpanded = isExpanded;
2055         mStackScrollAlgorithm.setIsExpanded(isExpanded);
2056         if (changed) {
2057             updateNotificationAnimationStates();
2058         }
2059     }
2060 
2061     @Override
onHeightChanged(ExpandableView view)2062     public void onHeightChanged(ExpandableView view) {
2063         updateContentHeight();
2064         updateScrollPositionOnExpandInBottom(view);
2065         clampScrollPosition();
2066         notifyHeightChangeListener(view);
2067         requestChildrenUpdate();
2068     }
2069 
2070     @Override
onReset(ExpandableView view)2071     public void onReset(ExpandableView view) {
2072         if (mIsExpanded && mAnimationsEnabled) {
2073             mRequestViewResizeAnimationOnLayout = true;
2074         }
2075         mStackScrollAlgorithm.onReset(view);
2076         updateAnimationState(view);
2077     }
2078 
updateScrollPositionOnExpandInBottom(ExpandableView view)2079     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2080         if (view instanceof ExpandableNotificationRow) {
2081             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2082             if (row.isUserLocked()) {
2083                 // We are actually expanding this view
2084                 float endPosition = row.getTranslationY() + row.getActualHeight();
2085                 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
2086                         mBottomStackSlowDownHeight;
2087                 if (endPosition > stackEnd) {
2088                     mOwnScrollY += endPosition - stackEnd;
2089                     mDisallowScrollingInThisMotion = true;
2090                 }
2091             }
2092         }
2093     }
2094 
setOnHeightChangedListener( ExpandableView.OnHeightChangedListener mOnHeightChangedListener)2095     public void setOnHeightChangedListener(
2096             ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2097         this.mOnHeightChangedListener = mOnHeightChangedListener;
2098     }
2099 
setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener)2100     public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2101         mOnEmptySpaceClickListener = listener;
2102     }
2103 
onChildAnimationFinished()2104     public void onChildAnimationFinished() {
2105         requestChildrenUpdate();
2106     }
2107 
2108     /**
2109      * See {@link AmbientState#setDimmed}.
2110      */
setDimmed(boolean dimmed, boolean animate)2111     public void setDimmed(boolean dimmed, boolean animate) {
2112         mStackScrollAlgorithm.setDimmed(dimmed);
2113         mAmbientState.setDimmed(dimmed);
2114         updatePadding(dimmed);
2115         if (animate && mAnimationsEnabled) {
2116             mDimmedNeedsAnimation = true;
2117             mNeedsAnimation =  true;
2118         }
2119         requestChildrenUpdate();
2120     }
2121 
setHideSensitive(boolean hideSensitive, boolean animate)2122     public void setHideSensitive(boolean hideSensitive, boolean animate) {
2123         if (hideSensitive != mAmbientState.isHideSensitive()) {
2124             int childCount = getChildCount();
2125             for (int i = 0; i < childCount; i++) {
2126                 ExpandableView v = (ExpandableView) getChildAt(i);
2127                 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2128             }
2129             mAmbientState.setHideSensitive(hideSensitive);
2130             if (animate && mAnimationsEnabled) {
2131                 mHideSensitiveNeedsAnimation = true;
2132                 mNeedsAnimation =  true;
2133             }
2134             requestChildrenUpdate();
2135         }
2136     }
2137 
2138     /**
2139      * See {@link AmbientState#setActivatedChild}.
2140      */
setActivatedChild(ActivatableNotificationView activatedChild)2141     public void setActivatedChild(ActivatableNotificationView activatedChild) {
2142         mAmbientState.setActivatedChild(activatedChild);
2143         if (mAnimationsEnabled) {
2144             mActivateNeedsAnimation = true;
2145             mNeedsAnimation =  true;
2146         }
2147         requestChildrenUpdate();
2148     }
2149 
getActivatedChild()2150     public ActivatableNotificationView getActivatedChild() {
2151         return mAmbientState.getActivatedChild();
2152     }
2153 
applyCurrentState()2154     private void applyCurrentState() {
2155         mCurrentStackScrollState.apply();
2156         if (mListener != null) {
2157             mListener.onChildLocationsChanged(this);
2158         }
2159     }
2160 
setSpeedBumpView(SpeedBumpView speedBumpView)2161     public void setSpeedBumpView(SpeedBumpView speedBumpView) {
2162         mSpeedBumpView = speedBumpView;
2163         addView(speedBumpView);
2164     }
2165 
updateSpeedBump(boolean visible)2166     private void updateSpeedBump(boolean visible) {
2167         boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
2168         if (visible != notGoneBefore) {
2169             int newVisibility = visible ? VISIBLE : GONE;
2170             mSpeedBumpView.setVisibility(newVisibility);
2171             if (visible) {
2172                 // Make invisible to ensure that the appear animation is played.
2173                 mSpeedBumpView.setInvisible();
2174             } else {
2175                 // TODO: This doesn't really work, because the view is already set to GONE above.
2176                 generateRemoveAnimation(mSpeedBumpView);
2177             }
2178         }
2179     }
2180 
goToFullShade(long delay)2181     public void goToFullShade(long delay) {
2182         updateSpeedBump(true /* visibility */);
2183         mDismissView.setInvisible();
2184         mEmptyShadeView.setInvisible();
2185         mGoToFullShadeNeedsAnimation = true;
2186         mGoToFullShadeDelay = delay;
2187         mNeedsAnimation = true;
2188         requestChildrenUpdate();
2189     }
2190 
cancelExpandHelper()2191     public void cancelExpandHelper() {
2192         mExpandHelper.cancel();
2193     }
2194 
setIntrinsicPadding(int intrinsicPadding)2195     public void setIntrinsicPadding(int intrinsicPadding) {
2196         mIntrinsicPadding = intrinsicPadding;
2197     }
2198 
getIntrinsicPadding()2199     public int getIntrinsicPadding() {
2200         return mIntrinsicPadding;
2201     }
2202 
2203     /**
2204      * @return the y position of the first notification
2205      */
getNotificationsTopY()2206     public float getNotificationsTopY() {
2207         return mTopPadding + getTranslationY();
2208     }
2209 
2210     @Override
shouldDelayChildPressedState()2211     public boolean shouldDelayChildPressedState() {
2212         return true;
2213     }
2214 
2215     /**
2216      * See {@link AmbientState#setDark}.
2217      */
setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation)2218     public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
2219         mAmbientState.setDark(dark);
2220         if (animate && mAnimationsEnabled) {
2221             mDarkNeedsAnimation = true;
2222             mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
2223             mNeedsAnimation =  true;
2224         }
2225         requestChildrenUpdate();
2226     }
2227 
findDarkAnimationOriginIndex(@ullable PointF screenLocation)2228     private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2229         if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2230             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2231         }
2232         if (screenLocation.y > getBottomMostNotificationBottom()) {
2233             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2234         }
2235         View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2236         if (child != null) {
2237             return getNotGoneIndex(child);
2238         } else {
2239             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2240         }
2241     }
2242 
getNotGoneIndex(View child)2243     private int getNotGoneIndex(View child) {
2244         int count = getChildCount();
2245         int notGoneIndex = 0;
2246         for (int i = 0; i < count; i++) {
2247             View v = getChildAt(i);
2248             if (child == v) {
2249                 return notGoneIndex;
2250             }
2251             if (v.getVisibility() != View.GONE) {
2252                 notGoneIndex++;
2253             }
2254         }
2255         return -1;
2256     }
2257 
setDismissView(DismissView dismissView)2258     public void setDismissView(DismissView dismissView) {
2259         mDismissView = dismissView;
2260         addView(mDismissView);
2261     }
2262 
setEmptyShadeView(EmptyShadeView emptyShadeView)2263     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2264         mEmptyShadeView = emptyShadeView;
2265         addView(mEmptyShadeView);
2266     }
2267 
updateEmptyShadeView(boolean visible)2268     public void updateEmptyShadeView(boolean visible) {
2269         int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2270         int newVisibility = visible ? VISIBLE : GONE;
2271         if (oldVisibility != newVisibility) {
2272             if (newVisibility != GONE) {
2273                 if (mEmptyShadeView.willBeGone()) {
2274                     mEmptyShadeView.cancelAnimation();
2275                 } else {
2276                     mEmptyShadeView.setInvisible();
2277                 }
2278                 mEmptyShadeView.setVisibility(newVisibility);
2279                 mEmptyShadeView.setWillBeGone(false);
2280                 updateContentHeight();
2281                 notifyHeightChangeListener(mDismissView);
2282             } else {
2283                 Runnable onFinishedRunnable = new Runnable() {
2284                     @Override
2285                     public void run() {
2286                         mEmptyShadeView.setVisibility(GONE);
2287                         mEmptyShadeView.setWillBeGone(false);
2288                         updateContentHeight();
2289                         notifyHeightChangeListener(mDismissView);
2290                     }
2291                 };
2292                 if (mAnimationsEnabled) {
2293                     mEmptyShadeView.setWillBeGone(true);
2294                     mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
2295                 } else {
2296                     mEmptyShadeView.setInvisible();
2297                     onFinishedRunnable.run();
2298                 }
2299             }
2300         }
2301     }
2302 
updateDismissView(boolean visible)2303     public void updateDismissView(boolean visible) {
2304         int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
2305         int newVisibility = visible ? VISIBLE : GONE;
2306         if (oldVisibility != newVisibility) {
2307             if (newVisibility != GONE) {
2308                 if (mDismissView.willBeGone()) {
2309                     mDismissView.cancelAnimation();
2310                 } else {
2311                     mDismissView.setInvisible();
2312                 }
2313                 mDismissView.setVisibility(newVisibility);
2314                 mDismissView.setWillBeGone(false);
2315                 updateContentHeight();
2316                 notifyHeightChangeListener(mDismissView);
2317             } else {
2318                 Runnable dimissHideFinishRunnable = new Runnable() {
2319                     @Override
2320                     public void run() {
2321                         mDismissView.setVisibility(GONE);
2322                         mDismissView.setWillBeGone(false);
2323                         updateContentHeight();
2324                         notifyHeightChangeListener(mDismissView);
2325                     }
2326                 };
2327                 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
2328                     mDismissView.setWillBeGone(true);
2329                     mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
2330                 } else {
2331                     dimissHideFinishRunnable.run();
2332                     mDismissView.showClearButton();
2333                 }
2334             }
2335         }
2336     }
2337 
setDismissAllInProgress(boolean dismissAllInProgress)2338     public void setDismissAllInProgress(boolean dismissAllInProgress) {
2339         mDismissAllInProgress = dismissAllInProgress;
2340         mDismissView.setDismissAllInProgress(dismissAllInProgress);
2341     }
2342 
isDismissViewNotGone()2343     public boolean isDismissViewNotGone() {
2344         return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2345     }
2346 
isDismissViewVisible()2347     public boolean isDismissViewVisible() {
2348         return mDismissView.isVisible();
2349     }
2350 
getDismissViewHeight()2351     public int getDismissViewHeight() {
2352         int height = mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2353 
2354         // Hack: Accommodate for additional distance when we only have one notification and the
2355         // dismiss all button.
2356         if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
2357                 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
2358             height += mCollapseSecondCardPadding;
2359         }
2360         return height;
2361     }
2362 
getEmptyShadeViewHeight()2363     public int getEmptyShadeViewHeight() {
2364         return mEmptyShadeView.getHeight();
2365     }
2366 
getBottomMostNotificationBottom()2367     public float getBottomMostNotificationBottom() {
2368         final int count = getChildCount();
2369         float max = 0;
2370         for (int childIdx = 0; childIdx < count; childIdx++) {
2371             ExpandableView child = (ExpandableView) getChildAt(childIdx);
2372             if (child.getVisibility() == GONE) {
2373                 continue;
2374             }
2375             float bottom = child.getTranslationY() + child.getActualHeight();
2376             if (bottom > max) {
2377                 max = bottom;
2378             }
2379         }
2380         return max + getTranslationY();
2381     }
2382 
2383     /**
2384      * @param qsMinHeight The minimum height of the quick settings including padding
2385      *                    See {@link StackScrollAlgorithm#updateIsSmallScreen}.
2386      */
updateIsSmallScreen(int qsMinHeight)2387     public void updateIsSmallScreen(int qsMinHeight) {
2388         mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight);
2389     }
2390 
setPhoneStatusBar(PhoneStatusBar phoneStatusBar)2391     public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
2392         this.mPhoneStatusBar = phoneStatusBar;
2393     }
2394 
onGoToKeyguard()2395     public void onGoToKeyguard() {
2396         if (mIsExpanded && mAnimationsEnabled) {
2397             mEverythingNeedsAnimation = true;
2398             requestChildrenUpdate();
2399         }
2400     }
2401 
isBelowLastNotification(float touchX, float touchY)2402     private boolean isBelowLastNotification(float touchX, float touchY) {
2403         ExpandableView lastChildNotGone = (ExpandableView) getLastChildNotGone();
2404         if (lastChildNotGone == null) {
2405             return touchY > mIntrinsicPadding;
2406         }
2407         if (lastChildNotGone != mDismissView && lastChildNotGone != mEmptyShadeView) {
2408             return touchY > lastChildNotGone.getY() + lastChildNotGone.getActualHeight();
2409         } else if (lastChildNotGone == mEmptyShadeView) {
2410             return touchY > mEmptyShadeView.getY();
2411         } else {
2412             float dismissY = mDismissView.getY();
2413             boolean belowDismissView = touchY > dismissY + mDismissView.getActualHeight();
2414             return belowDismissView || (touchY > dismissY
2415                     && mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
2416                     touchY - dismissY));
2417         }
2418     }
2419 
2420     /**
2421      * A listener that is notified when some child locations might have changed.
2422      */
2423     public interface OnChildLocationsChangedListener {
onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout)2424         public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2425     }
2426 
2427     /**
2428      * A listener that is notified when the empty space below the notifications is clicked on
2429      */
2430     public interface OnEmptySpaceClickListener {
onEmptySpaceClicked(float x, float y)2431         public void onEmptySpaceClicked(float x, float y);
2432     }
2433 
2434     /**
2435      * A listener that gets notified when the overscroll at the top has changed.
2436      */
2437     public interface OnOverscrollTopChangedListener {
2438 
2439         /**
2440          * Notifies a listener that the overscroll has changed.
2441          *
2442          * @param amount the amount of overscroll, in pixels
2443          * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2444          *                     unrubberbanded motion to directly expand overscroll view (e.g expand
2445          *                     QS)
2446          */
onOverscrollTopChanged(float amount, boolean isRubberbanded)2447         public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
2448 
2449         /**
2450          * Notify a listener that the scroller wants to escape from the scrolling motion and
2451          * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2452          *
2453          * @param velocity The velocity that the Scroller had when over flinging
2454          * @param open Should the fling open or close the overscroll view.
2455          */
flingTopOverscroll(float velocity, boolean open)2456         public void flingTopOverscroll(float velocity, boolean open);
2457     }
2458 
2459     static class AnimationEvent {
2460 
2461         static AnimationFilter[] FILTERS = new AnimationFilter[] {
2462 
2463                 // ANIMATION_TYPE_ADD
2464                 new AnimationFilter()
2465                         .animateAlpha()
2466                         .animateHeight()
2467                         .animateTopInset()
2468                         .animateY()
2469                         .animateZ()
2470                         .hasDelays(),
2471 
2472                 // ANIMATION_TYPE_REMOVE
2473                 new AnimationFilter()
2474                         .animateAlpha()
2475                         .animateHeight()
2476                         .animateTopInset()
2477                         .animateY()
2478                         .animateZ()
2479                         .hasDelays(),
2480 
2481                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2482                 new AnimationFilter()
2483                         .animateAlpha()
2484                         .animateHeight()
2485                         .animateTopInset()
2486                         .animateY()
2487                         .animateZ()
2488                         .hasDelays(),
2489 
2490                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2491                 new AnimationFilter()
2492                         .animateAlpha()
2493                         .animateHeight()
2494                         .animateTopInset()
2495                         .animateY()
2496                         .animateDimmed()
2497                         .animateScale()
2498                         .animateZ(),
2499 
2500                 // ANIMATION_TYPE_START_DRAG
2501                 new AnimationFilter()
2502                         .animateAlpha(),
2503 
2504                 // ANIMATION_TYPE_SNAP_BACK
2505                 new AnimationFilter()
2506                         .animateAlpha()
2507                         .animateHeight(),
2508 
2509                 // ANIMATION_TYPE_ACTIVATED_CHILD
2510                 new AnimationFilter()
2511                         .animateScale()
2512                         .animateAlpha(),
2513 
2514                 // ANIMATION_TYPE_DIMMED
2515                 new AnimationFilter()
2516                         .animateY()
2517                         .animateScale()
2518                         .animateDimmed(),
2519 
2520                 // ANIMATION_TYPE_CHANGE_POSITION
2521                 new AnimationFilter()
2522                         .animateAlpha()
2523                         .animateHeight()
2524                         .animateTopInset()
2525                         .animateY()
2526                         .animateZ(),
2527 
2528                 // ANIMATION_TYPE_DARK
2529                 new AnimationFilter()
2530                         .animateDark()
2531                         .hasDelays(),
2532 
2533                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2534                 new AnimationFilter()
2535                         .animateAlpha()
2536                         .animateHeight()
2537                         .animateTopInset()
2538                         .animateY()
2539                         .animateDimmed()
2540                         .animateScale()
2541                         .animateZ()
2542                         .hasDelays(),
2543 
2544                 // ANIMATION_TYPE_HIDE_SENSITIVE
2545                 new AnimationFilter()
2546                         .animateHideSensitive(),
2547 
2548                 // ANIMATION_TYPE_VIEW_RESIZE
2549                 new AnimationFilter()
2550                         .animateAlpha()
2551                         .animateHeight()
2552                         .animateTopInset()
2553                         .animateY()
2554                         .animateZ(),
2555 
2556                 // ANIMATION_TYPE_EVERYTHING
2557                 new AnimationFilter()
2558                         .animateAlpha()
2559                         .animateDark()
2560                         .animateScale()
2561                         .animateDimmed()
2562                         .animateHideSensitive()
2563                         .animateHeight()
2564                         .animateTopInset()
2565                         .animateY()
2566                         .animateZ(),
2567         };
2568 
2569         static int[] LENGTHS = new int[] {
2570 
2571                 // ANIMATION_TYPE_ADD
2572                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
2573 
2574                 // ANIMATION_TYPE_REMOVE
2575                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
2576 
2577                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2578                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2579 
2580                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2581                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2582 
2583                 // ANIMATION_TYPE_START_DRAG
2584                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2585 
2586                 // ANIMATION_TYPE_SNAP_BACK
2587                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2588 
2589                 // ANIMATION_TYPE_ACTIVATED_CHILD
2590                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2591 
2592                 // ANIMATION_TYPE_DIMMED
2593                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2594 
2595                 // ANIMATION_TYPE_CHANGE_POSITION
2596                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2597 
2598                 // ANIMATION_TYPE_DARK
2599                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2600 
2601                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2602                 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
2603 
2604                 // ANIMATION_TYPE_HIDE_SENSITIVE
2605                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2606 
2607                 // ANIMATION_TYPE_VIEW_RESIZE
2608                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2609 
2610                 // ANIMATION_TYPE_EVERYTHING
2611                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2612         };
2613 
2614         static final int ANIMATION_TYPE_ADD = 0;
2615         static final int ANIMATION_TYPE_REMOVE = 1;
2616         static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
2617         static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
2618         static final int ANIMATION_TYPE_START_DRAG = 4;
2619         static final int ANIMATION_TYPE_SNAP_BACK = 5;
2620         static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
2621         static final int ANIMATION_TYPE_DIMMED = 7;
2622         static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
2623         static final int ANIMATION_TYPE_DARK = 9;
2624         static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
2625         static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
2626         static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
2627         static final int ANIMATION_TYPE_EVERYTHING = 13;
2628 
2629         static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
2630         static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
2631 
2632         final long eventStartTime;
2633         final View changingView;
2634         final int animationType;
2635         final AnimationFilter filter;
2636         final long length;
2637         View viewAfterChangingView;
2638         int darkAnimationOriginIndex;
2639 
AnimationEvent(View view, int type)2640         AnimationEvent(View view, int type) {
2641             this(view, type, LENGTHS[type]);
2642         }
2643 
AnimationEvent(View view, int type, long length)2644         AnimationEvent(View view, int type, long length) {
2645             eventStartTime = AnimationUtils.currentAnimationTimeMillis();
2646             changingView = view;
2647             animationType = type;
2648             filter = FILTERS[type];
2649             this.length = length;
2650         }
2651 
2652         /**
2653          * Combines the length of several animation events into a single value.
2654          *
2655          * @param events The events of the lengths to combine.
2656          * @return The combined length. Depending on the event types, this might be the maximum of
2657          *         all events or the length of a specific event.
2658          */
combineLength(ArrayList<AnimationEvent> events)2659         static long combineLength(ArrayList<AnimationEvent> events) {
2660             long length = 0;
2661             int size = events.size();
2662             for (int i = 0; i < size; i++) {
2663                 AnimationEvent event = events.get(i);
2664                 length = Math.max(length, event.length);
2665                 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
2666                     return event.length;
2667                 }
2668             }
2669             return length;
2670         }
2671     }
2672 
2673 }
2674