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