• 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.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.PropertyValuesHolder;
23 import android.animation.TimeAnimator;
24 import android.animation.ValueAnimator;
25 import android.animation.ValueAnimator.AnimatorUpdateListener;
26 import android.annotation.FloatRange;
27 import android.annotation.Nullable;
28 import android.content.Context;
29 import android.content.res.Configuration;
30 import android.graphics.Canvas;
31 import android.graphics.Color;
32 import android.graphics.Paint;
33 import android.graphics.PointF;
34 import android.graphics.PorterDuff;
35 import android.graphics.PorterDuffXfermode;
36 import android.graphics.Rect;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.util.AttributeSet;
40 import android.util.FloatProperty;
41 import android.util.Log;
42 import android.util.Pair;
43 import android.util.Property;
44 import android.view.MotionEvent;
45 import android.view.VelocityTracker;
46 import android.view.View;
47 import android.view.ViewConfiguration;
48 import android.view.ViewGroup;
49 import android.view.ViewTreeObserver;
50 import android.view.WindowInsets;
51 import android.view.accessibility.AccessibilityEvent;
52 import android.view.accessibility.AccessibilityNodeInfo;
53 import android.view.animation.AnimationUtils;
54 import android.view.animation.Interpolator;
55 import android.widget.OverScroller;
56 import android.widget.ScrollView;
57 
58 import com.android.internal.logging.MetricsLogger;
59 import com.android.internal.logging.MetricsProto.MetricsEvent;
60 import com.android.systemui.ExpandHelper;
61 import com.android.systemui.Interpolators;
62 import com.android.systemui.R;
63 import com.android.systemui.SwipeHelper;
64 import com.android.systemui.classifier.FalsingManager;
65 import com.android.systemui.statusbar.ActivatableNotificationView;
66 import com.android.systemui.statusbar.DismissView;
67 import com.android.systemui.statusbar.EmptyShadeView;
68 import com.android.systemui.statusbar.ExpandableNotificationRow;
69 import com.android.systemui.statusbar.ExpandableView;
70 import com.android.systemui.statusbar.NotificationGuts;
71 import com.android.systemui.statusbar.NotificationOverflowContainer;
72 import com.android.systemui.statusbar.NotificationSettingsIconRow;
73 import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
74 import com.android.systemui.statusbar.StackScrollerDecorView;
75 import com.android.systemui.statusbar.StatusBarState;
76 import com.android.systemui.statusbar.notification.FakeShadowView;
77 import com.android.systemui.statusbar.notification.NotificationUtils;
78 import com.android.systemui.statusbar.phone.NotificationGroupManager;
79 import com.android.systemui.statusbar.phone.PhoneStatusBar;
80 import com.android.systemui.statusbar.phone.ScrimController;
81 import com.android.systemui.statusbar.policy.HeadsUpManager;
82 import com.android.systemui.statusbar.policy.ScrollAdapter;
83 
84 import java.util.ArrayList;
85 import java.util.Collections;
86 import java.util.Comparator;
87 import java.util.HashSet;
88 
89 /**
90  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
91  */
92 public class NotificationStackScrollLayout extends ViewGroup
93         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
94         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
95         SettingsIconRowListener, ScrollContainer {
96 
97     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
98     private static final String TAG = "StackScroller";
99     private static final boolean DEBUG = false;
100     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
101     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
102     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
103     /**
104      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
105      */
106     private static final int INVALID_POINTER = -1;
107 
108     private ExpandHelper mExpandHelper;
109     private NotificationSwipeHelper mSwipeHelper;
110     private boolean mSwipingInProgress;
111     private int mCurrentStackHeight = Integer.MAX_VALUE;
112     private final Paint mBackgroundPaint = new Paint();
113 
114     /**
115      * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
116      * externally from {@link #setStackHeight}
117      */
118     private float mLastSetStackHeight;
119     private int mOwnScrollY;
120     private int mMaxLayoutHeight;
121 
122     private VelocityTracker mVelocityTracker;
123     private OverScroller mScroller;
124     private Runnable mFinishScrollingCallback;
125     private int mTouchSlop;
126     private int mMinimumVelocity;
127     private int mMaximumVelocity;
128     private int mOverflingDistance;
129     private float mMaxOverScroll;
130     private boolean mIsBeingDragged;
131     private int mLastMotionY;
132     private int mDownX;
133     private int mActivePointerId;
134     private boolean mTouchIsClick;
135     private float mInitialTouchX;
136     private float mInitialTouchY;
137 
138     private Paint mDebugPaint;
139     private int mContentHeight;
140     private int mCollapsedSize;
141     private int mBottomStackSlowDownHeight;
142     private int mBottomStackPeekSize;
143     private int mPaddingBetweenElements;
144     private int mIncreasedPaddingBetweenElements;
145     private int mTopPadding;
146     private int mBottomInset = 0;
147 
148     /**
149      * The algorithm which calculates the properties for our children
150      */
151     private final StackScrollAlgorithm mStackScrollAlgorithm;
152 
153     /**
154      * The current State this Layout is in
155      */
156     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
157     private AmbientState mAmbientState = new AmbientState();
158     private NotificationGroupManager mGroupManager;
159     private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
160     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
161     private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
162     private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
163     private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
164     private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
165     private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
166     private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
167     private ArrayList<View> mSwipedOutViews = new ArrayList<>();
168     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
169     private boolean mAnimationsEnabled;
170     private boolean mChangePositionInProgress;
171     private boolean mChildTransferInProgress;
172 
173     /**
174      * The raw amount of the overScroll on the top, which is not rubber-banded.
175      */
176     private float mOverScrolledTopPixels;
177 
178     /**
179      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
180      */
181     private float mOverScrolledBottomPixels;
182     private OnChildLocationsChangedListener mListener;
183     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
184     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
185     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
186     private boolean mNeedsAnimation;
187     private boolean mTopPaddingNeedsAnimation;
188     private boolean mDimmedNeedsAnimation;
189     private boolean mHideSensitiveNeedsAnimation;
190     private boolean mDarkNeedsAnimation;
191     private int mDarkAnimationOriginIndex;
192     private boolean mActivateNeedsAnimation;
193     private boolean mGoToFullShadeNeedsAnimation;
194     private boolean mIsExpanded = true;
195     private boolean mChildrenUpdateRequested;
196     private boolean mIsExpansionChanging;
197     private boolean mPanelTracking;
198     private boolean mExpandingNotification;
199     private boolean mExpandedInThisMotion;
200     private boolean mScrollingEnabled;
201     private DismissView mDismissView;
202     private EmptyShadeView mEmptyShadeView;
203     private boolean mDismissAllInProgress;
204 
205     /**
206      * Was the scroller scrolled to the top when the down motion was observed?
207      */
208     private boolean mScrolledToTopOnFirstDown;
209     /**
210      * The minimal amount of over scroll which is needed in order to switch to the quick settings
211      * when over scrolling on a expanded card.
212      */
213     private float mMinTopOverScrollToEscape;
214     private int mIntrinsicPadding;
215     private float mStackTranslation;
216     private float mTopPaddingOverflow;
217     private boolean mDontReportNextOverScroll;
218     private boolean mDontClampNextScroll;
219     private boolean mRequestViewResizeAnimationOnLayout;
220     private boolean mNeedViewResizeAnimation;
221     private View mExpandedGroupView;
222     private boolean mEverythingNeedsAnimation;
223 
224     /**
225      * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
226      * This is needed to avoid scrolling too far after the notification was collapsed in the same
227      * motion.
228      */
229     private int mMaxScrollAfterExpand;
230     private SwipeHelper.LongPressListener mLongPressListener;
231 
232     private NotificationSettingsIconRow mCurrIconRow;
233     private View mTranslatingParentView;
234     private View mGearExposedView;
235 
236     /**
237      * Should in this touch motion only be scrolling allowed? It's true when the scroller was
238      * animating.
239      */
240     private boolean mOnlyScrollingInThisMotion;
241     private boolean mDisallowDismissInThisMotion;
242     private boolean mInterceptDelegateEnabled;
243     private boolean mDelegateToScrollView;
244     private boolean mDisallowScrollingInThisMotion;
245     private long mGoToFullShadeDelay;
246     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
247             = new ViewTreeObserver.OnPreDrawListener() {
248         @Override
249         public boolean onPreDraw() {
250             updateForcedScroll();
251             updateChildren();
252             mChildrenUpdateRequested = false;
253             getViewTreeObserver().removeOnPreDrawListener(this);
254             return true;
255         }
256     };
257     private PhoneStatusBar mPhoneStatusBar;
258     private int[] mTempInt2 = new int[2];
259     private boolean mGenerateChildOrderChangedEvent;
260     private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
261     private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
262     private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
263             = new HashSet<>();
264     private HeadsUpManager mHeadsUpManager;
265     private boolean mTrackingHeadsUp;
266     private ScrimController mScrimController;
267     private boolean mForceNoOverlappingRendering;
268     private NotificationOverflowContainer mOverflowContainer;
269     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
270     private FalsingManager mFalsingManager;
271     private boolean mAnimationRunning;
272     private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
273             = new ViewTreeObserver.OnPreDrawListener() {
274         @Override
275         public boolean onPreDraw() {
276             // if it needs animation
277             if (!mNeedsAnimation && !mChildrenUpdateRequested) {
278                 updateBackground();
279             }
280             return true;
281         }
282     };
283     private Rect mBackgroundBounds = new Rect();
284     private Rect mStartAnimationRect = new Rect();
285     private Rect mEndAnimationRect = new Rect();
286     private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
287     private boolean mAnimateNextBackgroundBottom;
288     private boolean mAnimateNextBackgroundTop;
289     private ObjectAnimator mBottomAnimator = null;
290     private ObjectAnimator mTopAnimator = null;
291     private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
292     private ActivatableNotificationView mLastVisibleBackgroundChild = null;
293     private int mBgColor;
294     private float mDimAmount;
295     private ValueAnimator mDimAnimator;
296     private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
297     private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
298         @Override
299         public void onAnimationEnd(Animator animation) {
300             mDimAnimator = null;
301         }
302     };
303     private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
304             = new ValueAnimator.AnimatorUpdateListener() {
305 
306         @Override
307         public void onAnimationUpdate(ValueAnimator animation) {
308             setDimAmount((Float) animation.getAnimatedValue());
309         }
310     };
311     protected ViewGroup mQsContainer;
312     private boolean mContinuousShadowUpdate;
313     private ViewTreeObserver.OnPreDrawListener mShadowUpdater
314             = new ViewTreeObserver.OnPreDrawListener() {
315 
316         @Override
317         public boolean onPreDraw() {
318             updateViewShadows();
319             return true;
320         }
321     };
322     private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
323         @Override
324         public int compare(ExpandableView view, ExpandableView otherView) {
325             float endY = view.getTranslationY() + view.getActualHeight();
326             float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
327             if (endY < otherEndY) {
328                 return -1;
329             } else if (endY > otherEndY) {
330                 return 1;
331             } else {
332                 // The two notifications end at the same location
333                 return 0;
334             }
335         }
336     };
337     private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
338     private boolean mPulsing;
339     private boolean mDrawBackgroundAsSrc;
340     private boolean mFadingOut;
341     private boolean mParentFadingOut;
342     private boolean mGroupExpandedForMeasure;
343     private boolean mScrollable;
344     private View mForcedScroll;
345     private float mBackgroundFadeAmount = 1.0f;
346     private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
347             new FloatProperty<NotificationStackScrollLayout>("backgroundFade") {
348                 @Override
349                 public void setValue(NotificationStackScrollLayout object, float value) {
350                     object.setBackgroundFadeAmount(value);
351                 }
352 
353                 @Override
354                 public Float get(NotificationStackScrollLayout object) {
355                     return object.getBackgroundFadeAmount();
356                 }
357             };
358 
NotificationStackScrollLayout(Context context)359     public NotificationStackScrollLayout(Context context) {
360         this(context, null);
361     }
362 
NotificationStackScrollLayout(Context context, AttributeSet attrs)363     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
364         this(context, attrs, 0);
365     }
366 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr)367     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
368         this(context, attrs, defStyleAttr, 0);
369     }
370 
NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)371     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
372             int defStyleRes) {
373         super(context, attrs, defStyleAttr, defStyleRes);
374         mBgColor = context.getColor(R.color.notification_shade_background_color);
375         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
376         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
377         mExpandHelper = new ExpandHelper(getContext(), this,
378                 minHeight, maxHeight);
379         mExpandHelper.setEventSource(this);
380         mExpandHelper.setScrollAdapter(this);
381         mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
382         mSwipeHelper.setLongPressListener(mLongPressListener);
383         mStackScrollAlgorithm = new StackScrollAlgorithm(context);
384         initView(context);
385         setWillNotDraw(false);
386         if (DEBUG) {
387             mDebugPaint = new Paint();
388             mDebugPaint.setColor(0xffff0000);
389             mDebugPaint.setStrokeWidth(2);
390             mDebugPaint.setStyle(Paint.Style.STROKE);
391         }
392         mFalsingManager = FalsingManager.getInstance(context);
393     }
394 
395     @Override
onGearTouched(ExpandableNotificationRow row, int x, int y)396     public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
397         if (mLongPressListener != null) {
398             MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
399                     row.getStatusBarNotification().getPackageName());
400             mLongPressListener.onLongPress(row, x, y);
401         }
402     }
403 
404     @Override
onSettingsIconRowReset(ExpandableNotificationRow row)405     public void onSettingsIconRowReset(ExpandableNotificationRow row) {
406         if (mTranslatingParentView != null && row == mTranslatingParentView) {
407             mSwipeHelper.setSnappedToGear(false);
408             mGearExposedView = null;
409             mTranslatingParentView = null;
410         }
411     }
412 
413     @Override
onDraw(Canvas canvas)414     protected void onDraw(Canvas canvas) {
415         if (mCurrentBounds.top < mCurrentBounds.bottom) {
416             canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
417                     mBackgroundPaint);
418         }
419         if (DEBUG) {
420             int y = mTopPadding;
421             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
422             y = (int) (getLayoutHeight() - mBottomStackPeekSize
423                     - mBottomStackSlowDownHeight);
424             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
425             y = (int) (getLayoutHeight() - mBottomStackPeekSize);
426             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
427             y = (int) getLayoutHeight();
428             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
429             y = getHeight() - getEmptyBottomMargin();
430             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
431         }
432     }
433 
updateBackgroundDimming()434     private void updateBackgroundDimming() {
435         float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
436         alpha *= mBackgroundFadeAmount;
437         // We need to manually blend in the background color
438         int scrimColor = mScrimController.getScrimBehindColor();
439         // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
440         float alphaInv = 1 - alpha;
441         int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
442                 (int) (mBackgroundFadeAmount * Color.red(mBgColor)
443                         + alphaInv * Color.red(scrimColor)),
444                 (int) (mBackgroundFadeAmount * Color.green(mBgColor)
445                         + alphaInv * Color.green(scrimColor)),
446                 (int) (mBackgroundFadeAmount * Color.blue(mBgColor)
447                         + alphaInv * Color.blue(scrimColor)));
448         mBackgroundPaint.setColor(color);
449         invalidate();
450     }
451 
initView(Context context)452     private void initView(Context context) {
453         mScroller = new OverScroller(getContext());
454         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
455         setClipChildren(false);
456         final ViewConfiguration configuration = ViewConfiguration.get(context);
457         mTouchSlop = configuration.getScaledTouchSlop();
458         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
459         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
460         mOverflingDistance = configuration.getScaledOverflingDistance();
461         mCollapsedSize = context.getResources()
462                 .getDimensionPixelSize(R.dimen.notification_min_height);
463         mBottomStackPeekSize = context.getResources()
464                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
465         mStackScrollAlgorithm.initView(context);
466         mPaddingBetweenElements = Math.max(1, context.getResources()
467                 .getDimensionPixelSize(R.dimen.notification_divider_height));
468         mIncreasedPaddingBetweenElements = context.getResources()
469                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
470         mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
471         mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
472                 R.dimen.min_top_overscroll_to_qs);
473     }
474 
setDrawBackgroundAsSrc(boolean asSrc)475     public void setDrawBackgroundAsSrc(boolean asSrc) {
476         mDrawBackgroundAsSrc = asSrc;
477         updateSrcDrawing();
478     }
479 
updateSrcDrawing()480     private void updateSrcDrawing() {
481         mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut)
482                 ? mSrcMode : null);
483         invalidate();
484     }
485 
notifyHeightChangeListener(ExpandableView view)486     private void notifyHeightChangeListener(ExpandableView view) {
487         if (mOnHeightChangedListener != null) {
488             mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
489         }
490     }
491 
492     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)493     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
494         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
495         // We need to measure all children even the GONE ones, such that the heights are calculated
496         // correctly as they are used to calculate how many we can fit on the screen.
497         final int size = getChildCount();
498         for (int i = 0; i < size; i++) {
499             measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
500         }
501     }
502 
503     @Override
onLayout(boolean changed, int l, int t, int r, int b)504     protected void onLayout(boolean changed, int l, int t, int r, int b) {
505         // we layout all our children centered on the top
506         float centerX = getWidth() / 2.0f;
507         for (int i = 0; i < getChildCount(); i++) {
508             View child = getChildAt(i);
509             // We need to layout all children even the GONE ones, such that the heights are
510             // calculated correctly as they are used to calculate how many we can fit on the screen
511             float width = child.getMeasuredWidth();
512             float height = child.getMeasuredHeight();
513             child.layout((int) (centerX - width / 2.0f),
514                     0,
515                     (int) (centerX + width / 2.0f),
516                     (int) height);
517         }
518         setMaxLayoutHeight(getHeight());
519         updateContentHeight();
520         clampScrollPosition();
521         if (mRequestViewResizeAnimationOnLayout) {
522             requestAnimationOnViewResize(null);
523             mRequestViewResizeAnimationOnLayout = false;
524         }
525         requestChildrenUpdate();
526         updateFirstAndLastBackgroundViews();
527     }
528 
requestAnimationOnViewResize(ExpandableNotificationRow row)529     private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
530         if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
531             mNeedViewResizeAnimation = true;
532             mNeedsAnimation = true;
533         }
534     }
535 
updateSpeedBumpIndex(int newIndex)536     public void updateSpeedBumpIndex(int newIndex) {
537         mAmbientState.setSpeedBumpIndex(newIndex);
538     }
539 
setChildLocationsChangedListener(OnChildLocationsChangedListener listener)540     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
541         mListener = listener;
542     }
543 
544     /**
545      * Returns the location the given child is currently rendered at.
546      *
547      * @param child the child to get the location for
548      * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
549      */
getChildLocation(View child)550     public int getChildLocation(View child) {
551         StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
552         if (childViewState == null) {
553             return StackViewState.LOCATION_UNKNOWN;
554         }
555         if (childViewState.gone) {
556             return StackViewState.LOCATION_GONE;
557         }
558         return childViewState.location;
559     }
560 
setMaxLayoutHeight(int maxLayoutHeight)561     private void setMaxLayoutHeight(int maxLayoutHeight) {
562         mMaxLayoutHeight = maxLayoutHeight;
563         updateAlgorithmHeightAndPadding();
564     }
565 
updateAlgorithmHeightAndPadding()566     private void updateAlgorithmHeightAndPadding() {
567         mAmbientState.setLayoutHeight(getLayoutHeight());
568         mAmbientState.setTopPadding(mTopPadding);
569     }
570 
571     /**
572      * Updates the children views according to the stack scroll algorithm. Call this whenever
573      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
574      */
updateChildren()575     private void updateChildren() {
576         updateScrollStateForAddedChildren();
577         mAmbientState.setScrollY(mOwnScrollY);
578         mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
579         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
580             applyCurrentState();
581         } else {
582             startAnimationToState();
583         }
584     }
585 
updateScrollStateForAddedChildren()586     private void updateScrollStateForAddedChildren() {
587         if (mChildrenToAddAnimated.isEmpty()) {
588             return;
589         }
590         for (int i = 0; i < getChildCount(); i++) {
591             ExpandableView child = (ExpandableView) getChildAt(i);
592             if (mChildrenToAddAnimated.contains(child)) {
593                 int startingPosition = getPositionInLinearLayout(child);
594                 int padding = child.getIncreasedPaddingAmount() == 1.0f
595                         ? mIncreasedPaddingBetweenElements :
596                         mPaddingBetweenElements;
597                 int childHeight = getIntrinsicHeight(child) + padding;
598                 if (startingPosition < mOwnScrollY) {
599                     // This child starts off screen, so let's keep it offscreen to keep the others visible
600 
601                     mOwnScrollY += childHeight;
602                 }
603             }
604         }
605         clampScrollPosition();
606     }
607 
updateForcedScroll()608     private void updateForcedScroll() {
609         if (mForcedScroll != null && (!mForcedScroll.hasFocus()
610                 || !mForcedScroll.isAttachedToWindow())) {
611             mForcedScroll = null;
612         }
613         if (mForcedScroll != null) {
614             ExpandableView expandableView = (ExpandableView) mForcedScroll;
615             int positionInLinearLayout = getPositionInLinearLayout(expandableView);
616             int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
617             int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
618 
619             targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
620 
621             // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
622             // that it is not visible anymore.
623             if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
624                 mOwnScrollY = targetScroll;
625             }
626         }
627     }
628 
requestChildrenUpdate()629     private void requestChildrenUpdate() {
630         if (!mChildrenUpdateRequested) {
631             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
632             mChildrenUpdateRequested = true;
633             invalidate();
634         }
635     }
636 
isCurrentlyAnimating()637     private boolean isCurrentlyAnimating() {
638         return mStateAnimator.isRunning();
639     }
640 
clampScrollPosition()641     private void clampScrollPosition() {
642         int scrollRange = getScrollRange();
643         if (scrollRange < mOwnScrollY) {
644             mOwnScrollY = scrollRange;
645         }
646     }
647 
getTopPadding()648     public int getTopPadding() {
649         return mTopPadding;
650     }
651 
setTopPadding(int topPadding, boolean animate)652     private void setTopPadding(int topPadding, boolean animate) {
653         if (mTopPadding != topPadding) {
654             mTopPadding = topPadding;
655             updateAlgorithmHeightAndPadding();
656             updateContentHeight();
657             if (animate && mAnimationsEnabled && mIsExpanded) {
658                 mTopPaddingNeedsAnimation = true;
659                 mNeedsAnimation =  true;
660             }
661             requestChildrenUpdate();
662             notifyHeightChangeListener(null);
663         }
664     }
665 
666     /**
667      * Update the height of the stack to a new height.
668      *
669      * @param height the new height of the stack
670      */
setStackHeight(float height)671     public void setStackHeight(float height) {
672         mLastSetStackHeight = height;
673         setIsExpanded(height > 0.0f);
674         int stackHeight;
675         float translationY;
676         float appearEndPosition = getAppearEndPosition();
677         float appearStartPosition = getAppearStartPosition();
678         if (height >= appearEndPosition) {
679             translationY = mTopPaddingOverflow;
680             stackHeight = (int) height;
681         } else {
682             float appearFraction = getAppearFraction(height);
683             if (appearFraction >= 0) {
684                 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
685                         appearFraction);
686             } else {
687                 // This may happen when pushing up a heads up. We linearly push it up from the
688                 // start
689                 translationY = height - appearStartPosition + getExpandTranslationStart();
690             }
691             stackHeight = (int) (height - translationY);
692         }
693         if (stackHeight != mCurrentStackHeight) {
694             mCurrentStackHeight = stackHeight;
695             updateAlgorithmHeightAndPadding();
696             requestChildrenUpdate();
697         }
698         setStackTranslation(translationY);
699     }
700 
701     /**
702      * @return The translation at the beginning when expanding.
703      *         Measured relative to the resting position.
704      */
getExpandTranslationStart()705     private float getExpandTranslationStart() {
706         int startPosition = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
707                 ? 0 : -getFirstChildIntrinsicHeight();
708         return startPosition - mTopPadding;
709     }
710 
711     /**
712      * @return the position from where the appear transition starts when expanding.
713      *         Measured in absolute height.
714      */
getAppearStartPosition()715     private float getAppearStartPosition() {
716         return mTrackingHeadsUp
717                 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
718                 : 0;
719     }
720 
721     /**
722      * @return the position from where the appear transition ends when expanding.
723      *         Measured in absolute height.
724      */
getAppearEndPosition()725     private float getAppearEndPosition() {
726         int firstItemHeight = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
727                 ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize
728                         + mBottomStackSlowDownHeight
729                 : getLayoutMinHeight();
730         return firstItemHeight + mTopPadding + mTopPaddingOverflow;
731     }
732 
733     /**
734      * @param height the height of the panel
735      * @return the fraction of the appear animation that has been performed
736      */
getAppearFraction(float height)737     public float getAppearFraction(float height) {
738         float appearEndPosition = getAppearEndPosition();
739         float appearStartPosition = getAppearStartPosition();
740         return (height - appearStartPosition)
741                 / (appearEndPosition - appearStartPosition);
742     }
743 
getStackTranslation()744     public float getStackTranslation() {
745         return mStackTranslation;
746     }
747 
setStackTranslation(float stackTranslation)748     private void setStackTranslation(float stackTranslation) {
749         if (stackTranslation != mStackTranslation) {
750             mStackTranslation = stackTranslation;
751             mAmbientState.setStackTranslation(stackTranslation);
752             requestChildrenUpdate();
753         }
754     }
755 
756     /**
757      * Get the current height of the view. This is at most the msize of the view given by a the
758      * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
759      *
760      * @return either the layout height or the externally defined height, whichever is smaller
761      */
getLayoutHeight()762     private int getLayoutHeight() {
763         return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
764     }
765 
getFirstItemMinHeight()766     public int getFirstItemMinHeight() {
767         final ExpandableView firstChild = getFirstChildNotGone();
768         return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
769     }
770 
getBottomStackPeekSize()771     public int getBottomStackPeekSize() {
772         return mBottomStackPeekSize;
773     }
774 
getBottomStackSlowDownHeight()775     public int getBottomStackSlowDownHeight() {
776         return mBottomStackSlowDownHeight;
777     }
778 
setLongPressListener(SwipeHelper.LongPressListener listener)779     public void setLongPressListener(SwipeHelper.LongPressListener listener) {
780         mSwipeHelper.setLongPressListener(listener);
781         mLongPressListener = listener;
782     }
783 
setQsContainer(ViewGroup qsContainer)784     public void setQsContainer(ViewGroup qsContainer) {
785         mQsContainer = qsContainer;
786     }
787 
788     @Override
onChildDismissed(View v)789     public void onChildDismissed(View v) {
790         ExpandableNotificationRow row = (ExpandableNotificationRow) v;
791         if (!row.isDismissed()) {
792             handleChildDismissed(v);
793         }
794         ViewGroup transientContainer = row.getTransientContainer();
795         if (transientContainer != null) {
796             transientContainer.removeTransientView(v);
797         }
798     }
799 
handleChildDismissed(View v)800     private void handleChildDismissed(View v) {
801         if (mDismissAllInProgress) {
802             return;
803         }
804         setSwipingInProgress(false);
805         if (mDragAnimPendingChildren.contains(v)) {
806             // We start the swipe and finish it in the same frame, we don't want any animation
807             // for the drag
808             mDragAnimPendingChildren.remove(v);
809         }
810         mSwipedOutViews.add(v);
811         mAmbientState.onDragFinished(v);
812         updateContinuousShadowDrawing();
813         if (v instanceof ExpandableNotificationRow) {
814             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
815             if (row.isHeadsUp()) {
816                 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
817             }
818         }
819         performDismiss(v, mGroupManager, false /* fromAccessibility */);
820 
821         mFalsingManager.onNotificationDismissed();
822         if (mFalsingManager.shouldEnforceBouncer()) {
823             mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
824                     false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
825         }
826     }
827 
performDismiss(View v, NotificationGroupManager groupManager, boolean fromAccessibility)828     public static void performDismiss(View v, NotificationGroupManager groupManager,
829             boolean fromAccessibility) {
830         if (!(v instanceof ExpandableNotificationRow)) {
831             return;
832         }
833         ExpandableNotificationRow row = (ExpandableNotificationRow) v;
834         if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
835             ExpandableNotificationRow groupSummary =
836                     groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
837             if (groupSummary.isClearable()) {
838                 performDismiss(groupSummary, groupManager, fromAccessibility);
839             }
840         }
841         row.setDismissed(true, fromAccessibility);
842         if (row.isClearable()) {
843             row.performDismiss();
844         }
845         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
846     }
847 
848     @Override
onChildSnappedBack(View animView, float targetLeft)849     public void onChildSnappedBack(View animView, float targetLeft) {
850         mAmbientState.onDragFinished(animView);
851         updateContinuousShadowDrawing();
852         if (!mDragAnimPendingChildren.contains(animView)) {
853             if (mAnimationsEnabled) {
854                 mSnappedBackChildren.add(animView);
855                 mNeedsAnimation = true;
856             }
857             requestChildrenUpdate();
858         } else {
859             // We start the swipe and snap back in the same frame, we don't want any animation
860             mDragAnimPendingChildren.remove(animView);
861         }
862         if (mCurrIconRow != null && targetLeft == 0) {
863             mCurrIconRow.resetState();
864             mCurrIconRow = null;
865         }
866     }
867 
868     @Override
updateSwipeProgress(View animView, boolean dismissable, float swipeProgress)869     public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
870         if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
871             mScrimController.setTopHeadsUpDragAmount(animView,
872                     Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f));
873         }
874         return true; // Don't fade out the notification
875     }
876 
877     @Override
onBeginDrag(View v)878     public void onBeginDrag(View v) {
879         mFalsingManager.onNotificatonStartDismissing();
880         setSwipingInProgress(true);
881         mAmbientState.onBeginDrag(v);
882         updateContinuousShadowDrawing();
883         if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
884             mDragAnimPendingChildren.add(v);
885             mNeedsAnimation = true;
886         }
887         requestChildrenUpdate();
888     }
889 
isPinnedHeadsUp(View v)890     public static boolean isPinnedHeadsUp(View v) {
891         if (v instanceof ExpandableNotificationRow) {
892             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
893             return row.isHeadsUp() && row.isPinned();
894         }
895         return false;
896     }
897 
isHeadsUp(View v)898     private boolean isHeadsUp(View v) {
899         if (v instanceof ExpandableNotificationRow) {
900             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
901             return row.isHeadsUp();
902         }
903         return false;
904     }
905 
906     @Override
onDragCancelled(View v)907     public void onDragCancelled(View v) {
908         mFalsingManager.onNotificatonStopDismissing();
909         setSwipingInProgress(false);
910     }
911 
912     @Override
getFalsingThresholdFactor()913     public float getFalsingThresholdFactor() {
914         return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
915     }
916 
917     @Override
getChildAtPosition(MotionEvent ev)918     public View getChildAtPosition(MotionEvent ev) {
919         View child = getChildAtPosition(ev.getX(), ev.getY());
920         if (child instanceof ExpandableNotificationRow) {
921             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
922             ExpandableNotificationRow parent = row.getNotificationParent();
923             if (parent != null && parent.areChildrenExpanded()
924                     && (parent.areGutsExposed()
925                         || mGearExposedView == parent
926                         || (parent.getNotificationChildren().size() == 1
927                                 && parent.isClearable()))) {
928                 // In this case the group is expanded and showing the gear for the
929                 // group, further interaction should apply to the group, not any
930                 // child notifications so we use the parent of the child. We also do the same
931                 // if we only have a single child.
932                 child = parent;
933             }
934         }
935         return child;
936     }
937 
getClosestChildAtRawPosition(float touchX, float touchY)938     public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
939         getLocationOnScreen(mTempInt2);
940         float localTouchY = touchY - mTempInt2[1];
941 
942         ExpandableView closestChild = null;
943         float minDist = Float.MAX_VALUE;
944 
945         // find the view closest to the location, accounting for GONE views
946         final int count = getChildCount();
947         for (int childIdx = 0; childIdx < count; childIdx++) {
948             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
949             if (slidingChild.getVisibility() == GONE
950                     || slidingChild instanceof StackScrollerDecorView) {
951                 continue;
952             }
953             float childTop = slidingChild.getTranslationY();
954             float top = childTop + slidingChild.getClipTopAmount();
955             float bottom = childTop + slidingChild.getActualHeight();
956 
957             float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
958             if (dist < minDist) {
959                 closestChild = slidingChild;
960                 minDist = dist;
961             }
962         }
963         return closestChild;
964     }
965 
966     @Override
getChildAtRawPosition(float touchX, float touchY)967     public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
968         getLocationOnScreen(mTempInt2);
969         return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
970     }
971 
972     @Override
getChildAtPosition(float touchX, float touchY)973     public ExpandableView getChildAtPosition(float touchX, float touchY) {
974         // find the view under the pointer, accounting for GONE views
975         final int count = getChildCount();
976         for (int childIdx = 0; childIdx < count; childIdx++) {
977             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
978             if (slidingChild.getVisibility() == GONE
979                     || slidingChild instanceof StackScrollerDecorView) {
980                 continue;
981             }
982             float childTop = slidingChild.getTranslationY();
983             float top = childTop + slidingChild.getClipTopAmount();
984             float bottom = childTop + slidingChild.getActualHeight();
985 
986             // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
987             // camera affordance).
988             int left = 0;
989             int right = getWidth();
990 
991             if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
992                 if (slidingChild instanceof ExpandableNotificationRow) {
993                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
994                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
995                             && mHeadsUpManager.getTopEntry().entry.row != row
996                             && mGroupManager.getGroupSummary(
997                                 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
998                                 != row) {
999                         continue;
1000                     }
1001                     return row.getViewAtPosition(touchY - childTop);
1002                 }
1003                 return slidingChild;
1004             }
1005         }
1006         return null;
1007     }
1008 
1009     @Override
canChildBeExpanded(View v)1010     public boolean canChildBeExpanded(View v) {
1011         return v instanceof ExpandableNotificationRow
1012                 && ((ExpandableNotificationRow) v).isExpandable()
1013                 && !((ExpandableNotificationRow) v).areGutsExposed()
1014                 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
1015     }
1016 
1017     /* Only ever called as a consequence of an expansion gesture in the shade. */
1018     @Override
setUserExpandedChild(View v, boolean userExpanded)1019     public void setUserExpandedChild(View v, boolean userExpanded) {
1020         if (v instanceof ExpandableNotificationRow) {
1021             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1022             row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
1023             row.onExpandedByGesture(userExpanded);
1024         }
1025     }
1026 
1027     @Override
setExpansionCancelled(View v)1028     public void setExpansionCancelled(View v) {
1029         if (v instanceof ExpandableNotificationRow) {
1030             ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
1031         }
1032     }
1033 
1034     @Override
setUserLockedChild(View v, boolean userLocked)1035     public void setUserLockedChild(View v, boolean userLocked) {
1036         if (v instanceof ExpandableNotificationRow) {
1037             ((ExpandableNotificationRow) v).setUserLocked(userLocked);
1038         }
1039         removeLongPressCallback();
1040         requestDisallowInterceptTouchEvent(true);
1041     }
1042 
1043     @Override
expansionStateChanged(boolean isExpanding)1044     public void expansionStateChanged(boolean isExpanding) {
1045         mExpandingNotification = isExpanding;
1046         if (!mExpandedInThisMotion) {
1047             mMaxScrollAfterExpand = mOwnScrollY;
1048             mExpandedInThisMotion = true;
1049         }
1050     }
1051 
1052     @Override
getMaxExpandHeight(ExpandableView view)1053     public int getMaxExpandHeight(ExpandableView view) {
1054         int maxContentHeight = view.getMaxContentHeight();
1055         if (view.isSummaryWithChildren()) {
1056             // Faking a measure with the group expanded to simulate how the group would look if
1057             // it was. Doing a calculation here would be highly non-trivial because of the
1058             // algorithm
1059             mGroupExpandedForMeasure = true;
1060             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1061             mGroupManager.toggleGroupExpansion(row.getStatusBarNotification());
1062             row.setForceUnlocked(true);
1063             mAmbientState.setLayoutHeight(mMaxLayoutHeight);
1064             mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
1065             mAmbientState.setLayoutHeight(getLayoutHeight());
1066             mGroupManager.toggleGroupExpansion(
1067                     row.getStatusBarNotification());
1068             mGroupExpandedForMeasure = false;
1069             row.setForceUnlocked(false);
1070             int height = mCurrentStackScrollState.getViewStateForView(view).height;
1071             return Math.min(height, maxContentHeight);
1072         }
1073         return maxContentHeight;
1074     }
1075 
setScrollingEnabled(boolean enable)1076     public void setScrollingEnabled(boolean enable) {
1077         mScrollingEnabled = enable;
1078     }
1079 
1080     @Override
lockScrollTo(View v)1081     public void lockScrollTo(View v) {
1082         if (mForcedScroll == v) {
1083             return;
1084         }
1085         mForcedScroll = v;
1086         scrollTo(v);
1087     }
1088 
1089     @Override
scrollTo(View v)1090     public boolean scrollTo(View v) {
1091         ExpandableView expandableView = (ExpandableView) v;
1092         int positionInLinearLayout = getPositionInLinearLayout(v);
1093         int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1094         int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
1095 
1096         // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1097         // that it is not visible anymore.
1098         if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1099             mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1100             mDontReportNextOverScroll = true;
1101             postInvalidateOnAnimation();
1102             return true;
1103         }
1104         return false;
1105     }
1106 
1107     /**
1108      * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
1109      *         the IME.
1110      */
targetScrollForView(ExpandableView v, int positionInLinearLayout)1111     private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1112         return positionInLinearLayout + v.getIntrinsicHeight() +
1113                 getImeInset() - getHeight() + getTopPadding();
1114     }
1115 
1116     @Override
onApplyWindowInsets(WindowInsets insets)1117     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1118         mBottomInset = insets.getSystemWindowInsetBottom();
1119 
1120         int range = getScrollRange();
1121         if (mOwnScrollY > range) {
1122             // HACK: We're repeatedly getting staggered insets here while the IME is
1123             // animating away. To work around that we'll wait until things have settled.
1124             removeCallbacks(mReclamp);
1125             postDelayed(mReclamp, 50);
1126         } else if (mForcedScroll != null) {
1127             // The scroll was requested before we got the actual inset - in case we need
1128             // to scroll up some more do so now.
1129             scrollTo(mForcedScroll);
1130         }
1131         return insets;
1132     }
1133 
1134     private Runnable mReclamp = new Runnable() {
1135         @Override
1136         public void run() {
1137             int range = getScrollRange();
1138             mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1139             mDontReportNextOverScroll = true;
1140             mDontClampNextScroll = true;
1141             postInvalidateOnAnimation();
1142         }
1143     };
1144 
setExpandingEnabled(boolean enable)1145     public void setExpandingEnabled(boolean enable) {
1146         mExpandHelper.setEnabled(enable);
1147     }
1148 
isScrollingEnabled()1149     private boolean isScrollingEnabled() {
1150         return mScrollingEnabled;
1151     }
1152 
1153     @Override
canChildBeDismissed(View v)1154     public boolean canChildBeDismissed(View v) {
1155         return StackScrollAlgorithm.canChildBeDismissed(v);
1156     }
1157 
1158     @Override
isAntiFalsingNeeded()1159     public boolean isAntiFalsingNeeded() {
1160         return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
1161     }
1162 
setSwipingInProgress(boolean isSwiped)1163     private void setSwipingInProgress(boolean isSwiped) {
1164         mSwipingInProgress = isSwiped;
1165         if(isSwiped) {
1166             requestDisallowInterceptTouchEvent(true);
1167         }
1168     }
1169 
1170     @Override
onConfigurationChanged(Configuration newConfig)1171     protected void onConfigurationChanged(Configuration newConfig) {
1172         super.onConfigurationChanged(newConfig);
1173         float densityScale = getResources().getDisplayMetrics().density;
1174         mSwipeHelper.setDensityScale(densityScale);
1175         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1176         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1177         initView(getContext());
1178     }
1179 
dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration)1180     public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
1181         mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1182                 true /* isDismissAll */);
1183     }
1184 
snapViewIfNeeded(ExpandableNotificationRow child)1185     public void snapViewIfNeeded(ExpandableNotificationRow child) {
1186         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
1187         // If the child is showing the gear to go to settings, snap to that
1188         float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
1189         mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
1190     }
1191 
1192     @Override
onTouchEvent(MotionEvent ev)1193     public boolean onTouchEvent(MotionEvent ev) {
1194         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1195                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
1196         handleEmptySpaceClick(ev);
1197         boolean expandWantsIt = false;
1198         if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
1199             if (isCancelOrUp) {
1200                 mExpandHelper.onlyObserveMovements(false);
1201             }
1202             boolean wasExpandingBefore = mExpandingNotification;
1203             expandWantsIt = mExpandHelper.onTouchEvent(ev);
1204             if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
1205                     && !mDisallowScrollingInThisMotion) {
1206                 dispatchDownEventToScroller(ev);
1207             }
1208         }
1209         boolean scrollerWantsIt = false;
1210         if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
1211                 && !mDisallowScrollingInThisMotion) {
1212             scrollerWantsIt = onScrollTouch(ev);
1213         }
1214         boolean horizontalSwipeWantsIt = false;
1215         if (!mIsBeingDragged
1216                 && !mExpandingNotification
1217                 && !mExpandedInThisMotion
1218                 && !mOnlyScrollingInThisMotion
1219                 && !mDisallowDismissInThisMotion) {
1220             horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
1221         }
1222         return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
1223     }
1224 
dispatchDownEventToScroller(MotionEvent ev)1225     private void dispatchDownEventToScroller(MotionEvent ev) {
1226         MotionEvent downEvent = MotionEvent.obtain(ev);
1227         downEvent.setAction(MotionEvent.ACTION_DOWN);
1228         onScrollTouch(downEvent);
1229         downEvent.recycle();
1230     }
1231 
onScrollTouch(MotionEvent ev)1232     private boolean onScrollTouch(MotionEvent ev) {
1233         if (!isScrollingEnabled()) {
1234             return false;
1235         }
1236         if (ev.getY() < mQsContainer.getBottom()) {
1237             return false;
1238         }
1239         mForcedScroll = null;
1240         initVelocityTrackerIfNotExists();
1241         mVelocityTracker.addMovement(ev);
1242 
1243         final int action = ev.getAction();
1244 
1245         switch (action & MotionEvent.ACTION_MASK) {
1246             case MotionEvent.ACTION_DOWN: {
1247                 if (getChildCount() == 0 || !isInContentBounds(ev)) {
1248                     return false;
1249                 }
1250                 boolean isBeingDragged = !mScroller.isFinished();
1251                 setIsBeingDragged(isBeingDragged);
1252 
1253                 /*
1254                  * If being flinged and user touches, stop the fling. isFinished
1255                  * will be false if being flinged.
1256                  */
1257                 if (!mScroller.isFinished()) {
1258                     mScroller.forceFinished(true);
1259                 }
1260 
1261                 // Remember where the motion event started
1262                 mLastMotionY = (int) ev.getY();
1263                 mDownX = (int) ev.getX();
1264                 mActivePointerId = ev.getPointerId(0);
1265                 break;
1266             }
1267             case MotionEvent.ACTION_MOVE:
1268                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1269                 if (activePointerIndex == -1) {
1270                     Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1271                     break;
1272                 }
1273 
1274                 final int y = (int) ev.getY(activePointerIndex);
1275                 final int x = (int) ev.getX(activePointerIndex);
1276                 int deltaY = mLastMotionY - y;
1277                 final int xDiff = Math.abs(x - mDownX);
1278                 final int yDiff = Math.abs(deltaY);
1279                 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
1280                     setIsBeingDragged(true);
1281                     if (deltaY > 0) {
1282                         deltaY -= mTouchSlop;
1283                     } else {
1284                         deltaY += mTouchSlop;
1285                     }
1286                 }
1287                 if (mIsBeingDragged) {
1288                     // Scroll to follow the motion event
1289                     mLastMotionY = y;
1290                     int range = getScrollRange();
1291                     if (mExpandedInThisMotion) {
1292                         range = Math.min(range, mMaxScrollAfterExpand);
1293                     }
1294 
1295                     float scrollAmount;
1296                     if (deltaY < 0) {
1297                         scrollAmount = overScrollDown(deltaY);
1298                     } else {
1299                         scrollAmount = overScrollUp(deltaY, range);
1300                     }
1301 
1302                     // Calling overScrollBy will call onOverScrolled, which
1303                     // calls onScrollChanged if applicable.
1304                     if (scrollAmount != 0.0f) {
1305                         // The scrolling motion could not be compensated with the
1306                         // existing overScroll, we have to scroll the view
1307                         overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1308                                 0, range, 0, getHeight() / 2, true);
1309                     }
1310                 }
1311                 break;
1312             case MotionEvent.ACTION_UP:
1313                 if (mIsBeingDragged) {
1314                     final VelocityTracker velocityTracker = mVelocityTracker;
1315                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1316                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1317 
1318                     if (shouldOverScrollFling(initialVelocity)) {
1319                         onOverScrollFling(true, initialVelocity);
1320                     } else {
1321                         if (getChildCount() > 0) {
1322                             if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1323                                 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1324                                 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1325                                     fling(-initialVelocity);
1326                                 } else {
1327                                     onOverScrollFling(false, initialVelocity);
1328                                 }
1329                             } else {
1330                                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1331                                         getScrollRange())) {
1332                                     postInvalidateOnAnimation();
1333                                 }
1334                             }
1335                         }
1336                     }
1337 
1338                     mActivePointerId = INVALID_POINTER;
1339                     endDrag();
1340                 }
1341 
1342                 break;
1343             case MotionEvent.ACTION_CANCEL:
1344                 if (mIsBeingDragged && getChildCount() > 0) {
1345                     if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1346                         postInvalidateOnAnimation();
1347                     }
1348                     mActivePointerId = INVALID_POINTER;
1349                     endDrag();
1350                 }
1351                 break;
1352             case MotionEvent.ACTION_POINTER_DOWN: {
1353                 final int index = ev.getActionIndex();
1354                 mLastMotionY = (int) ev.getY(index);
1355                 mDownX = (int) ev.getX(index);
1356                 mActivePointerId = ev.getPointerId(index);
1357                 break;
1358             }
1359             case MotionEvent.ACTION_POINTER_UP:
1360                 onSecondaryPointerUp(ev);
1361                 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
1362                 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
1363                 break;
1364         }
1365         return true;
1366     }
1367 
onOverScrollFling(boolean open, int initialVelocity)1368     private void onOverScrollFling(boolean open, int initialVelocity) {
1369         if (mOverscrollTopChangedListener != null) {
1370             mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1371         }
1372         mDontReportNextOverScroll = true;
1373         setOverScrollAmount(0.0f, true, false);
1374     }
1375 
1376     /**
1377      * Perform a scroll upwards and adapt the overscroll amounts accordingly
1378      *
1379      * @param deltaY The amount to scroll upwards, has to be positive.
1380      * @return The amount of scrolling to be performed by the scroller,
1381      *         not handled by the overScroll amount.
1382      */
overScrollUp(int deltaY, int range)1383     private float overScrollUp(int deltaY, int range) {
1384         deltaY = Math.max(deltaY, 0);
1385         float currentTopAmount = getCurrentOverScrollAmount(true);
1386         float newTopAmount = currentTopAmount - deltaY;
1387         if (currentTopAmount > 0) {
1388             setOverScrollAmount(newTopAmount, true /* onTop */,
1389                     false /* animate */);
1390         }
1391         // Top overScroll might not grab all scrolling motion,
1392         // we have to scroll as well.
1393         float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1394         float newScrollY = mOwnScrollY + scrollAmount;
1395         if (newScrollY > range) {
1396             if (!mExpandedInThisMotion) {
1397                 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1398                 // We overScroll on the top
1399                 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1400                         false /* onTop */,
1401                         false /* animate */);
1402             }
1403             mOwnScrollY = range;
1404             scrollAmount = 0.0f;
1405         }
1406         return scrollAmount;
1407     }
1408 
1409     /**
1410      * Perform a scroll downward and adapt the overscroll amounts accordingly
1411      *
1412      * @param deltaY The amount to scroll downwards, has to be negative.
1413      * @return The amount of scrolling to be performed by the scroller,
1414      *         not handled by the overScroll amount.
1415      */
overScrollDown(int deltaY)1416     private float overScrollDown(int deltaY) {
1417         deltaY = Math.min(deltaY, 0);
1418         float currentBottomAmount = getCurrentOverScrollAmount(false);
1419         float newBottomAmount = currentBottomAmount + deltaY;
1420         if (currentBottomAmount > 0) {
1421             setOverScrollAmount(newBottomAmount, false /* onTop */,
1422                     false /* animate */);
1423         }
1424         // Bottom overScroll might not grab all scrolling motion,
1425         // we have to scroll as well.
1426         float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1427         float newScrollY = mOwnScrollY + scrollAmount;
1428         if (newScrollY < 0) {
1429             float currentTopPixels = getCurrentOverScrolledPixels(true);
1430             // We overScroll on the top
1431             setOverScrolledPixels(currentTopPixels - newScrollY,
1432                     true /* onTop */,
1433                     false /* animate */);
1434             mOwnScrollY = 0;
1435             scrollAmount = 0.0f;
1436         }
1437         return scrollAmount;
1438     }
1439 
1440     private void onSecondaryPointerUp(MotionEvent ev) {
1441         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1442                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1443         final int pointerId = ev.getPointerId(pointerIndex);
1444         if (pointerId == mActivePointerId) {
1445             // This was our active pointer going up. Choose a new
1446             // active pointer and adjust accordingly.
1447             // TODO: Make this decision more intelligent.
1448             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1449             mLastMotionY = (int) ev.getY(newPointerIndex);
1450             mActivePointerId = ev.getPointerId(newPointerIndex);
1451             if (mVelocityTracker != null) {
1452                 mVelocityTracker.clear();
1453             }
1454         }
1455     }
1456 
initVelocityTrackerIfNotExists()1457     private void initVelocityTrackerIfNotExists() {
1458         if (mVelocityTracker == null) {
1459             mVelocityTracker = VelocityTracker.obtain();
1460         }
1461     }
1462 
recycleVelocityTracker()1463     private void recycleVelocityTracker() {
1464         if (mVelocityTracker != null) {
1465             mVelocityTracker.recycle();
1466             mVelocityTracker = null;
1467         }
1468     }
1469 
initOrResetVelocityTracker()1470     private void initOrResetVelocityTracker() {
1471         if (mVelocityTracker == null) {
1472             mVelocityTracker = VelocityTracker.obtain();
1473         } else {
1474             mVelocityTracker.clear();
1475         }
1476     }
1477 
setFinishScrollingCallback(Runnable runnable)1478     public void setFinishScrollingCallback(Runnable runnable) {
1479         mFinishScrollingCallback = runnable;
1480     }
1481 
1482     @Override
computeScroll()1483     public void computeScroll() {
1484         if (mScroller.computeScrollOffset()) {
1485             // This is called at drawing time by ViewGroup.
1486             int oldX = mScrollX;
1487             int oldY = mOwnScrollY;
1488             int x = mScroller.getCurrX();
1489             int y = mScroller.getCurrY();
1490 
1491             if (oldX != x || oldY != y) {
1492                 int range = getScrollRange();
1493                 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1494                     float currVelocity = mScroller.getCurrVelocity();
1495                     if (currVelocity >= mMinimumVelocity) {
1496                         mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1497                     }
1498                 }
1499 
1500                 if (mDontClampNextScroll) {
1501                     range = Math.max(range, oldY);
1502                 }
1503                 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
1504                         0, (int) (mMaxOverScroll), false);
1505                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1506             }
1507 
1508             // Keep on drawing until the animation has finished.
1509             postInvalidateOnAnimation();
1510         } else {
1511             mDontClampNextScroll = false;
1512             if (mFinishScrollingCallback != null) {
1513                 mFinishScrollingCallback.run();
1514             }
1515         }
1516     }
1517 
1518     @Override
overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)1519     protected boolean overScrollBy(int deltaX, int deltaY,
1520             int scrollX, int scrollY,
1521             int scrollRangeX, int scrollRangeY,
1522             int maxOverScrollX, int maxOverScrollY,
1523             boolean isTouchEvent) {
1524 
1525         int newScrollY = scrollY + deltaY;
1526 
1527         final int top = -maxOverScrollY;
1528         final int bottom = maxOverScrollY + scrollRangeY;
1529 
1530         boolean clampedY = false;
1531         if (newScrollY > bottom) {
1532             newScrollY = bottom;
1533             clampedY = true;
1534         } else if (newScrollY < top) {
1535             newScrollY = top;
1536             clampedY = true;
1537         }
1538 
1539         onOverScrolled(0, newScrollY, false, clampedY);
1540 
1541         return clampedY;
1542     }
1543 
1544     /**
1545      * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1546      * overscroll effect based on numPixels. By default this will also cancel animations on the
1547      * same overScroll edge.
1548      *
1549      * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1550      *                  the rubber-banding logic.
1551      * @param onTop Should the effect be applied on top of the scroller.
1552      * @param animate Should an animation be performed.
1553      */
setOverScrolledPixels(float numPixels, boolean onTop, boolean animate)1554     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
1555         setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
1556     }
1557 
1558     /**
1559      * Set the effective overScroll amount which will be directly reflected in the layout.
1560      * By default this will also cancel animations on the same overScroll edge.
1561      *
1562      * @param amount The amount to overScroll by.
1563      * @param onTop Should the effect be applied on top of the scroller.
1564      * @param animate Should an animation be performed.
1565      */
setOverScrollAmount(float amount, boolean onTop, boolean animate)1566     public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1567         setOverScrollAmount(amount, onTop, animate, true);
1568     }
1569 
1570     /**
1571      * Set the effective overScroll amount which will be directly reflected in the layout.
1572      *
1573      * @param amount The amount to overScroll by.
1574      * @param onTop Should the effect be applied on top of the scroller.
1575      * @param animate Should an animation be performed.
1576      * @param cancelAnimators Should running animations be cancelled.
1577      */
setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators)1578     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1579             boolean cancelAnimators) {
1580         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1581     }
1582 
1583     /**
1584      * Set the effective overScroll amount which will be directly reflected in the layout.
1585      *
1586      * @param amount The amount to overScroll by.
1587      * @param onTop Should the effect be applied on top of the scroller.
1588      * @param animate Should an animation be performed.
1589      * @param cancelAnimators Should running animations be cancelled.
1590      * @param isRubberbanded The value which will be passed to
1591      *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1592      */
setOverScrollAmount(float amount, boolean onTop, boolean animate, boolean cancelAnimators, boolean isRubberbanded)1593     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1594             boolean cancelAnimators, boolean isRubberbanded) {
1595         if (cancelAnimators) {
1596             mStateAnimator.cancelOverScrollAnimators(onTop);
1597         }
1598         setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
1599     }
1600 
setOverScrollAmountInternal(float amount, boolean onTop, boolean animate, boolean isRubberbanded)1601     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1602             boolean isRubberbanded) {
1603         amount = Math.max(0, amount);
1604         if (animate) {
1605             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
1606         } else {
1607             setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
1608             mAmbientState.setOverScrollAmount(amount, onTop);
1609             if (onTop) {
1610                 notifyOverscrollTopListener(amount, isRubberbanded);
1611             }
1612             requestChildrenUpdate();
1613         }
1614     }
1615 
notifyOverscrollTopListener(float amount, boolean isRubberbanded)1616     private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
1617         mExpandHelper.onlyObserveMovements(amount > 1.0f);
1618         if (mDontReportNextOverScroll) {
1619             mDontReportNextOverScroll = false;
1620             return;
1621         }
1622         if (mOverscrollTopChangedListener != null) {
1623             mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
1624         }
1625     }
1626 
setOverscrollTopChangedListener( OnOverscrollTopChangedListener overscrollTopChangedListener)1627     public void setOverscrollTopChangedListener(
1628             OnOverscrollTopChangedListener overscrollTopChangedListener) {
1629         mOverscrollTopChangedListener = overscrollTopChangedListener;
1630     }
1631 
getCurrentOverScrollAmount(boolean top)1632     public float getCurrentOverScrollAmount(boolean top) {
1633         return mAmbientState.getOverScrollAmount(top);
1634     }
1635 
getCurrentOverScrolledPixels(boolean top)1636     public float getCurrentOverScrolledPixels(boolean top) {
1637         return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1638     }
1639 
setOverScrolledPixels(float amount, boolean onTop)1640     private void setOverScrolledPixels(float amount, boolean onTop) {
1641         if (onTop) {
1642             mOverScrolledTopPixels = amount;
1643         } else {
1644             mOverScrolledBottomPixels = amount;
1645         }
1646     }
1647 
customScrollTo(int y)1648     private void customScrollTo(int y) {
1649         mOwnScrollY = y;
1650         updateChildren();
1651     }
1652 
1653     @Override
onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)1654     protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
1655         // Treat animating scrolls differently; see #computeScroll() for why.
1656         if (!mScroller.isFinished()) {
1657             final int oldX = mScrollX;
1658             final int oldY = mOwnScrollY;
1659             mScrollX = scrollX;
1660             mOwnScrollY = scrollY;
1661             if (clampedY) {
1662                 springBack();
1663             } else {
1664                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1665                 invalidateParentIfNeeded();
1666                 updateChildren();
1667                 float overScrollTop = getCurrentOverScrollAmount(true);
1668                 if (mOwnScrollY < 0) {
1669                     notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
1670                 } else {
1671                     notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
1672                 }
1673             }
1674         } else {
1675             customScrollTo(scrollY);
1676             scrollTo(scrollX, mScrollY);
1677         }
1678     }
1679 
springBack()1680     private void springBack() {
1681         int scrollRange = getScrollRange();
1682         boolean overScrolledTop = mOwnScrollY <= 0;
1683         boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1684         if (overScrolledTop || overScrolledBottom) {
1685             boolean onTop;
1686             float newAmount;
1687             if (overScrolledTop) {
1688                 onTop = true;
1689                 newAmount = -mOwnScrollY;
1690                 mOwnScrollY = 0;
1691                 mDontReportNextOverScroll = true;
1692             } else {
1693                 onTop = false;
1694                 newAmount = mOwnScrollY - scrollRange;
1695                 mOwnScrollY = scrollRange;
1696             }
1697             setOverScrollAmount(newAmount, onTop, false);
1698             setOverScrollAmount(0.0f, onTop, true);
1699             mScroller.forceFinished(true);
1700         }
1701     }
1702 
getScrollRange()1703     private int getScrollRange() {
1704         int contentHeight = getContentHeight();
1705         int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1706                 + mBottomStackSlowDownHeight);
1707         int imeInset = getImeInset();
1708         scrollRange += Math.min(imeInset, Math.max(0,
1709                 getContentHeight() - (getHeight() - imeInset)));
1710         return scrollRange;
1711     }
1712 
getImeInset()1713     private int getImeInset() {
1714         return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
1715     }
1716 
1717     /**
1718      * @return the first child which has visibility unequal to GONE
1719      */
getFirstChildNotGone()1720     public ExpandableView getFirstChildNotGone() {
1721         int childCount = getChildCount();
1722         for (int i = 0; i < childCount; i++) {
1723             View child = getChildAt(i);
1724             if (child.getVisibility() != View.GONE) {
1725                 return (ExpandableView) child;
1726             }
1727         }
1728         return null;
1729     }
1730 
1731     /**
1732      * @return the child before the given view which has visibility unequal to GONE
1733      */
getViewBeforeView(ExpandableView view)1734     public ExpandableView getViewBeforeView(ExpandableView view) {
1735         ExpandableView previousView = null;
1736         int childCount = getChildCount();
1737         for (int i = 0; i < childCount; i++) {
1738             View child = getChildAt(i);
1739             if (child == view) {
1740                 return previousView;
1741             }
1742             if (child.getVisibility() != View.GONE) {
1743                 previousView = (ExpandableView) child;
1744             }
1745         }
1746         return null;
1747     }
1748 
1749     /**
1750      * @return The first child which has visibility unequal to GONE which is currently below the
1751      *         given translationY or equal to it.
1752      */
getFirstChildBelowTranlsationY(float translationY)1753     private View getFirstChildBelowTranlsationY(float translationY) {
1754         int childCount = getChildCount();
1755         for (int i = 0; i < childCount; i++) {
1756             View child = getChildAt(i);
1757             if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1758                 return child;
1759             }
1760         }
1761         return null;
1762     }
1763 
1764     /**
1765      * @return the last child which has visibility unequal to GONE
1766      */
getLastChildNotGone()1767     public View getLastChildNotGone() {
1768         int childCount = getChildCount();
1769         for (int i = childCount - 1; i >= 0; i--) {
1770             View child = getChildAt(i);
1771             if (child.getVisibility() != View.GONE) {
1772                 return child;
1773             }
1774         }
1775         return null;
1776     }
1777 
1778     /**
1779      * @return the number of children which have visibility unequal to GONE
1780      */
getNotGoneChildCount()1781     public int getNotGoneChildCount() {
1782         int childCount = getChildCount();
1783         int count = 0;
1784         for (int i = 0; i < childCount; i++) {
1785             ExpandableView child = (ExpandableView) getChildAt(i);
1786             if (child.getVisibility() != View.GONE && !child.willBeGone()) {
1787                 count++;
1788             }
1789         }
1790         return count;
1791     }
1792 
getContentHeight()1793     public int getContentHeight() {
1794         return mContentHeight;
1795     }
1796 
updateContentHeight()1797     private void updateContentHeight() {
1798         int height = 0;
1799         float previousIncreasedAmount = 0.0f;
1800         for (int i = 0; i < getChildCount(); i++) {
1801             ExpandableView expandableView = (ExpandableView) getChildAt(i);
1802             if (expandableView.getVisibility() != View.GONE) {
1803                 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
1804                 if (height != 0) {
1805                     height += (int) NotificationUtils.interpolate(
1806                             mPaddingBetweenElements,
1807                             mIncreasedPaddingBetweenElements,
1808                             Math.max(previousIncreasedAmount, increasedPaddingAmount));
1809                 }
1810                 previousIncreasedAmount = increasedPaddingAmount;
1811                 height += expandableView.getIntrinsicHeight();
1812             }
1813         }
1814         mContentHeight = height + mTopPadding;
1815         updateScrollability();
1816     }
1817 
updateScrollability()1818     private void updateScrollability() {
1819         boolean scrollable = getScrollRange() > 0;
1820         if (scrollable != mScrollable) {
1821             mScrollable = scrollable;
1822             setFocusable(scrollable);
1823         }
1824     }
1825 
updateBackground()1826     private void updateBackground() {
1827         if (mAmbientState.isDark()) {
1828             return;
1829         }
1830         updateBackgroundBounds();
1831         if (!mCurrentBounds.equals(mBackgroundBounds)) {
1832             if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
1833                 startBackgroundAnimation();
1834             } else {
1835                 mCurrentBounds.set(mBackgroundBounds);
1836                 applyCurrentBackgroundBounds();
1837             }
1838         } else {
1839             if (mBottomAnimator != null) {
1840                 mBottomAnimator.cancel();
1841             }
1842             if (mTopAnimator != null) {
1843                 mTopAnimator.cancel();
1844             }
1845         }
1846         mAnimateNextBackgroundBottom = false;
1847         mAnimateNextBackgroundTop = false;
1848     }
1849 
areBoundsAnimating()1850     private boolean areBoundsAnimating() {
1851         return mBottomAnimator != null || mTopAnimator != null;
1852     }
1853 
startBackgroundAnimation()1854     private void startBackgroundAnimation() {
1855         // left and right are always instantly applied
1856         mCurrentBounds.left = mBackgroundBounds.left;
1857         mCurrentBounds.right = mBackgroundBounds.right;
1858         startBottomAnimation();
1859         startTopAnimation();
1860     }
1861 
startTopAnimation()1862     private void startTopAnimation() {
1863         int previousEndValue = mEndAnimationRect.top;
1864         int newEndValue = mBackgroundBounds.top;
1865         ObjectAnimator previousAnimator = mTopAnimator;
1866         if (previousAnimator != null && previousEndValue == newEndValue) {
1867             return;
1868         }
1869         if (!mAnimateNextBackgroundTop) {
1870             // just a local update was performed
1871             if (previousAnimator != null) {
1872                 // we need to increase all animation keyframes of the previous animator by the
1873                 // relative change to the end value
1874                 int previousStartValue = mStartAnimationRect.top;
1875                 PropertyValuesHolder[] values = previousAnimator.getValues();
1876                 values[0].setIntValues(previousStartValue, newEndValue);
1877                 mStartAnimationRect.top = previousStartValue;
1878                 mEndAnimationRect.top = newEndValue;
1879                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1880                 return;
1881             } else {
1882                 // no new animation needed, let's just apply the value
1883                 setBackgroundTop(newEndValue);
1884                 return;
1885             }
1886         }
1887         if (previousAnimator != null) {
1888             previousAnimator.cancel();
1889         }
1890         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
1891                 mCurrentBounds.top, newEndValue);
1892         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1893         animator.setInterpolator(interpolator);
1894         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1895         // remove the tag when the animation is finished
1896         animator.addListener(new AnimatorListenerAdapter() {
1897             @Override
1898             public void onAnimationEnd(Animator animation) {
1899                 mStartAnimationRect.top = -1;
1900                 mEndAnimationRect.top = -1;
1901                 mTopAnimator = null;
1902             }
1903         });
1904         animator.start();
1905         mStartAnimationRect.top = mCurrentBounds.top;
1906         mEndAnimationRect.top = newEndValue;
1907         mTopAnimator = animator;
1908     }
1909 
startBottomAnimation()1910     private void startBottomAnimation() {
1911         int previousStartValue = mStartAnimationRect.bottom;
1912         int previousEndValue = mEndAnimationRect.bottom;
1913         int newEndValue = mBackgroundBounds.bottom;
1914         ObjectAnimator previousAnimator = mBottomAnimator;
1915         if (previousAnimator != null && previousEndValue == newEndValue) {
1916             return;
1917         }
1918         if (!mAnimateNextBackgroundBottom) {
1919             // just a local update was performed
1920             if (previousAnimator != null) {
1921                 // we need to increase all animation keyframes of the previous animator by the
1922                 // relative change to the end value
1923                 PropertyValuesHolder[] values = previousAnimator.getValues();
1924                 values[0].setIntValues(previousStartValue, newEndValue);
1925                 mStartAnimationRect.bottom = previousStartValue;
1926                 mEndAnimationRect.bottom = newEndValue;
1927                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1928                 return;
1929             } else {
1930                 // no new animation needed, let's just apply the value
1931                 setBackgroundBottom(newEndValue);
1932                 return;
1933             }
1934         }
1935         if (previousAnimator != null) {
1936             previousAnimator.cancel();
1937         }
1938         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
1939                 mCurrentBounds.bottom, newEndValue);
1940         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1941         animator.setInterpolator(interpolator);
1942         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1943         // remove the tag when the animation is finished
1944         animator.addListener(new AnimatorListenerAdapter() {
1945             @Override
1946             public void onAnimationEnd(Animator animation) {
1947                 mStartAnimationRect.bottom = -1;
1948                 mEndAnimationRect.bottom = -1;
1949                 mBottomAnimator = null;
1950             }
1951         });
1952         animator.start();
1953         mStartAnimationRect.bottom = mCurrentBounds.bottom;
1954         mEndAnimationRect.bottom = newEndValue;
1955         mBottomAnimator = animator;
1956     }
1957 
setBackgroundTop(int top)1958     private void setBackgroundTop(int top) {
1959         mCurrentBounds.top = top;
1960         applyCurrentBackgroundBounds();
1961     }
1962 
setBackgroundBottom(int bottom)1963     public void setBackgroundBottom(int bottom) {
1964         mCurrentBounds.bottom = bottom;
1965         applyCurrentBackgroundBounds();
1966     }
1967 
applyCurrentBackgroundBounds()1968     private void applyCurrentBackgroundBounds() {
1969         if (!mFadingOut) {
1970             mScrimController.setExcludedBackgroundArea(mCurrentBounds);
1971         }
1972         invalidate();
1973     }
1974 
1975     /**
1976      * Update the background bounds to the new desired bounds
1977      */
updateBackgroundBounds()1978     private void updateBackgroundBounds() {
1979         mBackgroundBounds.left = (int) getX();
1980         mBackgroundBounds.right = (int) (getX() + getWidth());
1981         if (!mIsExpanded) {
1982             mBackgroundBounds.top = 0;
1983             mBackgroundBounds.bottom = 0;
1984         }
1985         ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
1986         int top = 0;
1987         if (firstView != null) {
1988             int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
1989             if (mAnimateNextBackgroundTop
1990                     || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
1991                     || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
1992                 // we're ending up at the same location as we are now, lets just skip the animation
1993                 top = finalTranslationY;
1994             } else {
1995                 top = (int) firstView.getTranslationY();
1996             }
1997         }
1998         ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
1999         int bottom = 0;
2000         if (lastView != null) {
2001             int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
2002             int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
2003             int finalBottom = finalTranslationY + finalHeight;
2004             finalBottom = Math.min(finalBottom, getHeight());
2005             if (mAnimateNextBackgroundBottom
2006                     || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
2007                     || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
2008                 // we're ending up at the same location as we are now, lets just skip the animation
2009                 bottom = finalBottom;
2010             } else {
2011                 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
2012                 bottom = Math.min(bottom, getHeight());
2013             }
2014         } else {
2015             top = mTopPadding;
2016             bottom = top;
2017         }
2018         if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
2019             top = (int) Math.max(mTopPadding + mStackTranslation, top);
2020         } else {
2021             // otherwise the animation from the shade to the keyguard will jump as it's maxed
2022             top = Math.max(0, top);
2023         }
2024         mBackgroundBounds.top = top;
2025         mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
2026     }
2027 
getFirstPinnedHeadsUp()2028     private ActivatableNotificationView getFirstPinnedHeadsUp() {
2029         int childCount = getChildCount();
2030         for (int i = 0; i < childCount; i++) {
2031             View child = getChildAt(i);
2032             if (child.getVisibility() != View.GONE
2033                     && child instanceof ExpandableNotificationRow) {
2034                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2035                 if (row.isPinned()) {
2036                     return row;
2037                 }
2038             }
2039         }
2040         return null;
2041     }
2042 
getLastChildWithBackground()2043     private ActivatableNotificationView getLastChildWithBackground() {
2044         int childCount = getChildCount();
2045         for (int i = childCount - 1; i >= 0; i--) {
2046             View child = getChildAt(i);
2047             if (child.getVisibility() != View.GONE
2048                     && child instanceof ActivatableNotificationView) {
2049                 return (ActivatableNotificationView) child;
2050             }
2051         }
2052         return null;
2053     }
2054 
getFirstChildWithBackground()2055     private ActivatableNotificationView getFirstChildWithBackground() {
2056         int childCount = getChildCount();
2057         for (int i = 0; i < childCount; i++) {
2058             View child = getChildAt(i);
2059             if (child.getVisibility() != View.GONE
2060                     && child instanceof ActivatableNotificationView) {
2061                 return (ActivatableNotificationView) child;
2062             }
2063         }
2064         return null;
2065     }
2066 
2067     /**
2068      * Fling the scroll view
2069      *
2070      * @param velocityY The initial velocity in the Y direction. Positive
2071      *                  numbers mean that the finger/cursor is moving down the screen,
2072      *                  which means we want to scroll towards the top.
2073      */
fling(int velocityY)2074     private void fling(int velocityY) {
2075         if (getChildCount() > 0) {
2076             int scrollRange = getScrollRange();
2077 
2078             float topAmount = getCurrentOverScrollAmount(true);
2079             float bottomAmount = getCurrentOverScrollAmount(false);
2080             if (velocityY < 0 && topAmount > 0) {
2081                 mOwnScrollY -= (int) topAmount;
2082                 mDontReportNextOverScroll = true;
2083                 setOverScrollAmount(0, true, false);
2084                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
2085                         * mOverflingDistance + topAmount;
2086             } else if (velocityY > 0 && bottomAmount > 0) {
2087                 mOwnScrollY += bottomAmount;
2088                 setOverScrollAmount(0, false, false);
2089                 mMaxOverScroll = Math.abs(velocityY) / 1000f
2090                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
2091                         +  bottomAmount;
2092             } else {
2093                 // it will be set once we reach the boundary
2094                 mMaxOverScroll = 0.0f;
2095             }
2096             int minScrollY = Math.max(0, scrollRange);
2097             if (mExpandedInThisMotion) {
2098                 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2099             }
2100             mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
2101                     minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
2102 
2103             postInvalidateOnAnimation();
2104         }
2105     }
2106 
2107     /**
2108      * @return Whether a fling performed on the top overscroll edge lead to the expanded
2109      * overScroll view (i.e QS).
2110      */
shouldOverScrollFling(int initialVelocity)2111     private boolean shouldOverScrollFling(int initialVelocity) {
2112         float topOverScroll = getCurrentOverScrollAmount(true);
2113         return mScrolledToTopOnFirstDown
2114                 && !mExpandedInThisMotion
2115                 && topOverScroll > mMinTopOverScrollToEscape
2116                 && initialVelocity > 0;
2117     }
2118 
2119     /**
2120      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2121      * account.
2122      *
2123      * @param qsHeight the top padding imposed by the quick settings panel
2124      * @param animate whether to animate the change
2125      * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
2126      *                               {@code qsHeight} is the final top padding
2127      */
updateTopPadding(float qsHeight, boolean animate, boolean ignoreIntrinsicPadding)2128     public void updateTopPadding(float qsHeight, boolean animate,
2129             boolean ignoreIntrinsicPadding) {
2130         float start = qsHeight;
2131         float stackHeight = getHeight() - start;
2132         int minStackHeight = getLayoutMinHeight();
2133         if (stackHeight <= minStackHeight) {
2134             float overflow = minStackHeight - stackHeight;
2135             stackHeight = minStackHeight;
2136             start = getHeight() - stackHeight;
2137             mTopPaddingOverflow = overflow;
2138         } else {
2139             mTopPaddingOverflow = 0;
2140         }
2141         setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
2142                 animate);
2143         setStackHeight(mLastSetStackHeight);
2144     }
2145 
getLayoutMinHeight()2146     public int getLayoutMinHeight() {
2147         int firstChildMinHeight = getFirstChildIntrinsicHeight();
2148         return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
2149                 mMaxLayoutHeight - mTopPadding);
2150     }
2151 
getFirstChildIntrinsicHeight()2152     public int getFirstChildIntrinsicHeight() {
2153         final ExpandableView firstChild = getFirstChildNotGone();
2154         int firstChildMinHeight = firstChild != null
2155                 ? firstChild.getIntrinsicHeight()
2156                 : mEmptyShadeView != null
2157                         ? mEmptyShadeView.getIntrinsicHeight()
2158                         : mCollapsedSize;
2159         if (mOwnScrollY > 0) {
2160             firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
2161         }
2162         return firstChildMinHeight;
2163     }
2164 
getTopPaddingOverflow()2165     public float getTopPaddingOverflow() {
2166         return mTopPaddingOverflow;
2167     }
2168 
getPeekHeight()2169     public int getPeekHeight() {
2170         final ExpandableView firstChild = getFirstChildNotGone();
2171         final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
2172                 : mCollapsedSize;
2173         return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
2174                 + mBottomStackSlowDownHeight;
2175     }
2176 
clampPadding(int desiredPadding)2177     private int clampPadding(int desiredPadding) {
2178         return Math.max(desiredPadding, mIntrinsicPadding);
2179     }
2180 
getRubberBandFactor(boolean onTop)2181     private float getRubberBandFactor(boolean onTop) {
2182         if (!onTop) {
2183             return RUBBER_BAND_FACTOR_NORMAL;
2184         }
2185         if (mExpandedInThisMotion) {
2186             return RUBBER_BAND_FACTOR_AFTER_EXPAND;
2187         } else if (mIsExpansionChanging || mPanelTracking) {
2188             return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2189         } else if (mScrolledToTopOnFirstDown) {
2190             return 1.0f;
2191         }
2192         return RUBBER_BAND_FACTOR_NORMAL;
2193     }
2194 
2195     /**
2196      * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2197      * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2198      * overscroll view (e.g. expand QS).
2199      */
isRubberbanded(boolean onTop)2200     private boolean isRubberbanded(boolean onTop) {
2201         return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
2202                 || !mScrolledToTopOnFirstDown;
2203     }
2204 
endDrag()2205     private void endDrag() {
2206         setIsBeingDragged(false);
2207 
2208         recycleVelocityTracker();
2209 
2210         if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
2211             setOverScrollAmount(0, true /* onTop */, true /* animate */);
2212         }
2213         if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
2214             setOverScrollAmount(0, false /* onTop */, true /* animate */);
2215         }
2216     }
2217 
transformTouchEvent(MotionEvent ev, View sourceView, View targetView)2218     private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
2219         ev.offsetLocation(sourceView.getX(), sourceView.getY());
2220         ev.offsetLocation(-targetView.getX(), -targetView.getY());
2221     }
2222 
2223     @Override
onInterceptTouchEvent(MotionEvent ev)2224     public boolean onInterceptTouchEvent(MotionEvent ev) {
2225         initDownStates(ev);
2226         handleEmptySpaceClick(ev);
2227         boolean expandWantsIt = false;
2228         if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
2229             expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
2230         }
2231         boolean scrollWantsIt = false;
2232         if (!mSwipingInProgress && !mExpandingNotification) {
2233             scrollWantsIt = onInterceptTouchEventScroll(ev);
2234         }
2235         boolean swipeWantsIt = false;
2236         if (!mIsBeingDragged
2237                 && !mExpandingNotification
2238                 && !mExpandedInThisMotion
2239                 && !mOnlyScrollingInThisMotion
2240                 && !mDisallowDismissInThisMotion) {
2241             swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
2242         }
2243         return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
2244     }
2245 
handleEmptySpaceClick(MotionEvent ev)2246     private void handleEmptySpaceClick(MotionEvent ev) {
2247         switch (ev.getActionMasked()) {
2248             case MotionEvent.ACTION_MOVE:
2249                 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
2250                         || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
2251                     mTouchIsClick = false;
2252                 }
2253                 break;
2254             case MotionEvent.ACTION_UP:
2255                 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
2256                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
2257                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
2258                 }
2259                 break;
2260         }
2261     }
2262 
initDownStates(MotionEvent ev)2263     private void initDownStates(MotionEvent ev) {
2264         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
2265             mExpandedInThisMotion = false;
2266             mOnlyScrollingInThisMotion = !mScroller.isFinished();
2267             mDisallowScrollingInThisMotion = false;
2268             mDisallowDismissInThisMotion = false;
2269             mTouchIsClick = true;
2270             mInitialTouchX = ev.getX();
2271             mInitialTouchY = ev.getY();
2272         }
2273     }
2274 
setChildTransferInProgress(boolean childTransferInProgress)2275     public void setChildTransferInProgress(boolean childTransferInProgress) {
2276         mChildTransferInProgress = childTransferInProgress;
2277     }
2278 
2279     @Override
onViewRemoved(View child)2280     public void onViewRemoved(View child) {
2281         super.onViewRemoved(child);
2282         // we only call our internal methods if this is actually a removal and not just a
2283         // notification which becomes a child notification
2284         if (!mChildTransferInProgress) {
2285             onViewRemovedInternal(child, this);
2286         }
2287     }
2288 
2289     @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)2290     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2291         super.requestDisallowInterceptTouchEvent(disallowIntercept);
2292         if (disallowIntercept) {
2293             mSwipeHelper.removeLongPressCallback();
2294         }
2295     }
2296 
onViewRemovedInternal(View child, ViewGroup container)2297     private void onViewRemovedInternal(View child, ViewGroup container) {
2298         if (mChangePositionInProgress) {
2299             // This is only a position change, don't do anything special
2300             return;
2301         }
2302         ExpandableView expandableView = (ExpandableView) child;
2303         expandableView.setOnHeightChangedListener(null);
2304         mCurrentStackScrollState.removeViewStateForView(child);
2305         updateScrollStateForRemovedChild(expandableView);
2306         boolean animationGenerated = generateRemoveAnimation(child);
2307         if (animationGenerated) {
2308             if (!mSwipedOutViews.contains(child)) {
2309                 container.getOverlay().add(child);
2310             } else if (Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
2311                 container.addTransientView(child, 0);
2312                 expandableView.setTransientContainer(container);
2313             }
2314         } else {
2315             mSwipedOutViews.remove(child);
2316         }
2317         updateAnimationState(false, child);
2318 
2319         // Make sure the clipRect we might have set is removed
2320         expandableView.setClipTopAmount(0);
2321 
2322         focusNextViewIfFocused(child);
2323     }
2324 
focusNextViewIfFocused(View view)2325     private void focusNextViewIfFocused(View view) {
2326         if (view instanceof ExpandableNotificationRow) {
2327             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2328             if (row.shouldRefocusOnDismiss()) {
2329                 View nextView = row.getChildAfterViewWhenDismissed();
2330                 if (nextView == null) {
2331                     View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
2332                     nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
2333                             ? groupParentWhenDismissed.getTranslationY()
2334                             : view.getTranslationY());
2335                 }
2336                 if (nextView != null) {
2337                     nextView.requestAccessibilityFocus();
2338                 }
2339             }
2340         }
2341 
2342     }
2343 
isChildInGroup(View child)2344     private boolean isChildInGroup(View child) {
2345         return child instanceof ExpandableNotificationRow
2346                 && mGroupManager.isChildInGroupWithSummary(
2347                         ((ExpandableNotificationRow) child).getStatusBarNotification());
2348     }
2349 
2350     /**
2351      * Generate a remove animation for a child view.
2352      *
2353      * @param child The view to generate the remove animation for.
2354      * @return Whether an animation was generated.
2355      */
generateRemoveAnimation(View child)2356     private boolean generateRemoveAnimation(View child) {
2357         if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
2358             mAddedHeadsUpChildren.remove(child);
2359             return false;
2360         }
2361         if (isClickedHeadsUp(child)) {
2362             // An animation is already running, add it to the Overlay
2363             mClearOverlayViewsWhenFinished.add(child);
2364             return true;
2365         }
2366         if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
2367             if (!mChildrenToAddAnimated.contains(child)) {
2368                 // Generate Animations
2369                 mChildrenToRemoveAnimated.add(child);
2370                 mNeedsAnimation = true;
2371                 return true;
2372             } else {
2373                 mChildrenToAddAnimated.remove(child);
2374                 mFromMoreCardAdditions.remove(child);
2375                 return false;
2376             }
2377         }
2378         return false;
2379     }
2380 
isClickedHeadsUp(View child)2381     private boolean isClickedHeadsUp(View child) {
2382         return HeadsUpManager.isClickedHeadsUpNotification(child);
2383     }
2384 
2385     /**
2386      * Remove a removed child view from the heads up animations if it was just added there
2387      *
2388      * @return whether any child was removed from the list to animate
2389      */
removeRemovedChildFromHeadsUpChangeAnimations(View child)2390     private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2391         boolean hasAddEvent = false;
2392         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2393             ExpandableNotificationRow row = eventPair.first;
2394             boolean isHeadsUp = eventPair.second;
2395             if (child == row) {
2396                 mTmpList.add(eventPair);
2397                 hasAddEvent |= isHeadsUp;
2398             }
2399         }
2400         if (hasAddEvent) {
2401             // This child was just added lets remove all events.
2402             mHeadsUpChangeAnimations.removeAll(mTmpList);
2403             ((ExpandableNotificationRow ) child).setHeadsupDisappearRunning(false);
2404         }
2405         mTmpList.clear();
2406         return hasAddEvent;
2407     }
2408 
2409     /**
2410      * @param child the child to query
2411      * @return whether a view is not a top level child but a child notification and that group is
2412      *         not expanded
2413      */
isChildInInvisibleGroup(View child)2414     private boolean isChildInInvisibleGroup(View child) {
2415         if (child instanceof ExpandableNotificationRow) {
2416             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2417             ExpandableNotificationRow groupSummary =
2418                     mGroupManager.getGroupSummary(row.getStatusBarNotification());
2419             if (groupSummary != null && groupSummary != row) {
2420                 return row.getVisibility() == View.INVISIBLE;
2421             }
2422         }
2423         return false;
2424     }
2425 
2426     /**
2427      * Updates the scroll position when a child was removed
2428      *
2429      * @param removedChild the removed child
2430      */
updateScrollStateForRemovedChild(ExpandableView removedChild)2431     private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
2432         int startingPosition = getPositionInLinearLayout(removedChild);
2433         int padding = (int) NotificationUtils.interpolate(
2434                 mPaddingBetweenElements,
2435                 mIncreasedPaddingBetweenElements,
2436                 removedChild.getIncreasedPaddingAmount());
2437         int childHeight = getIntrinsicHeight(removedChild) + padding;
2438         int endPosition = startingPosition + childHeight;
2439         if (endPosition <= mOwnScrollY) {
2440             // This child is fully scrolled of the top, so we have to deduct its height from the
2441             // scrollPosition
2442             mOwnScrollY -= childHeight;
2443         } else if (startingPosition < mOwnScrollY) {
2444             // This child is currently being scrolled into, set the scroll position to the start of
2445             // this child
2446             mOwnScrollY = startingPosition;
2447         }
2448     }
2449 
getIntrinsicHeight(View view)2450     private int getIntrinsicHeight(View view) {
2451         if (view instanceof ExpandableView) {
2452             ExpandableView expandableView = (ExpandableView) view;
2453             return expandableView.getIntrinsicHeight();
2454         }
2455         return view.getHeight();
2456     }
2457 
getPositionInLinearLayout(View requestedView)2458     private int getPositionInLinearLayout(View requestedView) {
2459         ExpandableNotificationRow childInGroup = null;
2460         ExpandableNotificationRow requestedRow = null;
2461         if (isChildInGroup(requestedView)) {
2462             // We're asking for a child in a group. Calculate the position of the parent first,
2463             // then within the parent.
2464             childInGroup = (ExpandableNotificationRow) requestedView;
2465             requestedView = requestedRow = childInGroup.getNotificationParent();
2466         }
2467         int position = 0;
2468         float previousIncreasedAmount = 0.0f;
2469         for (int i = 0; i < getChildCount(); i++) {
2470             ExpandableView child = (ExpandableView) getChildAt(i);
2471             boolean notGone = child.getVisibility() != View.GONE;
2472             if (notGone) {
2473                 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
2474                 if (position != 0) {
2475                     position += (int) NotificationUtils.interpolate(
2476                             mPaddingBetweenElements,
2477                             mIncreasedPaddingBetweenElements,
2478                             Math.max(previousIncreasedAmount, increasedPaddingAmount));
2479                 }
2480                 previousIncreasedAmount = increasedPaddingAmount;
2481             }
2482             if (child == requestedView) {
2483                 if (requestedRow != null) {
2484                     position += requestedRow.getPositionOfChild(childInGroup);
2485                 }
2486                 return position;
2487             }
2488             if (notGone) {
2489                 position += getIntrinsicHeight(child);
2490             }
2491         }
2492         return 0;
2493     }
2494 
2495     @Override
onViewAdded(View child)2496     public void onViewAdded(View child) {
2497         super.onViewAdded(child);
2498         onViewAddedInternal(child);
2499     }
2500 
updateFirstAndLastBackgroundViews()2501     private void updateFirstAndLastBackgroundViews() {
2502         ActivatableNotificationView firstChild = getFirstChildWithBackground();
2503         ActivatableNotificationView lastChild = getLastChildWithBackground();
2504         if (mAnimationsEnabled && mIsExpanded) {
2505             mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2506             mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2507         } else {
2508             mAnimateNextBackgroundTop = false;
2509             mAnimateNextBackgroundBottom = false;
2510         }
2511         mFirstVisibleBackgroundChild = firstChild;
2512         mLastVisibleBackgroundChild = lastChild;
2513     }
2514 
onViewAddedInternal(View child)2515     private void onViewAddedInternal(View child) {
2516         updateHideSensitiveForChild(child);
2517         ((ExpandableView) child).setOnHeightChangedListener(this);
2518         generateAddAnimation(child, false /* fromMoreCard */);
2519         updateAnimationState(child);
2520         updateChronometerForChild(child);
2521     }
2522 
updateHideSensitiveForChild(View child)2523     private void updateHideSensitiveForChild(View child) {
2524         if (child instanceof ExpandableView) {
2525             ExpandableView expandableView = (ExpandableView) child;
2526             expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
2527         }
2528     }
2529 
notifyGroupChildRemoved(View row, ViewGroup childrenContainer)2530     public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
2531         onViewRemovedInternal(row, childrenContainer);
2532     }
2533 
notifyGroupChildAdded(View row)2534     public void notifyGroupChildAdded(View row) {
2535         onViewAddedInternal(row);
2536     }
2537 
setAnimationsEnabled(boolean animationsEnabled)2538     public void setAnimationsEnabled(boolean animationsEnabled) {
2539         mAnimationsEnabled = animationsEnabled;
2540         updateNotificationAnimationStates();
2541     }
2542 
updateNotificationAnimationStates()2543     private void updateNotificationAnimationStates() {
2544         boolean running = mAnimationsEnabled || mPulsing;
2545         int childCount = getChildCount();
2546         for (int i = 0; i < childCount; i++) {
2547             View child = getChildAt(i);
2548             running &= mIsExpanded || isPinnedHeadsUp(child);
2549             updateAnimationState(running, child);
2550         }
2551     }
2552 
updateAnimationState(View child)2553     private void updateAnimationState(View child) {
2554         updateAnimationState((mAnimationsEnabled || mPulsing)
2555                 && (mIsExpanded || isPinnedHeadsUp(child)), child);
2556     }
2557 
2558 
updateAnimationState(boolean running, View child)2559     private void updateAnimationState(boolean running, View child) {
2560         if (child instanceof ExpandableNotificationRow) {
2561             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2562             row.setIconAnimationRunning(running);
2563         }
2564     }
2565 
isAddOrRemoveAnimationPending()2566     public boolean isAddOrRemoveAnimationPending() {
2567         return mNeedsAnimation
2568                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2569     }
2570     /**
2571      * Generate an animation for an added child view.
2572      *
2573      * @param child The view to be added.
2574      * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
2575      */
generateAddAnimation(View child, boolean fromMoreCard)2576     public void generateAddAnimation(View child, boolean fromMoreCard) {
2577         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
2578             // Generate Animations
2579             mChildrenToAddAnimated.add(child);
2580             if (fromMoreCard) {
2581                 mFromMoreCardAdditions.add(child);
2582             }
2583             mNeedsAnimation = true;
2584         }
2585         if (isHeadsUp(child) && !mChangePositionInProgress) {
2586             mAddedHeadsUpChildren.add(child);
2587             mChildrenToAddAnimated.remove(child);
2588         }
2589     }
2590 
2591     /**
2592      * Change the position of child to a new location
2593      *
2594      * @param child the view to change the position for
2595      * @param newIndex the new index
2596      */
changeViewPosition(View child, int newIndex)2597     public void changeViewPosition(View child, int newIndex) {
2598         int currentIndex = indexOfChild(child);
2599         if (child != null && child.getParent() == this && currentIndex != newIndex) {
2600             mChangePositionInProgress = true;
2601             ((ExpandableView)child).setChangingPosition(true);
2602             removeView(child);
2603             addView(child, newIndex);
2604             ((ExpandableView)child).setChangingPosition(false);
2605             mChangePositionInProgress = false;
2606             if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
2607                 mChildrenChangingPositions.add(child);
2608                 mNeedsAnimation = true;
2609             }
2610         }
2611     }
2612 
startAnimationToState()2613     private void startAnimationToState() {
2614         if (mNeedsAnimation) {
2615             generateChildHierarchyEvents();
2616             mNeedsAnimation = false;
2617         }
2618         if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
2619             setAnimationRunning(true);
2620             mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2621                     mGoToFullShadeDelay);
2622             mAnimationEvents.clear();
2623             updateBackground();
2624             updateViewShadows();
2625         } else {
2626             applyCurrentState();
2627         }
2628         mGoToFullShadeDelay = 0;
2629     }
2630 
generateChildHierarchyEvents()2631     private void generateChildHierarchyEvents() {
2632         generateHeadsUpAnimationEvents();
2633         generateChildRemovalEvents();
2634         generateChildAdditionEvents();
2635         generatePositionChangeEvents();
2636         generateSnapBackEvents();
2637         generateDragEvents();
2638         generateTopPaddingEvent();
2639         generateActivateEvent();
2640         generateDimmedEvent();
2641         generateHideSensitiveEvent();
2642         generateDarkEvent();
2643         generateGoToFullShadeEvent();
2644         generateViewResizeEvent();
2645         generateGroupExpansionEvent();
2646         generateAnimateEverythingEvent();
2647         mNeedsAnimation = false;
2648     }
2649 
generateHeadsUpAnimationEvents()2650     private void generateHeadsUpAnimationEvents() {
2651         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2652             ExpandableNotificationRow row = eventPair.first;
2653             boolean isHeadsUp = eventPair.second;
2654             int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2655             boolean onBottom = false;
2656             boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
2657             if (!mIsExpanded && !isHeadsUp) {
2658                 type = row.wasJustClicked()
2659                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2660                         : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
2661                 if (row.isChildInGroup()) {
2662                     // We can otherwise get stuck in there if it was just isolated
2663                     row.setHeadsupDisappearRunning(false);
2664                 }
2665             } else {
2666                 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
2667                 if (viewState == null) {
2668                     // A view state was never generated for this view, so we don't need to animate
2669                     // this. This may happen with notification children.
2670                     continue;
2671                 }
2672                 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2673                     if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2674                         // Our custom add animation
2675                         type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2676                     } else {
2677                         // Normal add animation
2678                         type = AnimationEvent.ANIMATION_TYPE_ADD;
2679                     }
2680                     onBottom = !pinnedAndClosed;
2681                 }
2682             }
2683             AnimationEvent event = new AnimationEvent(row, type);
2684             event.headsUpFromBottom = onBottom;
2685             mAnimationEvents.add(event);
2686         }
2687         mHeadsUpChangeAnimations.clear();
2688         mAddedHeadsUpChildren.clear();
2689     }
2690 
shouldHunAppearFromBottom(StackViewState viewState)2691     private boolean shouldHunAppearFromBottom(StackViewState viewState) {
2692         if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2693             return false;
2694         }
2695         return true;
2696     }
2697 
generateGroupExpansionEvent()2698     private void generateGroupExpansionEvent() {
2699         // Generate a group expansion/collapsing event if there is such a group at all
2700         if (mExpandedGroupView != null) {
2701             mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2702                     AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2703             mExpandedGroupView = null;
2704         }
2705     }
2706 
generateViewResizeEvent()2707     private void generateViewResizeEvent() {
2708         if (mNeedViewResizeAnimation) {
2709             mAnimationEvents.add(
2710                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2711         }
2712         mNeedViewResizeAnimation = false;
2713     }
2714 
generateSnapBackEvents()2715     private void generateSnapBackEvents() {
2716         for (View child : mSnappedBackChildren) {
2717             mAnimationEvents.add(new AnimationEvent(child,
2718                     AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2719         }
2720         mSnappedBackChildren.clear();
2721     }
2722 
generateDragEvents()2723     private void generateDragEvents() {
2724         for (View child : mDragAnimPendingChildren) {
2725             mAnimationEvents.add(new AnimationEvent(child,
2726                     AnimationEvent.ANIMATION_TYPE_START_DRAG));
2727         }
2728         mDragAnimPendingChildren.clear();
2729     }
2730 
generateChildRemovalEvents()2731     private void generateChildRemovalEvents() {
2732         for (View child : mChildrenToRemoveAnimated) {
2733             boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2734             int animationType = childWasSwipedOut
2735                     ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2736                     : AnimationEvent.ANIMATION_TYPE_REMOVE;
2737             AnimationEvent event = new AnimationEvent(child, animationType);
2738 
2739             // we need to know the view after this one
2740             event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2741             mAnimationEvents.add(event);
2742             mSwipedOutViews.remove(child);
2743         }
2744         mChildrenToRemoveAnimated.clear();
2745     }
2746 
generatePositionChangeEvents()2747     private void generatePositionChangeEvents() {
2748         for (View child : mChildrenChangingPositions) {
2749             mAnimationEvents.add(new AnimationEvent(child,
2750                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2751         }
2752         mChildrenChangingPositions.clear();
2753         if (mGenerateChildOrderChangedEvent) {
2754             mAnimationEvents.add(new AnimationEvent(null,
2755                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2756             mGenerateChildOrderChangedEvent = false;
2757         }
2758     }
2759 
generateChildAdditionEvents()2760     private void generateChildAdditionEvents() {
2761         for (View child : mChildrenToAddAnimated) {
2762             if (mFromMoreCardAdditions.contains(child)) {
2763                 mAnimationEvents.add(new AnimationEvent(child,
2764                         AnimationEvent.ANIMATION_TYPE_ADD,
2765                         StackStateAnimator.ANIMATION_DURATION_STANDARD));
2766             } else {
2767                 mAnimationEvents.add(new AnimationEvent(child,
2768                         AnimationEvent.ANIMATION_TYPE_ADD));
2769             }
2770         }
2771         mChildrenToAddAnimated.clear();
2772         mFromMoreCardAdditions.clear();
2773     }
2774 
generateTopPaddingEvent()2775     private void generateTopPaddingEvent() {
2776         if (mTopPaddingNeedsAnimation) {
2777             mAnimationEvents.add(
2778                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2779         }
2780         mTopPaddingNeedsAnimation = false;
2781     }
2782 
generateActivateEvent()2783     private void generateActivateEvent() {
2784         if (mActivateNeedsAnimation) {
2785             mAnimationEvents.add(
2786                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2787         }
2788         mActivateNeedsAnimation = false;
2789     }
2790 
generateAnimateEverythingEvent()2791     private void generateAnimateEverythingEvent() {
2792         if (mEverythingNeedsAnimation) {
2793             mAnimationEvents.add(
2794                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2795         }
2796         mEverythingNeedsAnimation = false;
2797     }
2798 
generateDimmedEvent()2799     private void generateDimmedEvent() {
2800         if (mDimmedNeedsAnimation) {
2801             mAnimationEvents.add(
2802                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2803         }
2804         mDimmedNeedsAnimation = false;
2805     }
2806 
generateHideSensitiveEvent()2807     private void generateHideSensitiveEvent() {
2808         if (mHideSensitiveNeedsAnimation) {
2809             mAnimationEvents.add(
2810                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2811         }
2812         mHideSensitiveNeedsAnimation = false;
2813     }
2814 
generateDarkEvent()2815     private void generateDarkEvent() {
2816         if (mDarkNeedsAnimation) {
2817             AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2818             ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2819             mAnimationEvents.add(ev);
2820             startBackgroundFadeIn();
2821         }
2822         mDarkNeedsAnimation = false;
2823     }
2824 
generateGoToFullShadeEvent()2825     private void generateGoToFullShadeEvent() {
2826         if (mGoToFullShadeNeedsAnimation) {
2827             mAnimationEvents.add(
2828                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2829         }
2830         mGoToFullShadeNeedsAnimation = false;
2831     }
2832 
onInterceptTouchEventScroll(MotionEvent ev)2833     private boolean onInterceptTouchEventScroll(MotionEvent ev) {
2834         if (!isScrollingEnabled()) {
2835             return false;
2836         }
2837         /*
2838          * This method JUST determines whether we want to intercept the motion.
2839          * If we return true, onMotionEvent will be called and we do the actual
2840          * scrolling there.
2841          */
2842 
2843         /*
2844         * Shortcut the most recurring case: the user is in the dragging
2845         * state and is moving their finger.  We want to intercept this
2846         * motion.
2847         */
2848         final int action = ev.getAction();
2849         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2850             return true;
2851         }
2852 
2853         switch (action & MotionEvent.ACTION_MASK) {
2854             case MotionEvent.ACTION_MOVE: {
2855                 /*
2856                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
2857                  * whether the user has moved far enough from the original down touch.
2858                  */
2859 
2860                 /*
2861                 * Locally do absolute value. mLastMotionY is set to the y value
2862                 * of the down event.
2863                 */
2864                 final int activePointerId = mActivePointerId;
2865                 if (activePointerId == INVALID_POINTER) {
2866                     // If we don't have a valid id, the touch down wasn't on content.
2867                     break;
2868                 }
2869 
2870                 final int pointerIndex = ev.findPointerIndex(activePointerId);
2871                 if (pointerIndex == -1) {
2872                     Log.e(TAG, "Invalid pointerId=" + activePointerId
2873                             + " in onInterceptTouchEvent");
2874                     break;
2875                 }
2876 
2877                 final int y = (int) ev.getY(pointerIndex);
2878                 final int x = (int) ev.getX(pointerIndex);
2879                 final int yDiff = Math.abs(y - mLastMotionY);
2880                 final int xDiff = Math.abs(x - mDownX);
2881                 if (yDiff > mTouchSlop && yDiff > xDiff) {
2882                     setIsBeingDragged(true);
2883                     mLastMotionY = y;
2884                     mDownX = x;
2885                     initVelocityTrackerIfNotExists();
2886                     mVelocityTracker.addMovement(ev);
2887                 }
2888                 break;
2889             }
2890 
2891             case MotionEvent.ACTION_DOWN: {
2892                 final int y = (int) ev.getY();
2893                 mScrolledToTopOnFirstDown = isScrolledToTop();
2894                 if (getChildAtPosition(ev.getX(), y) == null) {
2895                     setIsBeingDragged(false);
2896                     recycleVelocityTracker();
2897                     break;
2898                 }
2899 
2900                 /*
2901                  * Remember location of down touch.
2902                  * ACTION_DOWN always refers to pointer index 0.
2903                  */
2904                 mLastMotionY = y;
2905                 mDownX = (int) ev.getX();
2906                 mActivePointerId = ev.getPointerId(0);
2907 
2908                 initOrResetVelocityTracker();
2909                 mVelocityTracker.addMovement(ev);
2910                 /*
2911                 * If being flinged and user touches the screen, initiate drag;
2912                 * otherwise don't.  mScroller.isFinished should be false when
2913                 * being flinged.
2914                 */
2915                 boolean isBeingDragged = !mScroller.isFinished();
2916                 setIsBeingDragged(isBeingDragged);
2917                 break;
2918             }
2919 
2920             case MotionEvent.ACTION_CANCEL:
2921             case MotionEvent.ACTION_UP:
2922                 /* Release the drag */
2923                 setIsBeingDragged(false);
2924                 mActivePointerId = INVALID_POINTER;
2925                 recycleVelocityTracker();
2926                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2927                     postInvalidateOnAnimation();
2928                 }
2929                 break;
2930             case MotionEvent.ACTION_POINTER_UP:
2931                 onSecondaryPointerUp(ev);
2932                 break;
2933         }
2934 
2935         /*
2936         * The only time we want to intercept motion events is if we are in the
2937         * drag mode.
2938         */
2939         return mIsBeingDragged;
2940     }
2941 
2942     /**
2943      * @return Whether the specified motion event is actually happening over the content.
2944      */
isInContentBounds(MotionEvent event)2945     private boolean isInContentBounds(MotionEvent event) {
2946         return isInContentBounds(event.getY());
2947     }
2948 
2949     /**
2950      * @return Whether a y coordinate is inside the content.
2951      */
isInContentBounds(float y)2952     public boolean isInContentBounds(float y) {
2953         return y < getHeight() - getEmptyBottomMargin();
2954     }
2955 
setIsBeingDragged(boolean isDragged)2956     private void setIsBeingDragged(boolean isDragged) {
2957         mIsBeingDragged = isDragged;
2958         if (isDragged) {
2959             requestDisallowInterceptTouchEvent(true);
2960             removeLongPressCallback();
2961         }
2962     }
2963 
2964     @Override
onWindowFocusChanged(boolean hasWindowFocus)2965     public void onWindowFocusChanged(boolean hasWindowFocus) {
2966         super.onWindowFocusChanged(hasWindowFocus);
2967         if (!hasWindowFocus) {
2968             removeLongPressCallback();
2969         }
2970     }
2971 
2972     @Override
clearChildFocus(View child)2973     public void clearChildFocus(View child) {
2974         super.clearChildFocus(child);
2975         if (mForcedScroll == child) {
2976             mForcedScroll = null;
2977         }
2978     }
2979 
2980     @Override
requestDisallowLongPress()2981     public void requestDisallowLongPress() {
2982         removeLongPressCallback();
2983     }
2984 
2985     @Override
requestDisallowDismiss()2986     public void requestDisallowDismiss() {
2987         mDisallowDismissInThisMotion = true;
2988     }
2989 
removeLongPressCallback()2990     public void removeLongPressCallback() {
2991         mSwipeHelper.removeLongPressCallback();
2992     }
2993 
2994     @Override
isScrolledToTop()2995     public boolean isScrolledToTop() {
2996         return mOwnScrollY == 0;
2997     }
2998 
2999     @Override
isScrolledToBottom()3000     public boolean isScrolledToBottom() {
3001         return mOwnScrollY >= getScrollRange();
3002     }
3003 
3004     @Override
getHostView()3005     public View getHostView() {
3006         return this;
3007     }
3008 
getEmptyBottomMargin()3009     public int getEmptyBottomMargin() {
3010         int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize
3011                 - mBottomStackSlowDownHeight;
3012         return Math.max(emptyMargin, 0);
3013     }
3014 
getKeyguardBottomStackSize()3015     public float getKeyguardBottomStackSize() {
3016         return mBottomStackPeekSize + getResources().getDimensionPixelSize(
3017                 R.dimen.bottom_stack_slow_down_length);
3018     }
3019 
onExpansionStarted()3020     public void onExpansionStarted() {
3021         mIsExpansionChanging = true;
3022     }
3023 
onExpansionStopped()3024     public void onExpansionStopped() {
3025         mIsExpansionChanging = false;
3026         if (!mIsExpanded) {
3027             mOwnScrollY = 0;
3028             mPhoneStatusBar.resetUserExpandedStates();
3029 
3030             // lets make sure nothing is in the overlay / transient anymore
3031             clearTemporaryViews(this);
3032             for (int i = 0; i < getChildCount(); i++) {
3033                 ExpandableView child = (ExpandableView) getChildAt(i);
3034                 if (child instanceof ExpandableNotificationRow) {
3035                     ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3036                     clearTemporaryViews(row.getChildrenContainer());
3037                 }
3038             }
3039         }
3040     }
3041 
clearTemporaryViews(ViewGroup viewGroup)3042     private void clearTemporaryViews(ViewGroup viewGroup) {
3043         while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
3044             viewGroup.removeTransientView(viewGroup.getTransientView(0));
3045         }
3046         if (viewGroup != null) {
3047             viewGroup.getOverlay().clear();
3048         }
3049     }
3050 
onPanelTrackingStarted()3051     public void onPanelTrackingStarted() {
3052         mPanelTracking = true;
3053     }
onPanelTrackingStopped()3054     public void onPanelTrackingStopped() {
3055         mPanelTracking = false;
3056     }
3057 
resetScrollPosition()3058     public void resetScrollPosition() {
3059         mScroller.abortAnimation();
3060         mOwnScrollY = 0;
3061     }
3062 
setIsExpanded(boolean isExpanded)3063     private void setIsExpanded(boolean isExpanded) {
3064         boolean changed = isExpanded != mIsExpanded;
3065         mIsExpanded = isExpanded;
3066         mStackScrollAlgorithm.setIsExpanded(isExpanded);
3067         if (changed) {
3068             if (!mIsExpanded) {
3069                 mGroupManager.collapseAllGroups();
3070             }
3071             updateNotificationAnimationStates();
3072             updateChronometers();
3073         }
3074     }
3075 
updateChronometers()3076     private void updateChronometers() {
3077         int childCount = getChildCount();
3078         for (int i = 0; i < childCount; i++) {
3079             updateChronometerForChild(getChildAt(i));
3080         }
3081     }
3082 
updateChronometerForChild(View child)3083     private void updateChronometerForChild(View child) {
3084         if (child instanceof ExpandableNotificationRow) {
3085             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3086             row.setChronometerRunning(mIsExpanded);
3087         }
3088     }
3089 
3090     @Override
onHeightChanged(ExpandableView view, boolean needsAnimation)3091     public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
3092         updateContentHeight();
3093         updateScrollPositionOnExpandInBottom(view);
3094         clampScrollPosition();
3095         notifyHeightChangeListener(view);
3096         if (needsAnimation) {
3097             ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
3098                     ? (ExpandableNotificationRow) view
3099                     : null;
3100             requestAnimationOnViewResize(row);
3101         }
3102         requestChildrenUpdate();
3103     }
3104 
3105     @Override
onReset(ExpandableView view)3106     public void onReset(ExpandableView view) {
3107         if (mIsExpanded && mAnimationsEnabled) {
3108             mRequestViewResizeAnimationOnLayout = true;
3109         }
3110         updateAnimationState(view);
3111         updateChronometerForChild(view);
3112     }
3113 
updateScrollPositionOnExpandInBottom(ExpandableView view)3114     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
3115         if (view instanceof ExpandableNotificationRow) {
3116             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
3117             if (row.isUserLocked() && row != getFirstChildNotGone()) {
3118                 if (row.isSummaryWithChildren()) {
3119                     return;
3120                 }
3121                 // We are actually expanding this view
3122                 float endPosition = row.getTranslationY() + row.getActualHeight();
3123                 if (row.isChildInGroup()) {
3124                     endPosition += row.getNotificationParent().getTranslationY();
3125                 }
3126                 int stackEnd = getStackEndPosition();
3127                 if (endPosition > stackEnd) {
3128                     mOwnScrollY += endPosition - stackEnd;
3129                     mDisallowScrollingInThisMotion = true;
3130                 }
3131             }
3132         }
3133     }
3134 
getStackEndPosition()3135     private int getStackEndPosition() {
3136         return mMaxLayoutHeight - mBottomStackPeekSize - mBottomStackSlowDownHeight
3137                 + mPaddingBetweenElements + (int) mStackTranslation;
3138     }
3139 
setOnHeightChangedListener( ExpandableView.OnHeightChangedListener mOnHeightChangedListener)3140     public void setOnHeightChangedListener(
3141             ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
3142         this.mOnHeightChangedListener = mOnHeightChangedListener;
3143     }
3144 
setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener)3145     public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
3146         mOnEmptySpaceClickListener = listener;
3147     }
3148 
onChildAnimationFinished()3149     public void onChildAnimationFinished() {
3150         setAnimationRunning(false);
3151         requestChildrenUpdate();
3152         runAnimationFinishedRunnables();
3153         clearViewOverlays();
3154         clearHeadsUpDisappearRunning();
3155     }
3156 
clearHeadsUpDisappearRunning()3157     private void clearHeadsUpDisappearRunning() {
3158         for (int i = 0; i < getChildCount(); i++) {
3159             View view = getChildAt(i);
3160             if (view instanceof ExpandableNotificationRow) {
3161                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
3162                 row.setHeadsupDisappearRunning(false);
3163                 if (row.isSummaryWithChildren()) {
3164                     for (ExpandableNotificationRow child : row.getNotificationChildren()) {
3165                         child.setHeadsupDisappearRunning(false);
3166                     }
3167                 }
3168             }
3169         }
3170     }
3171 
clearViewOverlays()3172     private void clearViewOverlays() {
3173         for (View view : mClearOverlayViewsWhenFinished) {
3174             StackStateAnimator.removeFromOverlay(view);
3175         }
3176     }
3177 
runAnimationFinishedRunnables()3178     private void runAnimationFinishedRunnables() {
3179         for (Runnable runnable : mAnimationFinishedRunnables) {
3180             runnable.run();
3181         }
3182         mAnimationFinishedRunnables.clear();
3183     }
3184 
3185     /**
3186      * See {@link AmbientState#setDimmed}.
3187      */
setDimmed(boolean dimmed, boolean animate)3188     public void setDimmed(boolean dimmed, boolean animate) {
3189         mAmbientState.setDimmed(dimmed);
3190         if (animate && mAnimationsEnabled) {
3191             mDimmedNeedsAnimation = true;
3192             mNeedsAnimation =  true;
3193             animateDimmed(dimmed);
3194         } else {
3195             setDimAmount(dimmed ? 1.0f : 0.0f);
3196         }
3197         requestChildrenUpdate();
3198     }
3199 
setDimAmount(float dimAmount)3200     private void setDimAmount(float dimAmount) {
3201         mDimAmount = dimAmount;
3202         updateBackgroundDimming();
3203     }
3204 
animateDimmed(boolean dimmed)3205     private void animateDimmed(boolean dimmed) {
3206         if (mDimAnimator != null) {
3207             mDimAnimator.cancel();
3208         }
3209         float target = dimmed ? 1.0f : 0.0f;
3210         if (target == mDimAmount) {
3211             return;
3212         }
3213         mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
3214         mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
3215         mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
3216         mDimAnimator.addListener(mDimEndListener);
3217         mDimAnimator.addUpdateListener(mDimUpdateListener);
3218         mDimAnimator.start();
3219     }
3220 
setHideSensitive(boolean hideSensitive, boolean animate)3221     public void setHideSensitive(boolean hideSensitive, boolean animate) {
3222         if (hideSensitive != mAmbientState.isHideSensitive()) {
3223             int childCount = getChildCount();
3224             for (int i = 0; i < childCount; i++) {
3225                 ExpandableView v = (ExpandableView) getChildAt(i);
3226                 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
3227             }
3228             mAmbientState.setHideSensitive(hideSensitive);
3229             if (animate && mAnimationsEnabled) {
3230                 mHideSensitiveNeedsAnimation = true;
3231                 mNeedsAnimation =  true;
3232             }
3233             requestChildrenUpdate();
3234         }
3235     }
3236 
3237     /**
3238      * See {@link AmbientState#setActivatedChild}.
3239      */
setActivatedChild(ActivatableNotificationView activatedChild)3240     public void setActivatedChild(ActivatableNotificationView activatedChild) {
3241         mAmbientState.setActivatedChild(activatedChild);
3242         if (mAnimationsEnabled) {
3243             mActivateNeedsAnimation = true;
3244             mNeedsAnimation =  true;
3245         }
3246         requestChildrenUpdate();
3247     }
3248 
getActivatedChild()3249     public ActivatableNotificationView getActivatedChild() {
3250         return mAmbientState.getActivatedChild();
3251     }
3252 
applyCurrentState()3253     private void applyCurrentState() {
3254         mCurrentStackScrollState.apply();
3255         if (mListener != null) {
3256             mListener.onChildLocationsChanged(this);
3257         }
3258         runAnimationFinishedRunnables();
3259         setAnimationRunning(false);
3260         updateBackground();
3261         updateViewShadows();
3262     }
3263 
updateViewShadows()3264     private void updateViewShadows() {
3265         // we need to work around an issue where the shadow would not cast between siblings when
3266         // their z difference is between 0 and 0.1
3267 
3268         // Lefts first sort by Z difference
3269         for (int i = 0; i < getChildCount(); i++) {
3270             ExpandableView child = (ExpandableView) getChildAt(i);
3271             if (child.getVisibility() != GONE) {
3272                 mTmpSortedChildren.add(child);
3273             }
3274         }
3275         Collections.sort(mTmpSortedChildren, mViewPositionComparator);
3276 
3277         // Now lets update the shadow for the views
3278         ExpandableView previous = null;
3279         for (int i = 0; i < mTmpSortedChildren.size(); i++) {
3280             ExpandableView expandableView = mTmpSortedChildren.get(i);
3281             float translationZ = expandableView.getTranslationZ();
3282             float otherZ = previous == null ? translationZ : previous.getTranslationZ();
3283             float diff = otherZ - translationZ;
3284             if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
3285                 // There is no fake shadow to be drawn
3286                 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
3287             } else {
3288                 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
3289                         expandableView.getTranslationY() - previous.getExtraBottomPadding();
3290                 expandableView.setFakeShadowIntensity(
3291                         diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
3292                         previous.getOutlineAlpha(), (int) yLocation,
3293                         previous.getOutlineTranslation());
3294             }
3295             previous = expandableView;
3296         }
3297 
3298         mTmpSortedChildren.clear();
3299     }
3300 
goToFullShade(long delay)3301     public void goToFullShade(long delay) {
3302         mDismissView.setInvisible();
3303         mEmptyShadeView.setInvisible();
3304         mGoToFullShadeNeedsAnimation = true;
3305         mGoToFullShadeDelay = delay;
3306         mNeedsAnimation = true;
3307         requestChildrenUpdate();
3308     }
3309 
cancelExpandHelper()3310     public void cancelExpandHelper() {
3311         mExpandHelper.cancel();
3312     }
3313 
setIntrinsicPadding(int intrinsicPadding)3314     public void setIntrinsicPadding(int intrinsicPadding) {
3315         mIntrinsicPadding = intrinsicPadding;
3316     }
3317 
getIntrinsicPadding()3318     public int getIntrinsicPadding() {
3319         return mIntrinsicPadding;
3320     }
3321 
3322     /**
3323      * @return the y position of the first notification
3324      */
getNotificationsTopY()3325     public float getNotificationsTopY() {
3326         return mTopPadding + getStackTranslation();
3327     }
3328 
3329     @Override
shouldDelayChildPressedState()3330     public boolean shouldDelayChildPressedState() {
3331         return true;
3332     }
3333 
3334     /**
3335      * See {@link AmbientState#setDark}.
3336      */
setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation)3337     public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
3338         mAmbientState.setDark(dark);
3339         if (animate && mAnimationsEnabled) {
3340             mDarkNeedsAnimation = true;
3341             mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
3342             mNeedsAnimation =  true;
3343             setBackgroundFadeAmount(0.0f);
3344         } else if (!dark) {
3345             setBackgroundFadeAmount(1.0f);
3346         }
3347         requestChildrenUpdate();
3348         if (dark) {
3349             setWillNotDraw(!DEBUG);
3350             mScrimController.setExcludedBackgroundArea(null);
3351         } else {
3352             updateBackground();
3353             setWillNotDraw(false);
3354         }
3355     }
3356 
setBackgroundFadeAmount(float fadeAmount)3357     private void setBackgroundFadeAmount(float fadeAmount) {
3358         mBackgroundFadeAmount = fadeAmount;
3359         updateBackgroundDimming();
3360     }
3361 
getBackgroundFadeAmount()3362     public float getBackgroundFadeAmount() {
3363         return mBackgroundFadeAmount;
3364     }
3365 
startBackgroundFadeIn()3366     private void startBackgroundFadeIn() {
3367         ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
3368         int maxLength;
3369         if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE
3370                 || mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
3371             maxLength = getNotGoneChildCount() - 1;
3372         } else {
3373             maxLength = Math.max(mDarkAnimationOriginIndex,
3374                     getNotGoneChildCount() - mDarkAnimationOriginIndex - 1);
3375         }
3376         maxLength = Math.max(0, maxLength);
3377         long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK;
3378         fadeAnimator.setStartDelay(delay);
3379         fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
3380         fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
3381         fadeAnimator.start();
3382     }
3383 
findDarkAnimationOriginIndex(@ullable PointF screenLocation)3384     private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
3385         if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
3386             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3387         }
3388         if (screenLocation.y > getBottomMostNotificationBottom()) {
3389             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
3390         }
3391         View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
3392         if (child != null) {
3393             return getNotGoneIndex(child);
3394         } else {
3395             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3396         }
3397     }
3398 
getNotGoneIndex(View child)3399     private int getNotGoneIndex(View child) {
3400         int count = getChildCount();
3401         int notGoneIndex = 0;
3402         for (int i = 0; i < count; i++) {
3403             View v = getChildAt(i);
3404             if (child == v) {
3405                 return notGoneIndex;
3406             }
3407             if (v.getVisibility() != View.GONE) {
3408                 notGoneIndex++;
3409             }
3410         }
3411         return -1;
3412     }
3413 
setDismissView(DismissView dismissView)3414     public void setDismissView(DismissView dismissView) {
3415         int index = -1;
3416         if (mDismissView != null) {
3417             index = indexOfChild(mDismissView);
3418             removeView(mDismissView);
3419         }
3420         mDismissView = dismissView;
3421         addView(mDismissView, index);
3422     }
3423 
setEmptyShadeView(EmptyShadeView emptyShadeView)3424     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
3425         int index = -1;
3426         if (mEmptyShadeView != null) {
3427             index = indexOfChild(mEmptyShadeView);
3428             removeView(mEmptyShadeView);
3429         }
3430         mEmptyShadeView = emptyShadeView;
3431         addView(mEmptyShadeView, index);
3432     }
3433 
updateEmptyShadeView(boolean visible)3434     public void updateEmptyShadeView(boolean visible) {
3435         int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
3436         int newVisibility = visible ? VISIBLE : GONE;
3437         if (oldVisibility != newVisibility) {
3438             if (newVisibility != GONE) {
3439                 if (mEmptyShadeView.willBeGone()) {
3440                     mEmptyShadeView.cancelAnimation();
3441                 } else {
3442                     mEmptyShadeView.setInvisible();
3443                 }
3444                 mEmptyShadeView.setVisibility(newVisibility);
3445                 mEmptyShadeView.setWillBeGone(false);
3446                 updateContentHeight();
3447                 notifyHeightChangeListener(mEmptyShadeView);
3448             } else {
3449                 Runnable onFinishedRunnable = new Runnable() {
3450                     @Override
3451                     public void run() {
3452                         mEmptyShadeView.setVisibility(GONE);
3453                         mEmptyShadeView.setWillBeGone(false);
3454                         updateContentHeight();
3455                         notifyHeightChangeListener(mEmptyShadeView);
3456                     }
3457                 };
3458                 if (mAnimationsEnabled && mIsExpanded) {
3459                     mEmptyShadeView.setWillBeGone(true);
3460                     mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
3461                 } else {
3462                     mEmptyShadeView.setInvisible();
3463                     onFinishedRunnable.run();
3464                 }
3465             }
3466         }
3467     }
3468 
setOverflowContainer(NotificationOverflowContainer overFlowContainer)3469     public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
3470         int index = -1;
3471         if (mOverflowContainer != null) {
3472             index = indexOfChild(mOverflowContainer);
3473             removeView(mOverflowContainer);
3474         }
3475         mOverflowContainer = overFlowContainer;
3476         addView(mOverflowContainer, index);
3477     }
3478 
updateOverflowContainerVisibility(boolean visible)3479     public void updateOverflowContainerVisibility(boolean visible) {
3480         int oldVisibility = mOverflowContainer.willBeGone() ? GONE
3481                 : mOverflowContainer.getVisibility();
3482         final int newVisibility = visible ? VISIBLE : GONE;
3483         if (oldVisibility != newVisibility) {
3484             Runnable onFinishedRunnable = new Runnable() {
3485                 @Override
3486                 public void run() {
3487                     mOverflowContainer.setVisibility(newVisibility);
3488                     mOverflowContainer.setWillBeGone(false);
3489                     updateContentHeight();
3490                     notifyHeightChangeListener(mOverflowContainer);
3491                 }
3492             };
3493             if (!mAnimationsEnabled || !mIsExpanded) {
3494                 mOverflowContainer.cancelAppearDrawing();
3495                 onFinishedRunnable.run();
3496             } else if (newVisibility != GONE) {
3497                 mOverflowContainer.performAddAnimation(0,
3498                         StackStateAnimator.ANIMATION_DURATION_STANDARD);
3499                 mOverflowContainer.setVisibility(newVisibility);
3500                 mOverflowContainer.setWillBeGone(false);
3501                 updateContentHeight();
3502                 notifyHeightChangeListener(mOverflowContainer);
3503             } else {
3504                 mOverflowContainer.performRemoveAnimation(
3505                         StackStateAnimator.ANIMATION_DURATION_STANDARD,
3506                         0.0f,
3507                         onFinishedRunnable);
3508                 mOverflowContainer.setWillBeGone(true);
3509             }
3510         }
3511     }
3512 
updateDismissView(boolean visible)3513     public void updateDismissView(boolean visible) {
3514         int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
3515         int newVisibility = visible ? VISIBLE : GONE;
3516         if (oldVisibility != newVisibility) {
3517             if (newVisibility != GONE) {
3518                 if (mDismissView.willBeGone()) {
3519                     mDismissView.cancelAnimation();
3520                 } else {
3521                     mDismissView.setInvisible();
3522                 }
3523                 mDismissView.setVisibility(newVisibility);
3524                 mDismissView.setWillBeGone(false);
3525                 updateContentHeight();
3526                 notifyHeightChangeListener(mDismissView);
3527             } else {
3528                 Runnable dimissHideFinishRunnable = new Runnable() {
3529                     @Override
3530                     public void run() {
3531                         mDismissView.setVisibility(GONE);
3532                         mDismissView.setWillBeGone(false);
3533                         updateContentHeight();
3534                         notifyHeightChangeListener(mDismissView);
3535                     }
3536                 };
3537                 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
3538                     mDismissView.setWillBeGone(true);
3539                     mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3540                 } else {
3541                     dimissHideFinishRunnable.run();
3542                 }
3543             }
3544         }
3545     }
3546 
setDismissAllInProgress(boolean dismissAllInProgress)3547     public void setDismissAllInProgress(boolean dismissAllInProgress) {
3548         mDismissAllInProgress = dismissAllInProgress;
3549         mAmbientState.setDismissAllInProgress(dismissAllInProgress);
3550         handleDismissAllClipping();
3551     }
3552 
handleDismissAllClipping()3553     private void handleDismissAllClipping() {
3554         final int count = getChildCount();
3555         boolean previousChildWillBeDismissed = false;
3556         for (int i = 0; i < count; i++) {
3557             ExpandableView child = (ExpandableView) getChildAt(i);
3558             if (child.getVisibility() == GONE) {
3559                 continue;
3560             }
3561             if (mDismissAllInProgress && previousChildWillBeDismissed) {
3562                 child.setMinClipTopAmount(child.getClipTopAmount());
3563             } else {
3564                 child.setMinClipTopAmount(0);
3565             }
3566             previousChildWillBeDismissed = canChildBeDismissed(child);
3567         }
3568     }
3569 
isDismissViewNotGone()3570     public boolean isDismissViewNotGone() {
3571         return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3572     }
3573 
isDismissViewVisible()3574     public boolean isDismissViewVisible() {
3575         return mDismissView.isVisible();
3576     }
3577 
getDismissViewHeight()3578     public int getDismissViewHeight() {
3579         return mDismissView.getHeight() + mPaddingBetweenElements;
3580     }
3581 
getEmptyShadeViewHeight()3582     public int getEmptyShadeViewHeight() {
3583         return mEmptyShadeView.getHeight();
3584     }
3585 
getBottomMostNotificationBottom()3586     public float getBottomMostNotificationBottom() {
3587         final int count = getChildCount();
3588         float max = 0;
3589         for (int childIdx = 0; childIdx < count; childIdx++) {
3590             ExpandableView child = (ExpandableView) getChildAt(childIdx);
3591             if (child.getVisibility() == GONE) {
3592                 continue;
3593             }
3594             float bottom = child.getTranslationY() + child.getActualHeight();
3595             if (bottom > max) {
3596                 max = bottom;
3597             }
3598         }
3599         return max + getStackTranslation();
3600     }
3601 
setPhoneStatusBar(PhoneStatusBar phoneStatusBar)3602     public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3603         this.mPhoneStatusBar = phoneStatusBar;
3604     }
3605 
setGroupManager(NotificationGroupManager groupManager)3606     public void setGroupManager(NotificationGroupManager groupManager) {
3607         this.mGroupManager = groupManager;
3608     }
3609 
onGoToKeyguard()3610     public void onGoToKeyguard() {
3611         requestAnimateEverything();
3612     }
3613 
requestAnimateEverything()3614     private void requestAnimateEverything() {
3615         if (mIsExpanded && mAnimationsEnabled) {
3616             mEverythingNeedsAnimation = true;
3617             mNeedsAnimation = true;
3618             requestChildrenUpdate();
3619         }
3620     }
3621 
isBelowLastNotification(float touchX, float touchY)3622     public boolean isBelowLastNotification(float touchX, float touchY) {
3623         int childCount = getChildCount();
3624         for (int i = childCount - 1; i >= 0; i--) {
3625             ExpandableView child = (ExpandableView) getChildAt(i);
3626             if (child.getVisibility() != View.GONE) {
3627                 float childTop = child.getY();
3628                 if (childTop > touchY) {
3629                     // we are above a notification entirely let's abort
3630                     return false;
3631                 }
3632                 boolean belowChild = touchY > childTop + child.getActualHeight();
3633                 if (child == mDismissView) {
3634                     if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3635                                     touchY - childTop)) {
3636                         // We clicked on the dismiss button
3637                         return false;
3638                     }
3639                 } else if (child == mEmptyShadeView) {
3640                     // We arrived at the empty shade view, for which we accept all clicks
3641                     return true;
3642                 } else if (!belowChild){
3643                     // We are on a child
3644                     return false;
3645                 }
3646             }
3647         }
3648         return touchY > mTopPadding + mStackTranslation;
3649     }
3650 
3651     @Override
onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded)3652     public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
3653         boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
3654                 && (mIsExpanded || changedRow.isPinned());
3655         if (animated) {
3656             mExpandedGroupView = changedRow;
3657             mNeedsAnimation = true;
3658         }
3659         changedRow.setChildrenExpanded(expanded, animated);
3660         if (!mGroupExpandedForMeasure) {
3661             onHeightChanged(changedRow, false /* needsAnimation */);
3662         }
3663         runAfterAnimationFinished(new Runnable() {
3664             @Override
3665             public void run() {
3666                 changedRow.onFinishedExpansionChange();
3667             }
3668         });
3669     }
3670 
3671     @Override
onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group)3672     public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
3673         mPhoneStatusBar.requestNotificationUpdate();
3674     }
3675 
3676     /** @hide */
3677     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)3678     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
3679         super.onInitializeAccessibilityEventInternal(event);
3680         event.setScrollable(mScrollable);
3681         event.setScrollX(mScrollX);
3682         event.setScrollY(mOwnScrollY);
3683         event.setMaxScrollX(mScrollX);
3684         event.setMaxScrollY(getScrollRange());
3685     }
3686 
3687     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)3688     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3689         super.onInitializeAccessibilityNodeInfoInternal(info);
3690         final int scrollRange = getScrollRange();
3691         if (scrollRange > 0) {
3692             info.setScrollable(true);
3693             if (mScrollY > 0) {
3694                 info.addAction(
3695                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
3696                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
3697             }
3698             if (mScrollY < scrollRange) {
3699                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
3700                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
3701             }
3702         }
3703         // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
3704         info.setClassName(ScrollView.class.getName());
3705     }
3706 
3707     /** @hide */
3708     @Override
performAccessibilityActionInternal(int action, Bundle arguments)3709     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
3710         if (super.performAccessibilityActionInternal(action, arguments)) {
3711             return true;
3712         }
3713         if (!isEnabled()) {
3714             return false;
3715         }
3716         int direction = -1;
3717         switch (action) {
3718             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
3719                 // fall through
3720             case android.R.id.accessibilityActionScrollDown:
3721                 direction = 1;
3722                 // fall through
3723             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
3724                 // fall through
3725             case android.R.id.accessibilityActionScrollUp:
3726                 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
3727                         - mBottomStackPeekSize - mBottomStackSlowDownHeight;
3728                 final int targetScrollY = Math.max(0,
3729                         Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
3730                 if (targetScrollY != mOwnScrollY) {
3731                     mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
3732                     postInvalidateOnAnimation();
3733                     return true;
3734                 }
3735                 break;
3736         }
3737         return false;
3738     }
3739 
3740     @Override
onGroupsChanged()3741     public void onGroupsChanged() {
3742         mPhoneStatusBar.requestNotificationUpdate();
3743     }
3744 
generateChildOrderChangedEvent()3745     public void generateChildOrderChangedEvent() {
3746         if (mIsExpanded && mAnimationsEnabled) {
3747             mGenerateChildOrderChangedEvent = true;
3748             mNeedsAnimation = true;
3749             requestChildrenUpdate();
3750         }
3751     }
3752 
runAfterAnimationFinished(Runnable runnable)3753     public void runAfterAnimationFinished(Runnable runnable) {
3754         mAnimationFinishedRunnables.add(runnable);
3755     }
3756 
setHeadsUpManager(HeadsUpManager headsUpManager)3757     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3758         mHeadsUpManager = headsUpManager;
3759         mAmbientState.setHeadsUpManager(headsUpManager);
3760     }
3761 
generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp)3762     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3763         if (mAnimationsEnabled) {
3764             mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3765             mNeedsAnimation = true;
3766             if (!mIsExpanded && !isHeadsUp) {
3767                 row.setHeadsupDisappearRunning(true);
3768             }
3769             requestChildrenUpdate();
3770         }
3771     }
3772 
setShadeExpanded(boolean shadeExpanded)3773     public void setShadeExpanded(boolean shadeExpanded) {
3774         mAmbientState.setShadeExpanded(shadeExpanded);
3775         mStateAnimator.setShadeExpanded(shadeExpanded);
3776     }
3777 
3778     /**
3779      * Set the boundary for the bottom heads up position. The heads up will always be above this
3780      * position.
3781      *
3782      * @param height the height of the screen
3783      * @param bottomBarHeight the height of the bar on the bottom
3784      */
setHeadsUpBoundaries(int height, int bottomBarHeight)3785     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3786         mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3787         mStateAnimator.setHeadsUpAppearHeightBottom(height);
3788         requestChildrenUpdate();
3789     }
3790 
setTrackingHeadsUp(boolean trackingHeadsUp)3791     public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3792         mTrackingHeadsUp = trackingHeadsUp;
3793     }
3794 
setScrimController(ScrimController scrimController)3795     public void setScrimController(ScrimController scrimController) {
3796         mScrimController = scrimController;
3797         mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3798             @Override
3799             public void run() {
3800                 updateBackgroundDimming();
3801             }
3802         });
3803     }
3804 
forceNoOverlappingRendering(boolean force)3805     public void forceNoOverlappingRendering(boolean force) {
3806         mForceNoOverlappingRendering = force;
3807     }
3808 
3809     @Override
hasOverlappingRendering()3810     public boolean hasOverlappingRendering() {
3811         return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3812     }
3813 
setAnimationRunning(boolean animationRunning)3814     public void setAnimationRunning(boolean animationRunning) {
3815         if (animationRunning != mAnimationRunning) {
3816             if (animationRunning) {
3817                 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
3818             } else {
3819                 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
3820             }
3821             mAnimationRunning = animationRunning;
3822             updateContinuousShadowDrawing();
3823         }
3824     }
3825 
isExpanded()3826     public boolean isExpanded() {
3827         return mIsExpanded;
3828     }
3829 
setPulsing(boolean pulsing)3830     public void setPulsing(boolean pulsing) {
3831         mPulsing = pulsing;
3832         updateNotificationAnimationStates();
3833     }
3834 
setFadingOut(boolean fadingOut)3835     public void setFadingOut(boolean fadingOut) {
3836         if (fadingOut != mFadingOut) {
3837             mFadingOut = fadingOut;
3838             updateFadingState();
3839         }
3840     }
3841 
setParentFadingOut(boolean fadingOut)3842     public void setParentFadingOut(boolean fadingOut) {
3843         if (fadingOut != mParentFadingOut) {
3844             mParentFadingOut = fadingOut;
3845             updateFadingState();
3846         }
3847     }
3848 
updateFadingState()3849     private void updateFadingState() {
3850         if (mFadingOut || mParentFadingOut || mAmbientState.isDark()) {
3851             mScrimController.setExcludedBackgroundArea(null);
3852         } else {
3853             applyCurrentBackgroundBounds();
3854         }
3855         updateSrcDrawing();
3856     }
3857 
3858     @Override
setAlpha(@loatRangefrom = 0.0, to = 1.0) float alpha)3859     public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) {
3860         super.setAlpha(alpha);
3861         setFadingOut(alpha != 1.0f);
3862     }
3863 
3864     /**
3865      * Remove the a given view from the viewstate. This is currently used when the children are
3866      * kept in the parent artificially to have a nicer animation.
3867      * @param view the view to remove
3868      */
removeViewStateForView(View view)3869     public void removeViewStateForView(View view) {
3870         mCurrentStackScrollState.removeViewStateForView(view);
3871     }
3872 
3873     /**
3874      * A listener that is notified when some child locations might have changed.
3875      */
3876     public interface OnChildLocationsChangedListener {
onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout)3877         public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
3878     }
3879 
3880     /**
3881      * A listener that is notified when the empty space below the notifications is clicked on
3882      */
3883     public interface OnEmptySpaceClickListener {
onEmptySpaceClicked(float x, float y)3884         public void onEmptySpaceClicked(float x, float y);
3885     }
3886 
3887     /**
3888      * A listener that gets notified when the overscroll at the top has changed.
3889      */
3890     public interface OnOverscrollTopChangedListener {
3891 
3892         /**
3893          * Notifies a listener that the overscroll has changed.
3894          *
3895          * @param amount the amount of overscroll, in pixels
3896          * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
3897          *                     unrubberbanded motion to directly expand overscroll view (e.g expand
3898          *                     QS)
3899          */
onOverscrollTopChanged(float amount, boolean isRubberbanded)3900         public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
3901 
3902         /**
3903          * Notify a listener that the scroller wants to escape from the scrolling motion and
3904          * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
3905          *
3906          * @param velocity The velocity that the Scroller had when over flinging
3907          * @param open Should the fling open or close the overscroll view.
3908          */
flingTopOverscroll(float velocity, boolean open)3909         public void flingTopOverscroll(float velocity, boolean open);
3910     }
3911 
3912     private class NotificationSwipeHelper extends SwipeHelper {
3913         private static final long SHOW_GEAR_DELAY = 60;
3914         private static final long COVER_GEAR_DELAY = 4000;
3915         private CheckForDrag mCheckForDrag;
3916         private Runnable mFalsingCheck;
3917         private Handler mHandler;
3918         private boolean mGearSnappedTo;
3919         private boolean mGearSnappedOnLeft;
3920 
NotificationSwipeHelper(int swipeDirection, Callback callback, Context context)3921         public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
3922             super(swipeDirection, callback, context);
3923             mHandler = new Handler();
3924             mFalsingCheck = new Runnable() {
3925                 @Override
3926                 public void run() {
3927                     resetExposedGearView(true /* animate */, true /* force */);
3928                 }
3929             };
3930         }
3931 
3932         @Override
onDownUpdate(View currView)3933         public void onDownUpdate(View currView) {
3934             // Set the active view
3935             mTranslatingParentView = currView;
3936 
3937             // Reset check for drag gesture
3938             cancelCheckForDrag();
3939             if (mCurrIconRow != null) {
3940                 mCurrIconRow.setSnapping(false);
3941             }
3942             mCheckForDrag = null;
3943             mCurrIconRow = null;
3944             mHandler.removeCallbacks(mFalsingCheck);
3945 
3946             // Slide back any notifications that might be showing a gear
3947             resetExposedGearView(true /* animate */, false /* force */);
3948 
3949             if (currView instanceof ExpandableNotificationRow) {
3950                 // Set the listener for the current row's gear
3951                 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
3952                 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
3953             }
3954         }
3955 
3956         @Override
onMoveUpdate(View view, float translation, float delta)3957         public void onMoveUpdate(View view, float translation, float delta) {
3958             mHandler.removeCallbacks(mFalsingCheck);
3959 
3960             if (mCurrIconRow != null) {
3961                 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
3962 
3963                 // If the gear is visible and the movement is towards it it's not a location change.
3964                 boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
3965                 boolean locationChange = isTowardsGear(translation, onLeft)
3966                         ? false : mCurrIconRow.isIconLocationChange(translation);
3967                 if (locationChange) {
3968                     // Don't consider it "snapped" if location has changed.
3969                     setSnappedToGear(false);
3970 
3971                     // Changed directions, make sure we check to fade in icon again.
3972                     if (!mHandler.hasCallbacks(mCheckForDrag)) {
3973                         // No check scheduled, set null to schedule a new one.
3974                         mCheckForDrag = null;
3975                     } else {
3976                         // Check scheduled, reset alpha and update location; check will fade it in
3977                         mCurrIconRow.setGearAlpha(0f);
3978                         mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
3979                     }
3980                 }
3981             }
3982 
3983             final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
3984                     && ((ExpandableNotificationRow) view).areGutsExposed();
3985 
3986             if (!isPinnedHeadsUp(view) && !gutsExposed) {
3987                 // Only show the gear if we're not a heads up view and guts aren't exposed.
3988                 checkForDrag();
3989             }
3990         }
3991 
3992         @Override
dismissChild(final View view, float velocity, boolean useAccelerateInterpolator)3993         public void dismissChild(final View view, float velocity,
3994                 boolean useAccelerateInterpolator) {
3995             super.dismissChild(view, velocity, useAccelerateInterpolator);
3996             if (mIsExpanded) {
3997                 // We don't want to quick-dismiss when it's a heads up as this might lead to closing
3998                 // of the panel early.
3999                 handleChildDismissed(view);
4000             }
4001             handleGearCoveredOrDismissed();
4002         }
4003 
4004         @Override
snapChild(final View animView, final float targetLeft, float velocity)4005         public void snapChild(final View animView, final float targetLeft, float velocity) {
4006             super.snapChild(animView, targetLeft, velocity);
4007             onDragCancelled(animView);
4008             if (targetLeft == 0) {
4009                 handleGearCoveredOrDismissed();
4010             }
4011         }
4012 
handleGearCoveredOrDismissed()4013         private void handleGearCoveredOrDismissed() {
4014             cancelCheckForDrag();
4015             setSnappedToGear(false);
4016             if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
4017                 mGearExposedView = null;
4018             }
4019         }
4020 
4021         @Override
handleUpEvent(MotionEvent ev, View animView, float velocity, float translation)4022         public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
4023                 float translation) {
4024             if (mCurrIconRow == null) {
4025                 cancelCheckForDrag();
4026                 return false; // Let SwipeHelper handle it.
4027             }
4028 
4029             boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
4030             boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
4031 
4032             if (mGearSnappedTo && mCurrIconRow.isVisible()) {
4033                 if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
4034                     boolean coveringGear =
4035                             Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
4036                     if (gestureTowardsGear || coveringGear) {
4037                         // Gesture is towards or covering the gear
4038                         snapChild(animView, 0 /* leftTarget */, velocity);
4039                     } else if (isDismissGesture(ev)) {
4040                         // Gesture is a dismiss that's not towards the gear
4041                         dismissChild(animView, velocity,
4042                                 !swipedFastEnough() /* useAccelerateInterpolator */);
4043                     } else {
4044                         // Didn't move enough to dismiss or cover, snap to the gear
4045                         snapToGear(animView, velocity);
4046                     }
4047                 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
4048                         || (gestureTowardsGear && !swipedFarEnough())) {
4049                     // The gear has been snapped to previously, however, the gear is now on the
4050                     // other side. If gesture is towards gear and not too far snap to the gear.
4051                     snapToGear(animView, velocity);
4052                 } else {
4053                     dismissOrSnapBack(animView, velocity, ev);
4054                 }
4055             } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
4056                     || gestureTowardsGear) {
4057                 // Gear has not been snapped to previously and this is gear revealing gesture
4058                 snapToGear(animView, velocity);
4059             } else {
4060                 dismissOrSnapBack(animView, velocity, ev);
4061             }
4062             return true;
4063         }
4064 
dismissOrSnapBack(View animView, float velocity, MotionEvent ev)4065         private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
4066             if (isDismissGesture(ev)) {
4067                 dismissChild(animView, velocity,
4068                         !swipedFastEnough() /* useAccelerateInterpolator */);
4069             } else {
4070                 snapChild(animView, 0 /* leftTarget */, velocity);
4071             }
4072         }
4073 
snapToGear(View animView, float velocity)4074         private void snapToGear(View animView, float velocity) {
4075             final float snapBackThreshold = getSpaceForGear(animView);
4076             final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
4077                     : -snapBackThreshold;
4078             mGearExposedView = mTranslatingParentView;
4079             if (animView instanceof ExpandableNotificationRow) {
4080                 MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
4081                         ((ExpandableNotificationRow) animView).getStatusBarNotification()
4082                                 .getPackageName());
4083             }
4084             if (mCurrIconRow != null) {
4085                 mCurrIconRow.setSnapping(true);
4086                 setSnappedToGear(true);
4087             }
4088             onDragCancelled(animView);
4089 
4090             // If we're on the lockscreen we want to false this.
4091             if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
4092                 mHandler.removeCallbacks(mFalsingCheck);
4093                 mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
4094             }
4095             super.snapChild(animView, target, velocity);
4096         }
4097 
swipedEnoughToShowGear(View animView)4098         private boolean swipedEnoughToShowGear(View animView) {
4099             if (mTranslatingParentView == null) {
4100                 return false;
4101             }
4102             // If the notification can't be dismissed then how far it can move is
4103             // restricted -- reduce the distance it needs to move in this case.
4104             final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
4105             final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
4106             final float translation = getTranslation(animView);
4107             final boolean fromLeft = translation > 0;
4108             final float absTrans = Math.abs(translation);
4109             final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
4110 
4111             return mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
4112                     ? (translation > snapBackThreshold && translation <= notiThreshold)
4113                     : (translation < -snapBackThreshold && translation >= -notiThreshold));
4114         }
4115 
4116         @Override
getViewTranslationAnimator(View v, float target, AnimatorUpdateListener listener)4117         public Animator getViewTranslationAnimator(View v, float target,
4118                 AnimatorUpdateListener listener) {
4119             if (v instanceof ExpandableNotificationRow) {
4120                 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
4121             } else {
4122                 return super.getViewTranslationAnimator(v, target, listener);
4123             }
4124         }
4125 
4126         @Override
setTranslation(View v, float translate)4127         public void setTranslation(View v, float translate) {
4128             ((ExpandableView) v).setTranslation(translate);
4129         }
4130 
4131         @Override
getTranslation(View v)4132         public float getTranslation(View v) {
4133             return ((ExpandableView) v).getTranslation();
4134         }
4135 
closeControlsIfOutsideTouch(MotionEvent ev)4136         public void closeControlsIfOutsideTouch(MotionEvent ev) {
4137             NotificationGuts guts = mPhoneStatusBar.getExposedGuts();
4138             View view = null;
4139             int height = 0;
4140             if (guts != null) {
4141                 // Checking guts
4142                 view = guts;
4143                 height = guts.getActualHeight();
4144             } else if (mCurrIconRow != null && mCurrIconRow.isVisible()
4145                     && mTranslatingParentView != null) {
4146                 // Checking gear
4147                 view = mTranslatingParentView;
4148                 height = ((ExpandableView) mTranslatingParentView).getActualHeight();
4149             }
4150             if (view != null) {
4151                 final int rx = (int) ev.getRawX();
4152                 final int ry = (int) ev.getRawY();
4153 
4154                 getLocationOnScreen(mTempInt2);
4155                 int[] location = new int[2];
4156                 view.getLocationOnScreen(location);
4157                 final int x = location[0] - mTempInt2[0];
4158                 final int y = location[1] - mTempInt2[1];
4159                 Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
4160                 if (!rect.contains((int) rx, (int) ry)) {
4161                     // Touch was outside visible guts / gear notification, close what's visible
4162                     mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
4163                 }
4164             }
4165         }
4166 
4167         /**
4168          * Returns whether the gesture is towards the gear location or not.
4169          */
isTowardsGear(float velocity, boolean onLeft)4170         private boolean isTowardsGear(float velocity, boolean onLeft) {
4171             if (mCurrIconRow == null) {
4172                 return false;
4173             }
4174             return mCurrIconRow.isVisible()
4175                     && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
4176         }
4177 
4178         /**
4179          * Indicates the the gear has been snapped to.
4180          */
setSnappedToGear(boolean snapped)4181         private void setSnappedToGear(boolean snapped) {
4182             mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
4183             mGearSnappedTo = snapped && mCurrIconRow != null;
4184         }
4185 
4186         /**
4187          * Returns the horizontal space in pixels required to display the gear behind a
4188          * notification.
4189          */
getSpaceForGear(View view)4190         private float getSpaceForGear(View view) {
4191             if (view instanceof ExpandableNotificationRow) {
4192                 return ((ExpandableNotificationRow) view).getSpaceForGear();
4193             }
4194             return 0;
4195         }
4196 
checkForDrag()4197         private void checkForDrag() {
4198             if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
4199                 mCheckForDrag = new CheckForDrag();
4200                 mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
4201             }
4202         }
4203 
cancelCheckForDrag()4204         private void cancelCheckForDrag() {
4205             if (mCurrIconRow != null) {
4206                 mCurrIconRow.cancelFadeAnimator();
4207             }
4208             mHandler.removeCallbacks(mCheckForDrag);
4209         }
4210 
4211         private final class CheckForDrag implements Runnable {
4212             @Override
run()4213             public void run() {
4214                 if (mTranslatingParentView == null) {
4215                     return;
4216                 }
4217                 final float translation = getTranslation(mTranslatingParentView);
4218                 final float absTransX = Math.abs(translation);
4219                 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
4220                 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
4221                 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
4222                         || mCurrIconRow.isIconLocationChange(translation)))
4223                         && absTransX >= bounceBackToGearWidth * 0.4
4224                         && absTransX < notiThreshold) {
4225                     // Fade in the gear
4226                     mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
4227                             notiThreshold);
4228                 }
4229             }
4230         }
4231 
resetExposedGearView(boolean animate, boolean force)4232         public void resetExposedGearView(boolean animate, boolean force) {
4233             if (mGearExposedView == null
4234                     || (!force && mGearExposedView == mTranslatingParentView)) {
4235                 // If no gear is showing or it's showing for this view we do nothing.
4236                 return;
4237             }
4238             final View prevGearExposedView = mGearExposedView;
4239             if (animate) {
4240                 Animator anim = getViewTranslationAnimator(prevGearExposedView,
4241                         0 /* leftTarget */, null /* updateListener */);
4242                 if (anim != null) {
4243                     anim.start();
4244                 }
4245             } else if (mGearExposedView instanceof ExpandableNotificationRow) {
4246                 ((ExpandableNotificationRow) mGearExposedView).resetTranslation();
4247             }
4248             mGearExposedView = null;
4249             mGearSnappedTo = false;
4250         }
4251     }
4252 
updateContinuousShadowDrawing()4253     private void updateContinuousShadowDrawing() {
4254         boolean continuousShadowUpdate = mAnimationRunning
4255                 || !mAmbientState.getDraggedViews().isEmpty();
4256         if (continuousShadowUpdate != mContinuousShadowUpdate) {
4257             if (continuousShadowUpdate) {
4258                 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
4259             } else {
4260                 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
4261             }
4262             mContinuousShadowUpdate = continuousShadowUpdate;
4263         }
4264     }
4265 
resetExposedGearView(boolean animate, boolean force)4266     public void resetExposedGearView(boolean animate, boolean force) {
4267         mSwipeHelper.resetExposedGearView(animate, force);
4268     }
4269 
closeControlsIfOutsideTouch(MotionEvent ev)4270     public void closeControlsIfOutsideTouch(MotionEvent ev) {
4271         mSwipeHelper.closeControlsIfOutsideTouch(ev);
4272     }
4273 
4274     static class AnimationEvent {
4275 
4276         static AnimationFilter[] FILTERS = new AnimationFilter[] {
4277 
4278                 // ANIMATION_TYPE_ADD
4279                 new AnimationFilter()
4280                         .animateShadowAlpha()
4281                         .animateHeight()
4282                         .animateTopInset()
4283                         .animateY()
4284                         .animateZ()
4285                         .hasDelays(),
4286 
4287                 // ANIMATION_TYPE_REMOVE
4288                 new AnimationFilter()
4289                         .animateShadowAlpha()
4290                         .animateHeight()
4291                         .animateTopInset()
4292                         .animateY()
4293                         .animateZ()
4294                         .hasDelays(),
4295 
4296                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4297                 new AnimationFilter()
4298                         .animateShadowAlpha()
4299                         .animateHeight()
4300                         .animateTopInset()
4301                         .animateY()
4302                         .animateZ()
4303                         .hasDelays(),
4304 
4305                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4306                 new AnimationFilter()
4307                         .animateShadowAlpha()
4308                         .animateHeight()
4309                         .animateTopInset()
4310                         .animateY()
4311                         .animateDimmed()
4312                         .animateZ(),
4313 
4314                 // ANIMATION_TYPE_START_DRAG
4315                 new AnimationFilter()
4316                         .animateShadowAlpha(),
4317 
4318                 // ANIMATION_TYPE_SNAP_BACK
4319                 new AnimationFilter()
4320                         .animateShadowAlpha()
4321                         .animateHeight(),
4322 
4323                 // ANIMATION_TYPE_ACTIVATED_CHILD
4324                 new AnimationFilter()
4325                         .animateZ(),
4326 
4327                 // ANIMATION_TYPE_DIMMED
4328                 new AnimationFilter()
4329                         .animateDimmed(),
4330 
4331                 // ANIMATION_TYPE_CHANGE_POSITION
4332                 new AnimationFilter()
4333                         .animateAlpha() // maybe the children change positions
4334                         .animateShadowAlpha()
4335                         .animateHeight()
4336                         .animateTopInset()
4337                         .animateY()
4338                         .animateZ(),
4339 
4340                 // ANIMATION_TYPE_DARK
4341                 new AnimationFilter()
4342                         .animateDark()
4343                         .hasDelays(),
4344 
4345                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4346                 new AnimationFilter()
4347                         .animateShadowAlpha()
4348                         .animateHeight()
4349                         .animateTopInset()
4350                         .animateY()
4351                         .animateDimmed()
4352                         .animateZ()
4353                         .hasDelays(),
4354 
4355                 // ANIMATION_TYPE_HIDE_SENSITIVE
4356                 new AnimationFilter()
4357                         .animateHideSensitive(),
4358 
4359                 // ANIMATION_TYPE_VIEW_RESIZE
4360                 new AnimationFilter()
4361                         .animateShadowAlpha()
4362                         .animateHeight()
4363                         .animateTopInset()
4364                         .animateY()
4365                         .animateZ(),
4366 
4367                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
4368                 new AnimationFilter()
4369                         .animateAlpha()
4370                         .animateShadowAlpha()
4371                         .animateHeight()
4372                         .animateTopInset()
4373                         .animateY()
4374                         .animateZ(),
4375 
4376                 // ANIMATION_TYPE_HEADS_UP_APPEAR
4377                 new AnimationFilter()
4378                         .animateShadowAlpha()
4379                         .animateHeight()
4380                         .animateTopInset()
4381                         .animateY()
4382                         .animateZ(),
4383 
4384                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4385                 new AnimationFilter()
4386                         .animateShadowAlpha()
4387                         .animateHeight()
4388                         .animateTopInset()
4389                         .animateY()
4390                         .animateZ(),
4391 
4392                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4393                 new AnimationFilter()
4394                         .animateShadowAlpha()
4395                         .animateHeight()
4396                         .animateTopInset()
4397                         .animateY()
4398                         .animateZ()
4399                         .hasDelays(),
4400 
4401                 // ANIMATION_TYPE_HEADS_UP_OTHER
4402                 new AnimationFilter()
4403                         .animateShadowAlpha()
4404                         .animateHeight()
4405                         .animateTopInset()
4406                         .animateY()
4407                         .animateZ(),
4408 
4409                 // ANIMATION_TYPE_EVERYTHING
4410                 new AnimationFilter()
4411                         .animateAlpha()
4412                         .animateShadowAlpha()
4413                         .animateDark()
4414                         .animateDimmed()
4415                         .animateHideSensitive()
4416                         .animateHeight()
4417                         .animateTopInset()
4418                         .animateY()
4419                         .animateZ(),
4420         };
4421 
4422         static int[] LENGTHS = new int[] {
4423 
4424                 // ANIMATION_TYPE_ADD
4425                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
4426 
4427                 // ANIMATION_TYPE_REMOVE
4428                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
4429 
4430                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4431                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4432 
4433                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4434                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4435 
4436                 // ANIMATION_TYPE_START_DRAG
4437                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4438 
4439                 // ANIMATION_TYPE_SNAP_BACK
4440                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4441 
4442                 // ANIMATION_TYPE_ACTIVATED_CHILD
4443                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
4444 
4445                 // ANIMATION_TYPE_DIMMED
4446                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
4447 
4448                 // ANIMATION_TYPE_CHANGE_POSITION
4449                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4450 
4451                 // ANIMATION_TYPE_DARK
4452                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4453 
4454                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4455                 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
4456 
4457                 // ANIMATION_TYPE_HIDE_SENSITIVE
4458                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4459 
4460                 // ANIMATION_TYPE_VIEW_RESIZE
4461                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4462 
4463                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
4464                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4465 
4466                 // ANIMATION_TYPE_HEADS_UP_APPEAR
4467                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
4468 
4469                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4470                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4471 
4472                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4473                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4474 
4475                 // ANIMATION_TYPE_HEADS_UP_OTHER
4476                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4477 
4478                 // ANIMATION_TYPE_EVERYTHING
4479                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4480         };
4481 
4482         static final int ANIMATION_TYPE_ADD = 0;
4483         static final int ANIMATION_TYPE_REMOVE = 1;
4484         static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
4485         static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
4486         static final int ANIMATION_TYPE_START_DRAG = 4;
4487         static final int ANIMATION_TYPE_SNAP_BACK = 5;
4488         static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
4489         static final int ANIMATION_TYPE_DIMMED = 7;
4490         static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
4491         static final int ANIMATION_TYPE_DARK = 9;
4492         static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
4493         static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
4494         static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
4495         static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
4496         static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
4497         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
4498         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
4499         static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
4500         static final int ANIMATION_TYPE_EVERYTHING = 18;
4501 
4502         static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
4503         static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
4504 
4505         final long eventStartTime;
4506         final View changingView;
4507         final int animationType;
4508         final AnimationFilter filter;
4509         final long length;
4510         View viewAfterChangingView;
4511         int darkAnimationOriginIndex;
4512         boolean headsUpFromBottom;
4513 
AnimationEvent(View view, int type)4514         AnimationEvent(View view, int type) {
4515             this(view, type, LENGTHS[type]);
4516         }
4517 
AnimationEvent(View view, int type, long length)4518         AnimationEvent(View view, int type, long length) {
4519             eventStartTime = AnimationUtils.currentAnimationTimeMillis();
4520             changingView = view;
4521             animationType = type;
4522             filter = FILTERS[type];
4523             this.length = length;
4524         }
4525 
4526         /**
4527          * Combines the length of several animation events into a single value.
4528          *
4529          * @param events The events of the lengths to combine.
4530          * @return The combined length. Depending on the event types, this might be the maximum of
4531          *         all events or the length of a specific event.
4532          */
combineLength(ArrayList<AnimationEvent> events)4533         static long combineLength(ArrayList<AnimationEvent> events) {
4534             long length = 0;
4535             int size = events.size();
4536             for (int i = 0; i < size; i++) {
4537                 AnimationEvent event = events.get(i);
4538                 length = Math.max(length, event.length);
4539                 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
4540                     return event.length;
4541                 }
4542             }
4543             return length;
4544         }
4545     }
4546 
4547 }
4548