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