blob: d8f6bcd17a2a7cdc18cd313b637ce7d3811768e0 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
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
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010019import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010020import android.content.Context;
21import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010022import android.graphics.Canvas;
Selim Cinek67b22602014-03-10 15:40:16 +010023import android.graphics.Paint;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010024import android.graphics.PointF;
Selim Cinek67b22602014-03-10 15:40:16 +010025import android.util.AttributeSet;
26import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070027import android.util.Pair;
Selim Cinek67b22602014-03-10 15:40:16 +010028import android.view.MotionEvent;
29import android.view.VelocityTracker;
30import android.view.View;
31import android.view.ViewConfiguration;
32import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020033import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020034import android.view.animation.AnimationUtils;
Selim Cinek67b22602014-03-10 15:40:16 +010035import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020036
Selim Cinek67b22602014-03-10 15:40:16 +010037import com.android.systemui.ExpandHelper;
38import com.android.systemui.R;
39import com.android.systemui.SwipeHelper;
Selim Cineka32ab602014-06-11 15:06:01 +020040import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040041import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020042import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010043import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020044import com.android.systemui.statusbar.ExpandableView;
Selim Cinekb5605e52015-02-20 18:21:41 +010045import com.android.systemui.statusbar.NotificationData;
Selim Cinekc27437b2014-05-14 10:23:33 +020046import com.android.systemui.statusbar.SpeedBumpView;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010047import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020048import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb5605e52015-02-20 18:21:41 +010049import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020050import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070051import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070052import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010053import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010054
Selim Cinek572bbd42014-04-25 16:43:27 +020055import java.util.ArrayList;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020056import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020057
Selim Cinek67b22602014-03-10 15:40:16 +010058/**
59 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
60 */
61public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020062 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Selim Cinekb5605e52015-02-20 18:21:41 +010063 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
Selim Cinek67b22602014-03-10 15:40:16 +010064
65 private static final String TAG = "NotificationStackScrollLayout";
66 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020067 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
68 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020069 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010070
71 /**
72 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
73 */
74 private static final int INVALID_POINTER = -1;
75
Selim Cinek1408eb52014-06-02 14:45:38 +020076 private ExpandHelper mExpandHelper;
Selim Cinek67b22602014-03-10 15:40:16 +010077 private SwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +020078 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +010079 private int mCurrentStackHeight = Integer.MAX_VALUE;
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +010080
81 /**
82 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
83 * externally from {@link #setStackHeight}
84 */
85 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +010086 private int mOwnScrollY;
87 private int mMaxLayoutHeight;
88
89 private VelocityTracker mVelocityTracker;
90 private OverScroller mScroller;
91 private int mTouchSlop;
92 private int mMinimumVelocity;
93 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +010094 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020095 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +010096 private boolean mIsBeingDragged;
97 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +020098 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +010099 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100100 private boolean mTouchIsClick;
101 private float mInitialTouchX;
102 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100103
104 private int mSidePaddings;
105 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100106 private int mContentHeight;
107 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200108 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100109 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100110 private int mPaddingBetweenElements;
Selim Cineka5eaa602014-05-12 21:27:47 +0200111 private int mPaddingBetweenElementsDimmed;
112 private int mPaddingBetweenElementsNormal;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200113 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200114 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100115
116 /**
117 * The algorithm which calculates the properties for our children
118 */
119 private StackScrollAlgorithm mStackScrollAlgorithm;
120
121 /**
122 * The current State this Layout is in
123 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200124 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200125 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100126 private NotificationGroupManager mGroupManager;
Selim Cineka59ecc32015-04-07 10:51:49 -0700127 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
128 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
129 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
130 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
131 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
132 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200133 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700134 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
135 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200136 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200137 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200138 private boolean mChangePositionInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200139
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200140 /**
141 * The raw amount of the overScroll on the top, which is not rubber-banded.
142 */
143 private float mOverScrolledTopPixels;
144
145 /**
146 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
147 */
148 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200149 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200150 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200151 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100152 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200153 private boolean mNeedsAnimation;
154 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200155 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200156 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400157 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100158 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200159 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200160 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200161 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200162 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200163 private SpeedBumpView mSpeedBumpView;
164 private boolean mIsExpansionChanging;
Selim Cinek1408eb52014-06-02 14:45:38 +0200165 private boolean mExpandingNotification;
166 private boolean mExpandedInThisMotion;
167 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400168 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200169 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400170 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200171
172 /**
173 * Was the scroller scrolled to the top when the down motion was observed?
174 */
175 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200176 /**
177 * The minimal amount of over scroll which is needed in order to switch to the quick settings
178 * when over scrolling on a expanded card.
179 */
180 private float mMinTopOverScrollToEscape;
181 private int mIntrinsicPadding;
182 private int mNotificationTopPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700183 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200184 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200185 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200186 private boolean mRequestViewResizeAnimationOnLayout;
187 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100188 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700189 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700190
Selim Cinek1408eb52014-06-02 14:45:38 +0200191 /**
192 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
193 * This is needed to avoid scrolling too far after the notification was collapsed in the same
194 * motion.
195 */
196 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400197 private SwipeHelper.LongPressListener mLongPressListener;
Selim Cinek1408eb52014-06-02 14:45:38 +0200198
199 /**
200 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
201 * animating.
202 */
203 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200204 private ViewGroup mScrollView;
205 private boolean mInterceptDelegateEnabled;
206 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700207 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700208 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200209 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200210 = new ViewTreeObserver.OnPreDrawListener() {
211 @Override
212 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200213 updateChildren();
214 mChildrenUpdateRequested = false;
215 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200216 return true;
217 }
218 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200219 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100220 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100221 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700222 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
223 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
224 = new HashSet<>();
225 private HeadsUpManager mHeadsUpManager;
226 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700227 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200228 private boolean mForceNoOverlappingRendering;
Selim Cinek67b22602014-03-10 15:40:16 +0100229
230 public NotificationStackScrollLayout(Context context) {
231 this(context, null);
232 }
233
234 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
235 this(context, attrs, 0);
236 }
237
238 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
239 this(context, attrs, defStyleAttr, 0);
240 }
241
242 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
243 int defStyleRes) {
244 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200245 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
246 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
247 mExpandHelper = new ExpandHelper(getContext(), this,
248 minHeight, maxHeight);
249 mExpandHelper.setEventSource(this);
250 mExpandHelper.setScrollAdapter(this);
251
252 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
253 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinek67b22602014-03-10 15:40:16 +0100254 initView(context);
255 if (DEBUG) {
256 setWillNotDraw(false);
257 mDebugPaint = new Paint();
258 mDebugPaint.setColor(0xffff0000);
259 mDebugPaint.setStrokeWidth(2);
260 mDebugPaint.setStyle(Paint.Style.STROKE);
261 }
262 }
263
264 @Override
265 protected void onDraw(Canvas canvas) {
266 if (DEBUG) {
267 int y = mCollapsedSize;
268 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200269 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200270 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200271 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
272 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100273 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
274 y = (int) getLayoutHeight();
275 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200276 y = getHeight() - getEmptyBottomMargin();
277 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100278 }
279 }
280
281 private void initView(Context context) {
282 mScroller = new OverScroller(getContext());
283 setFocusable(true);
284 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200285 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100286 final ViewConfiguration configuration = ViewConfiguration.get(context);
287 mTouchSlop = configuration.getScaledTouchSlop();
288 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
289 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100290 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100291
292 mSidePaddings = context.getResources()
293 .getDimensionPixelSize(R.dimen.notification_side_padding);
Selim Cinek67b22602014-03-10 15:40:16 +0100294 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200295 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100296 mBottomStackPeekSize = context.getResources()
297 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinek67b22602014-03-10 15:40:16 +0100298 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200299 mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed());
Selim Cineka5eaa602014-05-12 21:27:47 +0200300 mPaddingBetweenElementsDimmed = context.getResources()
301 .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
302 mPaddingBetweenElementsNormal = context.getResources()
303 .getDimensionPixelSize(R.dimen.notification_padding);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200304 updatePadding(mAmbientState.isDimmed());
Selim Cinek1408eb52014-06-02 14:45:38 +0200305 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
306 R.dimen.min_top_overscroll_to_qs);
307 mNotificationTopPadding = getResources().getDimensionPixelSize(
308 R.dimen.notifications_top_padding);
Selim Cinekd83771e2014-07-04 16:45:31 +0200309 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
310 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200311 }
312
313 private void updatePadding(boolean dimmed) {
Jorim Jaggid7c1fae2014-08-13 18:27:47 +0200314 mPaddingBetweenElements = dimmed && mStackScrollAlgorithm.shouldScaleDimmed()
Selim Cineka5eaa602014-05-12 21:27:47 +0200315 ? mPaddingBetweenElementsDimmed
316 : mPaddingBetweenElementsNormal;
317 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
318 updateContentHeight();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200319 notifyHeightChangeListener(null);
320 }
321
322 private void notifyHeightChangeListener(ExpandableView view) {
323 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100324 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200325 }
Selim Cinek67b22602014-03-10 15:40:16 +0100326 }
327
328 @Override
329 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
330 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
331 int mode = MeasureSpec.getMode(widthMeasureSpec);
332 int size = MeasureSpec.getSize(widthMeasureSpec);
333 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
334 measureChildren(childMeasureSpec, heightMeasureSpec);
335 }
336
337 @Override
338 protected void onLayout(boolean changed, int l, int t, int r, int b) {
339
340 // we layout all our children centered on the top
341 float centerX = getWidth() / 2.0f;
342 for (int i = 0; i < getChildCount(); i++) {
343 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100344 if (child.getVisibility() == GONE) {
345 continue;
346 }
Selim Cinek67b22602014-03-10 15:40:16 +0100347 float width = child.getMeasuredWidth();
348 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100349 child.layout((int) (centerX - width / 2.0f),
350 0,
351 (int) (centerX + width / 2.0f),
352 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100353 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200354 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100355 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200356 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100357 if (mRequestViewResizeAnimationOnLayout) {
358 requestAnimationOnViewResize();
359 mRequestViewResizeAnimationOnLayout = false;
360 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200361 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100362 }
363
Selim Cineka5e211b2014-08-11 17:35:48 +0200364 private void requestAnimationOnViewResize() {
Selim Cinekb5605e52015-02-20 18:21:41 +0100365 if (mIsExpanded && mAnimationsEnabled) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200366 mNeedViewResizeAnimation = true;
367 mNeedsAnimation = true;
368 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200369 }
370
Selim Cinekc27437b2014-05-14 10:23:33 +0200371 public void updateSpeedBumpIndex(int newIndex) {
372 int currentIndex = indexOfChild(mSpeedBumpView);
373
374 // If we are currently layouted before the new speed bump index, we have to decrease it.
375 boolean validIndex = newIndex > 0;
376 if (newIndex > getChildCount() - 1) {
377 validIndex = false;
378 newIndex = -1;
379 }
380 if (validIndex && currentIndex != newIndex) {
381 changeViewPosition(mSpeedBumpView, newIndex);
382 }
383 updateSpeedBump(validIndex);
384 mAmbientState.setSpeedBumpIndex(newIndex);
385 }
386
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200387 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
388 mListener = listener;
389 }
390
391 /**
392 * Returns the location the given child is currently rendered at.
393 *
394 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100395 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200396 */
397 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100398 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200399 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100400 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200401 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100402 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100403 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100404 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200405 return childViewState.location;
406 }
407
Selim Cinek67b22602014-03-10 15:40:16 +0100408 private void setMaxLayoutHeight(int maxLayoutHeight) {
409 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200410 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100411 }
412
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200413 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700414 mAmbientState.setLayoutHeight(getLayoutHeight());
415 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100416 }
417
418 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200419 * @return whether the height of the layout needs to be adapted, in order to ensure that the
420 * last child is not in the bottom stack.
421 */
422 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200423 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200424 }
425
Selim Cinek4a1ac842014-05-01 15:51:58 +0200426 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100427 * Updates the children views according to the stack scroll algorithm. Call this whenever
428 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
429 */
430 private void updateChildren() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200431 mAmbientState.setScrollY(mOwnScrollY);
432 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200433 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200434 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100435 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200436 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100437 }
438 }
439
Selim Cinek319bdc42014-05-01 23:01:58 +0200440 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200441 if (!mChildrenUpdateRequested) {
442 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
443 mChildrenUpdateRequested = true;
444 invalidate();
445 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200446 }
447
Selim Cinek67b22602014-03-10 15:40:16 +0100448 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200449 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100450 }
451
Selim Cinekf7a14c02014-07-07 14:01:46 +0200452 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100453 int scrollRange = getScrollRange();
454 if (scrollRange < mOwnScrollY) {
455 mOwnScrollY = scrollRange;
456 }
457 }
458
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200459 public int getTopPadding() {
460 return mTopPadding;
461 }
462
Selim Cinek1408eb52014-06-02 14:45:38 +0200463 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200464 if (mTopPadding != topPadding) {
465 mTopPadding = topPadding;
466 updateAlgorithmHeightAndPadding();
467 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200468 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200469 mTopPaddingNeedsAnimation = true;
470 mNeedsAnimation = true;
471 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200472 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200473 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200474 }
475 }
476
477 /**
478 * Update the height of the stack to a new height.
479 *
480 * @param height the new height of the stack
481 */
482 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100483 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200484 setIsExpanded(height > 0.0f);
485 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200486 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200487 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700488 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700489 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek684a4422015-04-15 16:18:39 -0700490 int normalUnfoldPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700491 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700492 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100493 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700494 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200495 stackHeight = newStackHeight;
496 } else {
497
498 // We did not reach the position yet where we actually start growing,
499 // so we translate the stack upwards.
500 int translationY = (newStackHeight - minStackHeight);
501 // A slight parallax effect is introduced in order for the stack to catch up with
502 // the top card.
Jorim Jaggi58bef332014-11-21 18:10:32 +0100503 float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
504 / minStackHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200505 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700506 if (!trackingHeadsUp) {
507 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
508 mCollapseSecondCardPadding);
509 } else {
510 translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight());
511 }
512 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200513 stackHeight = (int) (height - (translationY - mTopPadding));
514 }
515 if (stackHeight != mCurrentStackHeight) {
516 mCurrentStackHeight = stackHeight;
517 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200518 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200519 }
Selim Cinekd2281152015-04-10 14:37:46 -0700520 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700521 }
522
Selim Cinekd2281152015-04-10 14:37:46 -0700523 public float getStackTranslation() {
524 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700525 }
526
Selim Cinekd2281152015-04-10 14:37:46 -0700527 private void setStackTranslation(float stackTranslation) {
528 if (stackTranslation != mStackTranslation) {
529 mStackTranslation = stackTranslation;
530 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700531 requestChildrenUpdate();
532 }
Selim Cinek67b22602014-03-10 15:40:16 +0100533 }
534
535 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100536 * Get the current height of the view. This is at most the msize of the view given by a the
Selim Cinek67b22602014-03-10 15:40:16 +0100537 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
538 *
539 * @return either the layout height or the externally defined height, whichever is smaller
540 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200541 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100542 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
543 }
544
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100545 public int getItemHeight() {
546 return mCollapsedSize;
547 }
548
549 public int getBottomStackPeekSize() {
550 return mBottomStackPeekSize;
551 }
552
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200553 public int getCollapseSecondCardPadding() {
554 return mCollapseSecondCardPadding;
555 }
556
Dan Sandler4247a5c2014-07-23 15:58:08 -0400557 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100558 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400559 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100560 }
561
Jorim Jaggi56306252014-07-03 00:40:09 +0200562 public void setScrollView(ViewGroup scrollView) {
563 mScrollView = scrollView;
564 }
565
566 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
567 mInterceptDelegateEnabled = interceptDelegateEnabled;
568 }
569
Selim Cinek67b22602014-03-10 15:40:16 +0100570 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400571 if (mDismissAllInProgress) {
572 return;
573 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100574 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200575 if (mDragAnimPendingChildren.contains(v)) {
576 // We start the swipe and finish it in the same frame, we don't want any animation
577 // for the drag
578 mDragAnimPendingChildren.remove(v);
579 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200580 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200581 mAmbientState.onDragFinished(v);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700582 if (v instanceof ExpandableNotificationRow) {
583 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
584 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700585 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700586 }
587 }
588 final View veto = v.findViewById(R.id.veto);
589 if (veto != null && veto.getVisibility() != View.GONE) {
590 veto.performClick();
591 }
592 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
Selim Cinekeb973562014-05-02 17:07:49 +0200593 }
594
595 @Override
596 public void onChildSnappedBack(View animView) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200597 mAmbientState.onDragFinished(animView);
Selim Cinekeb973562014-05-02 17:07:49 +0200598 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200599 if (mAnimationsEnabled) {
600 mSnappedBackChildren.add(animView);
601 mNeedsAnimation = true;
602 }
Selim Cinekeb973562014-05-02 17:07:49 +0200603 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200604 } else {
605 // We start the swipe and snap back in the same frame, we don't want any animation
606 mDragAnimPendingChildren.remove(animView);
607 }
Selim Cinek67b22602014-03-10 15:40:16 +0100608 }
609
Adrian Roos5d9cc662014-05-28 17:08:13 +0200610 @Override
611 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700612 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700613 mScrimController.setTopHeadsUpDragAmount(animView,
614 Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
615 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200616 return false;
617 }
618
Selim Cinek67b22602014-03-10 15:40:16 +0100619 public void onBeginDrag(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100620 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200621 mAmbientState.onBeginDrag(v);
Selim Cinek131c1e22015-05-11 19:04:49 -0700622 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200623 mDragAnimPendingChildren.add(v);
624 mNeedsAnimation = true;
625 }
Selim Cinekeb973562014-05-02 17:07:49 +0200626 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100627 }
628
Selim Cinek684a4422015-04-15 16:18:39 -0700629 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700630 if (v instanceof ExpandableNotificationRow) {
631 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700632 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700633 }
634 return false;
635 }
636
637 private boolean isHeadsUp(View v) {
638 if (v instanceof ExpandableNotificationRow) {
639 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
640 return row.isHeadsUp();
641 }
642 return false;
643 }
644
Selim Cinek67b22602014-03-10 15:40:16 +0100645 public void onDragCancelled(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100646 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100647 }
648
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700649 @Override
650 public float getFalsingThresholdFactor() {
651 return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
652 }
653
Selim Cinek67b22602014-03-10 15:40:16 +0100654 public View getChildAtPosition(MotionEvent ev) {
655 return getChildAtPosition(ev.getX(), ev.getY());
656 }
657
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100658 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
659 getLocationOnScreen(mTempInt2);
660 float localTouchY = touchY - mTempInt2[1];
661
662 ExpandableView closestChild = null;
663 float minDist = Float.MAX_VALUE;
664
665 // find the view closest to the location, accounting for GONE views
666 final int count = getChildCount();
667 for (int childIdx = 0; childIdx < count; childIdx++) {
668 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
669 if (slidingChild.getVisibility() == GONE
670 || slidingChild instanceof StackScrollerDecorView
671 || slidingChild == mSpeedBumpView) {
672 continue;
673 }
674 float childTop = slidingChild.getTranslationY();
675 float top = childTop + slidingChild.getClipTopAmount();
676 float bottom = childTop + slidingChild.getActualHeight();
677
678 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
679 if (dist < minDist) {
680 closestChild = slidingChild;
681 minDist = dist;
682 }
683 }
684 return closestChild;
685 }
686
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200687 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100688 getLocationOnScreen(mTempInt2);
689 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100690 }
691
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200692 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100693 // find the view under the pointer, accounting for GONE views
694 final int count = getChildCount();
695 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200696 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100697 if (slidingChild.getVisibility() == GONE
698 || slidingChild instanceof StackScrollerDecorView
699 || slidingChild == mSpeedBumpView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100700 continue;
701 }
Selim Cinek89faff12014-06-19 16:29:04 -0700702 float childTop = slidingChild.getTranslationY();
703 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200704 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200705
706 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
707 // camera affordance).
708 int left = 0;
709 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100710
711 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100712 if (slidingChild instanceof ExpandableNotificationRow) {
713 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700714 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cineka59ecc32015-04-07 10:51:49 -0700715 && mHeadsUpManager.getTopEntry().entry.row != row) {
716 continue;
717 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100718 return row.getViewAtPosition(touchY - childTop);
719 }
Selim Cinek67b22602014-03-10 15:40:16 +0100720 return slidingChild;
721 }
722 }
723 return null;
724 }
725
726 public boolean canChildBeExpanded(View v) {
727 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700728 && ((ExpandableNotificationRow) v).isExpandable()
729 && !((ExpandableNotificationRow) v).isHeadsUp();
Selim Cinek67b22602014-03-10 15:40:16 +0100730 }
731
732 public void setUserExpandedChild(View v, boolean userExpanded) {
733 if (v instanceof ExpandableNotificationRow) {
734 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
735 }
736 }
737
738 public void setUserLockedChild(View v, boolean userLocked) {
739 if (v instanceof ExpandableNotificationRow) {
740 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
741 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200742 removeLongPressCallback();
743 requestDisallowInterceptTouchEvent(true);
744 }
745
746 @Override
747 public void expansionStateChanged(boolean isExpanding) {
748 mExpandingNotification = isExpanding;
749 if (!mExpandedInThisMotion) {
750 mMaxScrollAfterExpand = mOwnScrollY;
751 mExpandedInThisMotion = true;
752 }
753 }
754
755 public void setScrollingEnabled(boolean enable) {
756 mScrollingEnabled = enable;
757 }
758
759 public void setExpandingEnabled(boolean enable) {
760 mExpandHelper.setEnabled(enable);
761 }
762
763 private boolean isScrollingEnabled() {
764 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100765 }
766
767 public View getChildContentView(View v) {
768 return v;
769 }
770
771 public boolean canChildBeDismissed(View v) {
772 final View veto = v.findViewById(R.id.veto);
773 return (veto != null && veto.getVisibility() != View.GONE);
774 }
775
Selim Cinek19c8c702014-08-25 22:09:19 +0200776 @Override
777 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200778 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200779 }
780
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100781 private void setSwipingInProgress(boolean isSwiped) {
782 mSwipingInProgress = isSwiped;
783 if(isSwiped) {
784 requestDisallowInterceptTouchEvent(true);
785 }
Selim Cinek67b22602014-03-10 15:40:16 +0100786 }
787
788 @Override
789 protected void onConfigurationChanged(Configuration newConfig) {
790 super.onConfigurationChanged(newConfig);
791 float densityScale = getResources().getDisplayMetrics().density;
792 mSwipeHelper.setDensityScale(densityScale);
793 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
794 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
795 initView(getContext());
796 }
797
Dan Sandlereceda3d2014-07-21 15:35:01 -0400798 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400799 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100800 }
801
802 @Override
803 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200804 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
805 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Jorim Jaggi56306252014-07-03 00:40:09 +0200806 if (mDelegateToScrollView) {
807 if (isCancelOrUp) {
808 mDelegateToScrollView = false;
809 }
810 transformTouchEvent(ev, this, mScrollView);
811 return mScrollView.onTouchEvent(ev);
812 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100813 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200814 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +0200815 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200816 if (isCancelOrUp) {
817 mExpandHelper.onlyObserveMovements(false);
818 }
819 boolean wasExpandingBefore = mExpandingNotification;
820 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200821 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
822 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200823 dispatchDownEventToScroller(ev);
824 }
825 }
Selim Cinek67b22602014-03-10 15:40:16 +0100826 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700827 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
828 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100829 scrollerWantsIt = onScrollTouch(ev);
830 }
831 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200832 if (!mIsBeingDragged
833 && !mExpandingNotification
834 && !mExpandedInThisMotion
835 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100836 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
837 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200838 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
839 }
840
841 private void dispatchDownEventToScroller(MotionEvent ev) {
842 MotionEvent downEvent = MotionEvent.obtain(ev);
843 downEvent.setAction(MotionEvent.ACTION_DOWN);
844 onScrollTouch(downEvent);
845 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100846 }
847
848 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200849 if (!isScrollingEnabled()) {
850 return false;
851 }
Selim Cinek67b22602014-03-10 15:40:16 +0100852 initVelocityTrackerIfNotExists();
853 mVelocityTracker.addMovement(ev);
854
855 final int action = ev.getAction();
856
857 switch (action & MotionEvent.ACTION_MASK) {
858 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +0200859 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +0100860 return false;
861 }
862 boolean isBeingDragged = !mScroller.isFinished();
863 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +0100864
865 /*
866 * If being flinged and user touches, stop the fling. isFinished
867 * will be false if being flinged.
868 */
869 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200870 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100871 }
872
873 // Remember where the motion event started
874 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +0200875 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +0100876 mActivePointerId = ev.getPointerId(0);
877 break;
878 }
879 case MotionEvent.ACTION_MOVE:
880 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
881 if (activePointerIndex == -1) {
882 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
883 break;
884 }
885
886 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +0200887 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +0100888 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200889 final int xDiff = Math.abs(x - mDownX);
890 final int yDiff = Math.abs(deltaY);
891 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +0100892 setIsBeingDragged(true);
893 if (deltaY > 0) {
894 deltaY -= mTouchSlop;
895 } else {
896 deltaY += mTouchSlop;
897 }
898 }
899 if (mIsBeingDragged) {
900 // Scroll to follow the motion event
901 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200902 int range = getScrollRange();
903 if (mExpandedInThisMotion) {
904 range = Math.min(range, mMaxScrollAfterExpand);
905 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200906
907 float scrollAmount;
908 if (deltaY < 0) {
909 scrollAmount = overScrollDown(deltaY);
910 } else {
911 scrollAmount = overScrollUp(deltaY, range);
912 }
Selim Cinek67b22602014-03-10 15:40:16 +0100913
914 // Calling overScrollBy will call onOverScrolled, which
915 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200916 if (scrollAmount != 0.0f) {
917 // The scrolling motion could not be compensated with the
918 // existing overScroll, we have to scroll the view
919 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
920 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +0100921 }
Selim Cinek67b22602014-03-10 15:40:16 +0100922 }
923 break;
924 case MotionEvent.ACTION_UP:
925 if (mIsBeingDragged) {
926 final VelocityTracker velocityTracker = mVelocityTracker;
927 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
928 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
929
Selim Cinek1408eb52014-06-02 14:45:38 +0200930 if (shouldOverScrollFling(initialVelocity)) {
931 onOverScrollFling(true, initialVelocity);
932 } else {
933 if (getChildCount() > 0) {
934 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
935 float currentOverScrollTop = getCurrentOverScrollAmount(true);
936 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
937 fling(-initialVelocity);
938 } else {
939 onOverScrollFling(false, initialVelocity);
940 }
941 } else {
942 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
943 getScrollRange())) {
944 postInvalidateOnAnimation();
945 }
Selim Cinek67b22602014-03-10 15:40:16 +0100946 }
947 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200948 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700949
950 mActivePointerId = INVALID_POINTER;
951 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +0100952 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700953
Selim Cinek67b22602014-03-10 15:40:16 +0100954 break;
955 case MotionEvent.ACTION_CANCEL:
956 if (mIsBeingDragged && getChildCount() > 0) {
957 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
958 postInvalidateOnAnimation();
959 }
960 mActivePointerId = INVALID_POINTER;
961 endDrag();
962 }
963 break;
964 case MotionEvent.ACTION_POINTER_DOWN: {
965 final int index = ev.getActionIndex();
966 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +0200967 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +0100968 mActivePointerId = ev.getPointerId(index);
969 break;
970 }
971 case MotionEvent.ACTION_POINTER_UP:
972 onSecondaryPointerUp(ev);
973 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +0200974 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +0100975 break;
976 }
977 return true;
978 }
979
Selim Cinek1408eb52014-06-02 14:45:38 +0200980 private void onOverScrollFling(boolean open, int initialVelocity) {
981 if (mOverscrollTopChangedListener != null) {
982 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
983 }
984 mDontReportNextOverScroll = true;
985 setOverScrollAmount(0.0f, true, false);
986 }
987
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200988 /**
989 * Perform a scroll upwards and adapt the overscroll amounts accordingly
990 *
991 * @param deltaY The amount to scroll upwards, has to be positive.
992 * @return The amount of scrolling to be performed by the scroller,
993 * not handled by the overScroll amount.
994 */
995 private float overScrollUp(int deltaY, int range) {
996 deltaY = Math.max(deltaY, 0);
997 float currentTopAmount = getCurrentOverScrollAmount(true);
998 float newTopAmount = currentTopAmount - deltaY;
999 if (currentTopAmount > 0) {
1000 setOverScrollAmount(newTopAmount, true /* onTop */,
1001 false /* animate */);
1002 }
1003 // Top overScroll might not grab all scrolling motion,
1004 // we have to scroll as well.
1005 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1006 float newScrollY = mOwnScrollY + scrollAmount;
1007 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001008 if (!mExpandedInThisMotion) {
1009 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1010 // We overScroll on the top
1011 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1012 false /* onTop */,
1013 false /* animate */);
1014 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001015 mOwnScrollY = range;
1016 scrollAmount = 0.0f;
1017 }
1018 return scrollAmount;
1019 }
1020
1021 /**
1022 * Perform a scroll downward and adapt the overscroll amounts accordingly
1023 *
1024 * @param deltaY The amount to scroll downwards, has to be negative.
1025 * @return The amount of scrolling to be performed by the scroller,
1026 * not handled by the overScroll amount.
1027 */
1028 private float overScrollDown(int deltaY) {
1029 deltaY = Math.min(deltaY, 0);
1030 float currentBottomAmount = getCurrentOverScrollAmount(false);
1031 float newBottomAmount = currentBottomAmount + deltaY;
1032 if (currentBottomAmount > 0) {
1033 setOverScrollAmount(newBottomAmount, false /* onTop */,
1034 false /* animate */);
1035 }
1036 // Bottom overScroll might not grab all scrolling motion,
1037 // we have to scroll as well.
1038 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1039 float newScrollY = mOwnScrollY + scrollAmount;
1040 if (newScrollY < 0) {
1041 float currentTopPixels = getCurrentOverScrolledPixels(true);
1042 // We overScroll on the top
1043 setOverScrolledPixels(currentTopPixels - newScrollY,
1044 true /* onTop */,
1045 false /* animate */);
1046 mOwnScrollY = 0;
1047 scrollAmount = 0.0f;
1048 }
1049 return scrollAmount;
1050 }
1051
Selim Cinek67b22602014-03-10 15:40:16 +01001052 private void onSecondaryPointerUp(MotionEvent ev) {
1053 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1054 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1055 final int pointerId = ev.getPointerId(pointerIndex);
1056 if (pointerId == mActivePointerId) {
1057 // This was our active pointer going up. Choose a new
1058 // active pointer and adjust accordingly.
1059 // TODO: Make this decision more intelligent.
1060 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1061 mLastMotionY = (int) ev.getY(newPointerIndex);
1062 mActivePointerId = ev.getPointerId(newPointerIndex);
1063 if (mVelocityTracker != null) {
1064 mVelocityTracker.clear();
1065 }
1066 }
1067 }
1068
1069 private void initVelocityTrackerIfNotExists() {
1070 if (mVelocityTracker == null) {
1071 mVelocityTracker = VelocityTracker.obtain();
1072 }
1073 }
1074
1075 private void recycleVelocityTracker() {
1076 if (mVelocityTracker != null) {
1077 mVelocityTracker.recycle();
1078 mVelocityTracker = null;
1079 }
1080 }
1081
1082 private void initOrResetVelocityTracker() {
1083 if (mVelocityTracker == null) {
1084 mVelocityTracker = VelocityTracker.obtain();
1085 } else {
1086 mVelocityTracker.clear();
1087 }
1088 }
1089
1090 @Override
1091 public void computeScroll() {
1092 if (mScroller.computeScrollOffset()) {
1093 // This is called at drawing time by ViewGroup.
1094 int oldX = mScrollX;
1095 int oldY = mOwnScrollY;
1096 int x = mScroller.getCurrX();
1097 int y = mScroller.getCurrY();
1098
1099 if (oldX != x || oldY != y) {
1100 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001101 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1102 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001103 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001104 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1105 }
1106 }
Selim Cinek67b22602014-03-10 15:40:16 +01001107
1108 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001109 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001110 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001111 }
1112
1113 // Keep on drawing until the animation has finished.
1114 postInvalidateOnAnimation();
1115 }
1116 }
1117
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001118 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001119 protected boolean overScrollBy(int deltaX, int deltaY,
1120 int scrollX, int scrollY,
1121 int scrollRangeX, int scrollRangeY,
1122 int maxOverScrollX, int maxOverScrollY,
1123 boolean isTouchEvent) {
1124
1125 int newScrollY = scrollY + deltaY;
1126
1127 final int top = -maxOverScrollY;
1128 final int bottom = maxOverScrollY + scrollRangeY;
1129
1130 boolean clampedY = false;
1131 if (newScrollY > bottom) {
1132 newScrollY = bottom;
1133 clampedY = true;
1134 } else if (newScrollY < top) {
1135 newScrollY = top;
1136 clampedY = true;
1137 }
1138
1139 onOverScrolled(0, newScrollY, false, clampedY);
1140
1141 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001142 }
1143
1144 /**
1145 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1146 * overscroll effect based on numPixels. By default this will also cancel animations on the
1147 * same overScroll edge.
1148 *
1149 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1150 * the rubber-banding logic.
1151 * @param onTop Should the effect be applied on top of the scroller.
1152 * @param animate Should an animation be performed.
1153 */
1154 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001155 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001156 }
1157
1158 /**
1159 * Set the effective overScroll amount which will be directly reflected in the layout.
1160 * By default this will also cancel animations on the same overScroll edge.
1161 *
1162 * @param amount The amount to overScroll by.
1163 * @param onTop Should the effect be applied on top of the scroller.
1164 * @param animate Should an animation be performed.
1165 */
1166 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1167 setOverScrollAmount(amount, onTop, animate, true);
1168 }
1169
1170 /**
1171 * Set the effective overScroll amount which will be directly reflected in the layout.
1172 *
1173 * @param amount The amount to overScroll by.
1174 * @param onTop Should the effect be applied on top of the scroller.
1175 * @param animate Should an animation be performed.
1176 * @param cancelAnimators Should running animations be cancelled.
1177 */
1178 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1179 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001180 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1181 }
1182
1183 /**
1184 * Set the effective overScroll amount which will be directly reflected in the layout.
1185 *
1186 * @param amount The amount to overScroll by.
1187 * @param onTop Should the effect be applied on top of the scroller.
1188 * @param animate Should an animation be performed.
1189 * @param cancelAnimators Should running animations be cancelled.
1190 * @param isRubberbanded The value which will be passed to
1191 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1192 */
1193 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1194 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001195 if (cancelAnimators) {
1196 mStateAnimator.cancelOverScrollAnimators(onTop);
1197 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001198 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001199 }
1200
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001201 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1202 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001203 amount = Math.max(0, amount);
1204 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001205 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001206 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001207 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001208 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001209 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001210 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001211 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001212 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001213 }
1214 }
1215
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001216 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001217 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1218 if (mDontReportNextOverScroll) {
1219 mDontReportNextOverScroll = false;
1220 return;
1221 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001222 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001223 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001224 }
1225 }
1226
1227 public void setOverscrollTopChangedListener(
1228 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1229 mOverscrollTopChangedListener = overscrollTopChangedListener;
1230 }
1231
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001232 public float getCurrentOverScrollAmount(boolean top) {
1233 return mAmbientState.getOverScrollAmount(top);
1234 }
1235
1236 public float getCurrentOverScrolledPixels(boolean top) {
1237 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1238 }
1239
1240 private void setOverScrolledPixels(float amount, boolean onTop) {
1241 if (onTop) {
1242 mOverScrolledTopPixels = amount;
1243 } else {
1244 mOverScrolledBottomPixels = amount;
1245 }
1246 }
1247
Selim Cinek319bdc42014-05-01 23:01:58 +02001248 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001249 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001250 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001251 }
1252
1253 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001254 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001255 // Treat animating scrolls differently; see #computeScroll() for why.
1256 if (!mScroller.isFinished()) {
1257 final int oldX = mScrollX;
1258 final int oldY = mOwnScrollY;
1259 mScrollX = scrollX;
1260 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001261 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001262 springBack();
1263 } else {
1264 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1265 invalidateParentIfNeeded();
1266 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001267 float overScrollTop = getCurrentOverScrollAmount(true);
1268 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001269 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001270 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001271 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001272 }
Selim Cinek67b22602014-03-10 15:40:16 +01001273 }
Selim Cinek67b22602014-03-10 15:40:16 +01001274 } else {
1275 customScrollTo(scrollY);
1276 scrollTo(scrollX, mScrollY);
1277 }
1278 }
1279
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001280 private void springBack() {
1281 int scrollRange = getScrollRange();
1282 boolean overScrolledTop = mOwnScrollY <= 0;
1283 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1284 if (overScrolledTop || overScrolledBottom) {
1285 boolean onTop;
1286 float newAmount;
1287 if (overScrolledTop) {
1288 onTop = true;
1289 newAmount = -mOwnScrollY;
1290 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001291 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001292 } else {
1293 onTop = false;
1294 newAmount = mOwnScrollY - scrollRange;
1295 mOwnScrollY = scrollRange;
1296 }
1297 setOverScrollAmount(newAmount, onTop, false);
1298 setOverScrollAmount(0.0f, onTop, true);
1299 mScroller.forceFinished(true);
1300 }
1301 }
1302
Selim Cinek67b22602014-03-10 15:40:16 +01001303 private int getScrollRange() {
1304 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001305 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001306 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001307 int contentHeight = getContentHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001308 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cineka5eaa602014-05-12 21:27:47 +02001309 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1310 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001311 if (scrollRange > 0) {
1312 View lastChild = getLastChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001313 // We want to at least be able collapse the first item and not ending in a weird
1314 // end state.
1315 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1316 }
Selim Cinek67b22602014-03-10 15:40:16 +01001317 }
1318 return scrollRange;
1319 }
1320
Selim Cinek343e6e22014-04-11 21:23:30 +02001321 /**
1322 * @return the first child which has visibility unequal to GONE
1323 */
1324 private View getFirstChildNotGone() {
1325 int childCount = getChildCount();
1326 for (int i = 0; i < childCount; i++) {
1327 View child = getChildAt(i);
1328 if (child.getVisibility() != View.GONE) {
1329 return child;
1330 }
1331 }
1332 return null;
1333 }
1334
Selim Cinek4a1ac842014-05-01 15:51:58 +02001335 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001336 * @return The first child which has visibility unequal to GONE which is currently below the
1337 * given translationY or equal to it.
1338 */
1339 private View getFirstChildBelowTranlsationY(float translationY) {
1340 int childCount = getChildCount();
1341 for (int i = 0; i < childCount; i++) {
1342 View child = getChildAt(i);
1343 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1344 return child;
1345 }
1346 }
1347 return null;
1348 }
1349
1350 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001351 * @return the last child which has visibility unequal to GONE
1352 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001353 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001354 int childCount = getChildCount();
1355 for (int i = childCount - 1; i >= 0; i--) {
1356 View child = getChildAt(i);
1357 if (child.getVisibility() != View.GONE) {
1358 return child;
1359 }
1360 }
1361 return null;
1362 }
1363
Jorim Jaggi069cd032014-05-15 03:09:01 +02001364 /**
1365 * @return the number of children which have visibility unequal to GONE
1366 */
1367 public int getNotGoneChildCount() {
1368 int childCount = getChildCount();
1369 int count = 0;
1370 for (int i = 0; i < childCount; i++) {
1371 View child = getChildAt(i);
1372 if (child.getVisibility() != View.GONE) {
1373 count++;
1374 }
1375 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001376 if (mDismissView.willBeGone()) {
1377 count--;
1378 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02001379 if (mEmptyShadeView.willBeGone()) {
1380 count--;
1381 }
Jorim Jaggi069cd032014-05-15 03:09:01 +02001382 return count;
1383 }
1384
Selim Cinek343e6e22014-04-11 21:23:30 +02001385 private int getMaxExpandHeight(View view) {
1386 if (view instanceof ExpandableNotificationRow) {
1387 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001388 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001389 }
1390 return view.getHeight();
1391 }
1392
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001393 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001394 return mContentHeight;
1395 }
1396
1397 private void updateContentHeight() {
1398 int height = 0;
1399 for (int i = 0; i < getChildCount(); i++) {
1400 View child = getChildAt(i);
Jorim Jaggid4a57442014-04-10 02:45:55 +02001401 if (child.getVisibility() != View.GONE) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001402 if (height != 0) {
1403 // add the padding before this element
Jorim Jaggid4a57442014-04-10 02:45:55 +02001404 height += mPaddingBetweenElements;
1405 }
Selim Cineke53e6bb2015-04-13 16:14:26 -07001406 if (child instanceof ExpandableView) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001407 ExpandableView expandableView = (ExpandableView) child;
Selim Cineke53e6bb2015-04-13 16:14:26 -07001408 height += expandableView.getIntrinsicHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001409 }
Selim Cinek67b22602014-03-10 15:40:16 +01001410 }
1411 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001412 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001413 }
1414
1415 /**
1416 * Fling the scroll view
1417 *
1418 * @param velocityY The initial velocity in the Y direction. Positive
1419 * numbers mean that the finger/cursor is moving down the screen,
1420 * which means we want to scroll towards the top.
1421 */
1422 private void fling(int velocityY) {
1423 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001424 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001425
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001426 float topAmount = getCurrentOverScrollAmount(true);
1427 float bottomAmount = getCurrentOverScrollAmount(false);
1428 if (velocityY < 0 && topAmount > 0) {
1429 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001430 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001431 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001432 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001433 * mOverflingDistance + topAmount;
1434 } else if (velocityY > 0 && bottomAmount > 0) {
1435 mOwnScrollY += bottomAmount;
1436 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001437 mMaxOverScroll = Math.abs(velocityY) / 1000f
1438 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1439 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001440 } else {
1441 // it will be set once we reach the boundary
1442 mMaxOverScroll = 0.0f;
1443 }
1444 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek4195dd02014-05-19 18:16:14 +02001445 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001446
1447 postInvalidateOnAnimation();
1448 }
1449 }
1450
Selim Cinek1408eb52014-06-02 14:45:38 +02001451 /**
1452 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1453 * overScroll view (i.e QS).
1454 */
1455 private boolean shouldOverScrollFling(int initialVelocity) {
1456 float topOverScroll = getCurrentOverScrollAmount(true);
1457 return mScrolledToTopOnFirstDown
1458 && !mExpandedInThisMotion
1459 && topOverScroll > mMinTopOverScrollToEscape
1460 && initialVelocity > 0;
1461 }
1462
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001463 /**
1464 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1465 * account.
1466 *
1467 * @param qsHeight the top padding imposed by the quick settings panel
1468 * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
1469 * container
1470 * @param animate whether to animate the change
1471 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1472 * {@code qsHeight} is the final top padding
1473 */
1474 public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
1475 boolean ignoreIntrinsicPadding) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001476 float start = qsHeight - scrollY + mNotificationTopPadding;
1477 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001478 int minStackHeight = getMinStackHeight();
1479 if (stackHeight <= minStackHeight) {
1480 float overflow = minStackHeight - stackHeight;
1481 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001482 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001483 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001484 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001485 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001486 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001487 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1488 animate);
1489 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001490 }
1491
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001492 public int getNotificationTopPadding() {
1493 return mNotificationTopPadding;
1494 }
1495
1496 public int getMinStackHeight() {
Selim Cinekd83771e2014-07-04 16:45:31 +02001497 return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001498 }
1499
1500 public float getTopPaddingOverflow() {
1501 return mTopPaddingOverflow;
1502 }
1503
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001504 public int getPeekHeight() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001505 return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
1506 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001507 }
1508
Selim Cinek1408eb52014-06-02 14:45:38 +02001509 private int clampPadding(int desiredPadding) {
1510 return Math.max(desiredPadding, mIntrinsicPadding);
1511 }
1512
Selim Cinekfed1ab62014-06-17 14:10:33 -07001513 private float getRubberBandFactor(boolean onTop) {
1514 if (!onTop) {
1515 return RUBBER_BAND_FACTOR_NORMAL;
1516 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001517 if (mExpandedInThisMotion) {
1518 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
1519 } else if (mIsExpansionChanging) {
1520 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1521 } else if (mScrolledToTopOnFirstDown) {
1522 return 1.0f;
1523 }
1524 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001525 }
1526
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001527 /**
1528 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1529 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1530 * overscroll view (e.g. expand QS).
1531 */
1532 private boolean isRubberbanded(boolean onTop) {
1533 return !onTop || mExpandedInThisMotion || mIsExpansionChanging
1534 || !mScrolledToTopOnFirstDown;
1535 }
1536
Selim Cinek67b22602014-03-10 15:40:16 +01001537 private void endDrag() {
1538 setIsBeingDragged(false);
1539
1540 recycleVelocityTracker();
1541
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001542 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1543 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1544 }
1545 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1546 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1547 }
Selim Cinek67b22602014-03-10 15:40:16 +01001548 }
1549
Jorim Jaggi56306252014-07-03 00:40:09 +02001550 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1551 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1552 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1553 }
1554
Selim Cinek67b22602014-03-10 15:40:16 +01001555 @Override
1556 public boolean onInterceptTouchEvent(MotionEvent ev) {
Jorim Jaggi56306252014-07-03 00:40:09 +02001557 if (mInterceptDelegateEnabled) {
1558 transformTouchEvent(ev, this, mScrollView);
1559 if (mScrollView.onInterceptTouchEvent(ev)) {
1560 mDelegateToScrollView = true;
1561 removeLongPressCallback();
1562 return true;
1563 }
1564 transformTouchEvent(ev, mScrollView, this);
1565 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001566 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001567 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001568 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001569 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001570 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1571 }
Selim Cinek67b22602014-03-10 15:40:16 +01001572 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001573 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001574 scrollWantsIt = onInterceptTouchEventScroll(ev);
1575 }
1576 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001577 if (!mIsBeingDragged
1578 && !mExpandingNotification
1579 && !mExpandedInThisMotion
1580 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001581 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1582 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001583 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1584 }
1585
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001586 private void handleEmptySpaceClick(MotionEvent ev) {
1587 switch (ev.getActionMasked()) {
1588 case MotionEvent.ACTION_MOVE:
1589 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1590 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1591 mTouchIsClick = false;
1592 }
1593 break;
1594 case MotionEvent.ACTION_UP:
1595 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1596 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1597 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1598 }
1599 break;
1600 }
1601 }
1602
Selim Cinek1408eb52014-06-02 14:45:38 +02001603 private void initDownStates(MotionEvent ev) {
1604 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1605 mExpandedInThisMotion = false;
1606 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001607 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001608 mTouchIsClick = true;
1609 mInitialTouchX = ev.getX();
1610 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001611 }
Selim Cinek67b22602014-03-10 15:40:16 +01001612 }
1613
Christoph Studer068f5922014-04-08 17:43:07 -04001614 @Override
1615 protected void onViewRemoved(View child) {
1616 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001617 // we only call our internal methods if this is actually a removal and not just a
1618 // notification which becomes a child notification
1619 if (!isChildInGroup(child)) {
1620 onViewRemovedInternal(child);
1621 }
1622 }
1623
1624 private void onViewRemovedInternal(View child) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001625 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001626 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001627 // This is only a position change, don't do anything special
1628 return;
1629 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001630 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04001631 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001632 updateScrollStateForRemovedChild(child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07001633 boolean animationGenerated = generateRemoveAnimation(child);
1634 if (animationGenerated && !mSwipedOutViews.contains(child)) {
1635 // Add this view to an overlay in order to ensure that it will still be temporary
1636 // drawn when removed
1637 getOverlay().add(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001638 }
Selim Cinekcab4a602014-09-03 14:47:57 +02001639 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02001640
1641 // Make sure the clipRect we might have set is removed
Selim Cineka272dfe2015-02-20 18:12:28 +01001642 ((ExpandableView) child).setClipTopOptimization(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02001643 }
1644
Selim Cinekb5605e52015-02-20 18:21:41 +01001645 private boolean isChildInGroup(View child) {
1646 return child instanceof ExpandableNotificationRow
1647 && mGroupManager.isChildInGroupWithSummary(
1648 ((ExpandableNotificationRow) child).getStatusBarNotification());
1649 }
1650
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001651 /**
1652 * Generate a remove animation for a child view.
1653 *
1654 * @param child The view to generate the remove animation for.
1655 * @return Whether an animation was generated.
1656 */
1657 private boolean generateRemoveAnimation(View child) {
Selim Cinek233241f2015-06-01 06:11:19 -07001658 if (mAddedHeadsUpChildren.contains(child)) {
1659 removeChildFromHeadsUpChangeAnimations(child);
1660 mAddedHeadsUpChildren.remove(child);
1661 return false;
1662 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001663 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07001664 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02001665 // Generate Animations
1666 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001667 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001668 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02001669 } else {
1670 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001671 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001672 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02001673 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001674 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001675 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001676 }
1677
Selim Cinekffa6eb82015-05-21 12:11:12 -07001678 private void removeChildFromHeadsUpChangeAnimations(View child) {
1679 ArrayList<Pair<ExpandableNotificationRow, Boolean> > toRemove = new ArrayList<>();
1680 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
1681 ExpandableNotificationRow row = eventPair.first;
1682 if (child == row) {
1683 toRemove.add(eventPair);
1684 }
1685 }
1686 mHeadsUpChangeAnimations.removeAll(toRemove);
1687 }
1688
Selim Cinek572bbd42014-04-25 16:43:27 +02001689 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01001690 * @param child the child to query
1691 * @return whether a view is not a top level child but a child notification and that group is
1692 * not expanded
1693 */
1694 private boolean isChildInInvisibleGroup(View child) {
1695 if (child instanceof ExpandableNotificationRow) {
1696 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1697 ExpandableNotificationRow groupSummary =
1698 mGroupManager.getGroupSummary(row.getStatusBarNotification());
1699 if (groupSummary != null && groupSummary != row) {
1700 return !groupSummary.areChildrenExpanded();
1701 }
1702 }
1703 return false;
1704 }
1705
1706 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02001707 * Updates the scroll position when a child was removed
1708 *
1709 * @param removedChild the removed child
1710 */
1711 private void updateScrollStateForRemovedChild(View removedChild) {
1712 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinekd7c4e002014-07-04 18:36:42 +02001713 int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
Selim Cinek572bbd42014-04-25 16:43:27 +02001714 int endPosition = startingPosition + childHeight;
1715 if (endPosition <= mOwnScrollY) {
1716 // This child is fully scrolled of the top, so we have to deduct its height from the
1717 // scrollPosition
1718 mOwnScrollY -= childHeight;
1719 } else if (startingPosition < mOwnScrollY) {
1720 // This child is currently being scrolled into, set the scroll position to the start of
1721 // this child
1722 mOwnScrollY = startingPosition;
1723 }
1724 }
1725
Selim Cinekd7c4e002014-07-04 18:36:42 +02001726 private int getIntrinsicHeight(View view) {
1727 if (view instanceof ExpandableView) {
1728 ExpandableView expandableView = (ExpandableView) view;
1729 return expandableView.getIntrinsicHeight();
1730 }
1731 return view.getHeight();
1732 }
1733
Selim Cinek572bbd42014-04-25 16:43:27 +02001734 private int getPositionInLinearLayout(View requestedChild) {
1735 int position = 0;
1736 for (int i = 0; i < getChildCount(); i++) {
1737 View child = getChildAt(i);
1738 if (child == requestedChild) {
1739 return position;
1740 }
1741 if (child.getVisibility() != View.GONE) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02001742 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001743 if (i < getChildCount()-1) {
1744 position += mPaddingBetweenElements;
1745 }
1746 }
1747 }
1748 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02001749 }
1750
1751 @Override
1752 protected void onViewAdded(View child) {
1753 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001754 onViewAddedInternal(child);
1755 }
1756
1757 private void onViewAddedInternal(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02001758 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02001759 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00001760 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02001761 updateAnimationState(child);
Selim Cinek7d5f3742014-11-07 18:07:49 +01001762 if (canChildBeDismissed(child)) {
1763 // Make sure the dismissButton is visible and not in the animated state.
1764 // We need to do this to avoid a race where a clearable notification is added after the
1765 // dismiss animation is finished
1766 mDismissView.showClearButton();
1767 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001768 }
1769
Selim Cinekb5605e52015-02-20 18:21:41 +01001770 public void notifyGroupChildRemoved(View row) {
1771 onViewRemovedInternal(row);
1772 }
1773
1774 public void notifyGroupChildAdded(View row) {
1775 onViewAddedInternal(row);
1776 }
1777
Jorim Jaggi75c95042014-05-16 19:09:59 +02001778 public void setAnimationsEnabled(boolean animationsEnabled) {
1779 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02001780 updateNotificationAnimationStates();
1781 }
1782
1783 private void updateNotificationAnimationStates() {
Selim Cinek8d490d42015-04-10 00:05:50 -07001784 boolean running = mAnimationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02001785 int childCount = getChildCount();
1786 for (int i = 0; i < childCount; i++) {
1787 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07001788 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02001789 updateAnimationState(running, child);
1790 }
1791 }
1792
Selim Cinek51ae05d2014-09-09 15:51:38 +02001793 private void updateAnimationState(View child) {
Selim Cinek8d490d42015-04-10 00:05:50 -07001794 updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02001795 }
1796
1797
Selim Cinekcab4a602014-09-03 14:47:57 +02001798 private void updateAnimationState(boolean running, View child) {
1799 if (child instanceof ExpandableNotificationRow) {
1800 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1801 row.setIconAnimationRunning(running);
1802 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02001803 }
1804
1805 public boolean isAddOrRemoveAnimationPending() {
1806 return mNeedsAnimation
1807 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1808 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001809 /**
1810 * Generate an animation for an added child view.
1811 *
1812 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00001813 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001814 */
Jorim Jaggif6411742014-08-05 17:10:43 +00001815 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001816 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001817 // Generate Animations
1818 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00001819 if (fromMoreCard) {
1820 mFromMoreCardAdditions.add(child);
1821 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001822 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02001823 }
Selim Cineka59ecc32015-04-07 10:51:49 -07001824 if (isHeadsUp(child)) {
1825 mAddedHeadsUpChildren.add(child);
1826 mChildrenToAddAnimated.remove(child);
1827 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001828 }
1829
1830 /**
1831 * Change the position of child to a new location
1832 *
1833 * @param child the view to change the position for
1834 * @param newIndex the new index
1835 */
1836 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04001837 int currentIndex = indexOfChild(child);
1838 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001839 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02001840 removeView(child);
1841 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001842 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04001843 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001844 mChildrenChangingPositions.add(child);
1845 mNeedsAnimation = true;
1846 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001847 }
1848 }
1849
Selim Cinekf4c19962014-05-01 21:55:31 +02001850 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001851 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001852 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001853 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001854 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001855 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001856 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1857 mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001858 mAnimationEvents.clear();
Selim Cinekf4c19962014-05-01 21:55:31 +02001859 } else {
1860 applyCurrentState();
1861 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001862 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02001863 }
1864
1865 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07001866 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02001867 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001868 generateChildAdditionEvents();
1869 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02001870 generateSnapBackEvents();
1871 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001872 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001873 generateActivateEvent();
1874 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02001875 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04001876 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001877 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02001878 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01001879 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02001880 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001881 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001882 }
1883
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001884 private void generateHeadsUpAnimationEvents() {
1885 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001886 ExpandableNotificationRow row = eventPair.first;
1887 boolean isHeadsUp = eventPair.second;
1888 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
1889 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07001890 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07001891 if (!mIsExpanded && !isHeadsUp) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001892 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinek131c1e22015-05-11 19:04:49 -07001893 } else if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
1894 if (pinnedAndClosed || shouldHunAppearFromBottom(row)) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001895 // Our custom add animation
1896 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
1897 } else {
1898 // Normal add animation
1899 type = AnimationEvent.ANIMATION_TYPE_ADD;
1900 }
Selim Cinek131c1e22015-05-11 19:04:49 -07001901 onBottom = !pinnedAndClosed;
Selim Cineka59ecc32015-04-07 10:51:49 -07001902 }
1903 AnimationEvent event = new AnimationEvent(row, type);
1904 event.headsUpFromBottom = onBottom;
1905 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001906 }
1907 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07001908 mAddedHeadsUpChildren.clear();
1909 }
1910
1911 private boolean shouldHunAppearFromBottom(ExpandableNotificationRow row) {
1912 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
1913 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
1914 return false;
1915 }
1916 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001917 }
1918
Selim Cinekb5605e52015-02-20 18:21:41 +01001919 private void generateGroupExpansionEvent() {
1920 // Generate a group expansion/collapsing event if there is such a group at all
1921 if (mExpandedGroupView != null) {
1922 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
1923 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
1924 mExpandedGroupView = null;
1925 }
1926 }
1927
Selim Cineka5e211b2014-08-11 17:35:48 +02001928 private void generateViewResizeEvent() {
1929 if (mNeedViewResizeAnimation) {
1930 mAnimationEvents.add(
1931 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
1932 }
1933 mNeedViewResizeAnimation = false;
1934 }
1935
Selim Cinekeb973562014-05-02 17:07:49 +02001936 private void generateSnapBackEvents() {
1937 for (View child : mSnappedBackChildren) {
1938 mAnimationEvents.add(new AnimationEvent(child,
1939 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1940 }
1941 mSnappedBackChildren.clear();
1942 }
1943
1944 private void generateDragEvents() {
1945 for (View child : mDragAnimPendingChildren) {
1946 mAnimationEvents.add(new AnimationEvent(child,
1947 AnimationEvent.ANIMATION_TYPE_START_DRAG));
1948 }
1949 mDragAnimPendingChildren.clear();
1950 }
1951
Selim Cinek572bbd42014-04-25 16:43:27 +02001952 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001953 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001954 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1955 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001956 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1957 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001958 AnimationEvent event = new AnimationEvent(child, animationType);
1959
1960 // we need to know the view after this one
1961 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1962 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02001963 }
1964 mSwipedOutViews.clear();
1965 mChildrenToRemoveAnimated.clear();
1966 }
1967
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001968 private void generatePositionChangeEvents() {
1969 for (View child : mChildrenChangingPositions) {
1970 mAnimationEvents.add(new AnimationEvent(child,
1971 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1972 }
1973 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01001974 if (mGenerateChildOrderChangedEvent) {
1975 mAnimationEvents.add(new AnimationEvent(null,
1976 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1977 mGenerateChildOrderChangedEvent = false;
1978 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001979 }
1980
Selim Cinek572bbd42014-04-25 16:43:27 +02001981 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001982 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001983 if (mFromMoreCardAdditions.contains(child)) {
1984 mAnimationEvents.add(new AnimationEvent(child,
1985 AnimationEvent.ANIMATION_TYPE_ADD,
1986 StackStateAnimator.ANIMATION_DURATION_STANDARD));
1987 } else {
1988 mAnimationEvents.add(new AnimationEvent(child,
1989 AnimationEvent.ANIMATION_TYPE_ADD));
1990 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001991 }
1992 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001993 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04001994 }
1995
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001996 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02001997 if (mTopPaddingNeedsAnimation) {
1998 mAnimationEvents.add(
1999 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2000 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002001 mTopPaddingNeedsAnimation = false;
2002 }
2003
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002004 private void generateActivateEvent() {
2005 if (mActivateNeedsAnimation) {
2006 mAnimationEvents.add(
2007 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2008 }
2009 mActivateNeedsAnimation = false;
2010 }
2011
Selim Cinekd9acca52014-09-01 22:33:25 +02002012 private void generateAnimateEverythingEvent() {
2013 if (mEverythingNeedsAnimation) {
2014 mAnimationEvents.add(
2015 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2016 }
2017 mEverythingNeedsAnimation = false;
2018 }
2019
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002020 private void generateDimmedEvent() {
2021 if (mDimmedNeedsAnimation) {
2022 mAnimationEvents.add(
2023 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2024 }
2025 mDimmedNeedsAnimation = false;
2026 }
2027
Jorim Jaggiae441282014-08-01 02:45:18 +02002028 private void generateHideSensitiveEvent() {
2029 if (mHideSensitiveNeedsAnimation) {
2030 mAnimationEvents.add(
2031 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2032 }
2033 mHideSensitiveNeedsAnimation = false;
2034 }
2035
John Spurlockbf370992014-06-17 13:58:31 -04002036 private void generateDarkEvent() {
2037 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002038 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2039 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2040 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04002041 }
2042 mDarkNeedsAnimation = false;
2043 }
2044
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002045 private void generateGoToFullShadeEvent() {
2046 if (mGoToFullShadeNeedsAnimation) {
2047 mAnimationEvents.add(
2048 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2049 }
2050 mGoToFullShadeNeedsAnimation = false;
2051 }
2052
Selim Cinek67b22602014-03-10 15:40:16 +01002053 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002054 if (!isScrollingEnabled()) {
2055 return false;
2056 }
Selim Cinek67b22602014-03-10 15:40:16 +01002057 /*
2058 * This method JUST determines whether we want to intercept the motion.
2059 * If we return true, onMotionEvent will be called and we do the actual
2060 * scrolling there.
2061 */
2062
2063 /*
2064 * Shortcut the most recurring case: the user is in the dragging
2065 * state and he is moving his finger. We want to intercept this
2066 * motion.
2067 */
2068 final int action = ev.getAction();
2069 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2070 return true;
2071 }
2072
Selim Cinek67b22602014-03-10 15:40:16 +01002073 switch (action & MotionEvent.ACTION_MASK) {
2074 case MotionEvent.ACTION_MOVE: {
2075 /*
2076 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
2077 * whether the user has moved far enough from his original down touch.
2078 */
2079
2080 /*
2081 * Locally do absolute value. mLastMotionY is set to the y value
2082 * of the down event.
2083 */
2084 final int activePointerId = mActivePointerId;
2085 if (activePointerId == INVALID_POINTER) {
2086 // If we don't have a valid id, the touch down wasn't on content.
2087 break;
2088 }
2089
2090 final int pointerIndex = ev.findPointerIndex(activePointerId);
2091 if (pointerIndex == -1) {
2092 Log.e(TAG, "Invalid pointerId=" + activePointerId
2093 + " in onInterceptTouchEvent");
2094 break;
2095 }
2096
2097 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002098 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002099 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002100 final int xDiff = Math.abs(x - mDownX);
2101 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002102 setIsBeingDragged(true);
2103 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002104 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002105 initVelocityTrackerIfNotExists();
2106 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002107 }
2108 break;
2109 }
2110
2111 case MotionEvent.ACTION_DOWN: {
2112 final int y = (int) ev.getY();
2113 if (getChildAtPosition(ev.getX(), y) == null) {
2114 setIsBeingDragged(false);
2115 recycleVelocityTracker();
2116 break;
2117 }
2118
2119 /*
2120 * Remember location of down touch.
2121 * ACTION_DOWN always refers to pointer index 0.
2122 */
2123 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002124 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002125 mActivePointerId = ev.getPointerId(0);
Selim Cinek1408eb52014-06-02 14:45:38 +02002126 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002127
2128 initOrResetVelocityTracker();
2129 mVelocityTracker.addMovement(ev);
2130 /*
2131 * If being flinged and user touches the screen, initiate drag;
2132 * otherwise don't. mScroller.isFinished should be false when
2133 * being flinged.
2134 */
2135 boolean isBeingDragged = !mScroller.isFinished();
2136 setIsBeingDragged(isBeingDragged);
2137 break;
2138 }
2139
2140 case MotionEvent.ACTION_CANCEL:
2141 case MotionEvent.ACTION_UP:
2142 /* Release the drag */
2143 setIsBeingDragged(false);
2144 mActivePointerId = INVALID_POINTER;
2145 recycleVelocityTracker();
2146 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2147 postInvalidateOnAnimation();
2148 }
2149 break;
2150 case MotionEvent.ACTION_POINTER_UP:
2151 onSecondaryPointerUp(ev);
2152 break;
2153 }
2154
2155 /*
2156 * The only time we want to intercept motion events is if we are in the
2157 * drag mode.
2158 */
2159 return mIsBeingDragged;
2160 }
2161
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002162 /**
2163 * @return Whether the specified motion event is actually happening over the content.
2164 */
2165 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002166 return isInContentBounds(event.getY());
2167 }
2168
2169 /**
2170 * @return Whether a y coordinate is inside the content.
2171 */
2172 public boolean isInContentBounds(float y) {
2173 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002174 }
2175
Selim Cinek67b22602014-03-10 15:40:16 +01002176 private void setIsBeingDragged(boolean isDragged) {
2177 mIsBeingDragged = isDragged;
2178 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002179 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002180 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002181 }
2182 }
2183
2184 @Override
2185 public void onWindowFocusChanged(boolean hasWindowFocus) {
2186 super.onWindowFocusChanged(hasWindowFocus);
2187 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002188 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002189 }
2190 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002191
Selim Cinek1408eb52014-06-02 14:45:38 +02002192 public void removeLongPressCallback() {
2193 mSwipeHelper.removeLongPressCallback();
2194 }
2195
Selim Cinekfab078b2014-03-27 22:45:58 +01002196 @Override
2197 public boolean isScrolledToTop() {
2198 return mOwnScrollY == 0;
2199 }
2200
2201 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002202 public boolean isScrolledToBottom() {
2203 return mOwnScrollY >= getScrollRange();
2204 }
2205
2206 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002207 public View getHostView() {
2208 return this;
2209 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002210
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002211 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002212 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002213 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002214 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02002215 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002216 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002217 }
2218 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002219 }
2220
Selim Cinek1685e632014-04-08 02:27:49 +02002221 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002222 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002223 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2224 }
2225
2226 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002227 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02002228 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002229 if (!mIsExpanded) {
2230 mOwnScrollY = 0;
Selim Cinekf336f4c2014-11-12 16:58:16 +01002231
2232 // lets make sure nothing is in the overlay anymore
2233 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002234 }
Selim Cinek1685e632014-04-08 02:27:49 +02002235 }
2236
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002237 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002238 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002239 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002240 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002241 if (changed) {
2242 updateNotificationAnimationStates();
2243 }
Selim Cinek1685e632014-04-08 02:27:49 +02002244 }
2245
Jorim Jaggibe565df2014-04-28 17:51:23 +02002246 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002247 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002248 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002249 updateScrollPositionOnExpandInBottom(view);
2250 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002251 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002252 if (needsAnimation) {
2253 requestAnimationOnViewResize();
2254 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002255 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002256 }
2257
Selim Cineka5e211b2014-08-11 17:35:48 +02002258 @Override
2259 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002260 if (mIsExpanded && mAnimationsEnabled) {
2261 mRequestViewResizeAnimationOnLayout = true;
2262 }
Selim Cinek31094df2014-08-14 19:28:15 +02002263 mStackScrollAlgorithm.onReset(view);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002264 updateAnimationState(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002265 }
2266
Selim Cinekf7a14c02014-07-07 14:01:46 +02002267 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2268 if (view instanceof ExpandableNotificationRow) {
2269 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002270 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02002271 // We are actually expanding this view
2272 float endPosition = row.getTranslationY() + row.getActualHeight();
2273 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
Selim Cinekcb9400a2015-06-03 16:56:13 +02002274 mBottomStackSlowDownHeight + (int) mStackTranslation;
Selim Cinekf7a14c02014-07-07 14:01:46 +02002275 if (endPosition > stackEnd) {
2276 mOwnScrollY += endPosition - stackEnd;
2277 mDisallowScrollingInThisMotion = true;
2278 }
2279 }
2280 }
2281 }
2282
Jorim Jaggibe565df2014-04-28 17:51:23 +02002283 public void setOnHeightChangedListener(
2284 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2285 this.mOnHeightChangedListener = mOnHeightChangedListener;
2286 }
2287
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002288 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2289 mOnEmptySpaceClickListener = listener;
2290 }
2291
Selim Cinek572bbd42014-04-25 16:43:27 +02002292 public void onChildAnimationFinished() {
Selim Cinek319bdc42014-05-01 23:01:58 +02002293 requestChildrenUpdate();
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002294 for (Runnable runnable : mAnimationFinishedRunnables) {
2295 runnable.run();
2296 }
2297 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002298 }
2299
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002300 /**
2301 * See {@link AmbientState#setDimmed}.
2302 */
2303 public void setDimmed(boolean dimmed, boolean animate) {
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002304 mStackScrollAlgorithm.setDimmed(dimmed);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002305 mAmbientState.setDimmed(dimmed);
Selim Cineka5eaa602014-05-12 21:27:47 +02002306 updatePadding(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002307 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002308 mDimmedNeedsAnimation = true;
2309 mNeedsAnimation = true;
2310 }
2311 requestChildrenUpdate();
2312 }
2313
Jorim Jaggiae441282014-08-01 02:45:18 +02002314 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2315 if (hideSensitive != mAmbientState.isHideSensitive()) {
2316 int childCount = getChildCount();
2317 for (int i = 0; i < childCount; i++) {
2318 ExpandableView v = (ExpandableView) getChildAt(i);
2319 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2320 }
2321 mAmbientState.setHideSensitive(hideSensitive);
2322 if (animate && mAnimationsEnabled) {
2323 mHideSensitiveNeedsAnimation = true;
2324 mNeedsAnimation = true;
2325 }
2326 requestChildrenUpdate();
2327 }
2328 }
2329
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002330 /**
2331 * See {@link AmbientState#setActivatedChild}.
2332 */
Selim Cineka32ab602014-06-11 15:06:01 +02002333 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002334 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002335 if (mAnimationsEnabled) {
2336 mActivateNeedsAnimation = true;
2337 mNeedsAnimation = true;
2338 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002339 requestChildrenUpdate();
2340 }
2341
Selim Cineka32ab602014-06-11 15:06:01 +02002342 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002343 return mAmbientState.getActivatedChild();
2344 }
2345
Selim Cinek572bbd42014-04-25 16:43:27 +02002346 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002347 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002348 if (mListener != null) {
2349 mListener.onChildLocationsChanged(this);
2350 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002351 }
2352
Selim Cinekc27437b2014-05-14 10:23:33 +02002353 public void setSpeedBumpView(SpeedBumpView speedBumpView) {
2354 mSpeedBumpView = speedBumpView;
2355 addView(speedBumpView);
2356 }
2357
2358 private void updateSpeedBump(boolean visible) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002359 boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
2360 if (visible != notGoneBefore) {
2361 int newVisibility = visible ? VISIBLE : GONE;
Selim Cinekc27437b2014-05-14 10:23:33 +02002362 mSpeedBumpView.setVisibility(newVisibility);
2363 if (visible) {
Selim Cinekc27437b2014-05-14 10:23:33 +02002364 // Make invisible to ensure that the appear animation is played.
2365 mSpeedBumpView.setInvisible();
Selim Cinekc27437b2014-05-14 10:23:33 +02002366 } else {
Jorim Jaggi8b730062014-07-31 22:19:52 +02002367 // TODO: This doesn't really work, because the view is already set to GONE above.
Selim Cinekc27437b2014-05-14 10:23:33 +02002368 generateRemoveAnimation(mSpeedBumpView);
2369 }
2370 }
2371 }
2372
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002373 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002374 updateSpeedBump(true /* visibility */);
2375 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002376 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002377 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002378 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002379 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002380 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002381 }
2382
Selim Cinek1408eb52014-06-02 14:45:38 +02002383 public void cancelExpandHelper() {
2384 mExpandHelper.cancel();
2385 }
2386
2387 public void setIntrinsicPadding(int intrinsicPadding) {
2388 mIntrinsicPadding = intrinsicPadding;
2389 }
2390
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002391 public int getIntrinsicPadding() {
2392 return mIntrinsicPadding;
2393 }
2394
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002395 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002396 * @return the y position of the first notification
2397 */
2398 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07002399 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02002400 }
2401
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002402 @Override
2403 public boolean shouldDelayChildPressedState() {
2404 return true;
2405 }
2406
Jorim Jaggi457cc352014-06-02 22:47:42 +02002407 /**
John Spurlockbf370992014-06-17 13:58:31 -04002408 * See {@link AmbientState#setDark}.
2409 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002410 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04002411 mAmbientState.setDark(dark);
2412 if (animate && mAnimationsEnabled) {
2413 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002414 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04002415 mNeedsAnimation = true;
2416 }
2417 requestChildrenUpdate();
2418 }
2419
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002420 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2421 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2422 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2423 }
2424 if (screenLocation.y > getBottomMostNotificationBottom()) {
2425 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2426 }
2427 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2428 if (child != null) {
2429 return getNotGoneIndex(child);
2430 } else {
2431 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2432 }
2433 }
2434
2435 private int getNotGoneIndex(View child) {
2436 int count = getChildCount();
2437 int notGoneIndex = 0;
2438 for (int i = 0; i < count; i++) {
2439 View v = getChildAt(i);
2440 if (child == v) {
2441 return notGoneIndex;
2442 }
2443 if (v.getVisibility() != View.GONE) {
2444 notGoneIndex++;
2445 }
2446 }
2447 return -1;
2448 }
2449
Dan Sandlereceda3d2014-07-21 15:35:01 -04002450 public void setDismissView(DismissView dismissView) {
2451 mDismissView = dismissView;
2452 addView(mDismissView);
2453 }
2454
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002455 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2456 mEmptyShadeView = emptyShadeView;
2457 addView(mEmptyShadeView);
2458 }
2459
2460 public void updateEmptyShadeView(boolean visible) {
2461 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2462 int newVisibility = visible ? VISIBLE : GONE;
2463 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002464 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002465 if (mEmptyShadeView.willBeGone()) {
2466 mEmptyShadeView.cancelAnimation();
2467 } else {
2468 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002469 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002470 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002471 mEmptyShadeView.setWillBeGone(false);
2472 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002473 notifyHeightChangeListener(mDismissView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002474 } else {
Selim Cinek20867102014-12-10 17:09:17 +01002475 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002476 @Override
2477 public void run() {
2478 mEmptyShadeView.setVisibility(GONE);
2479 mEmptyShadeView.setWillBeGone(false);
2480 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002481 notifyHeightChangeListener(mDismissView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002482 }
Selim Cinek20867102014-12-10 17:09:17 +01002483 };
2484 if (mAnimationsEnabled) {
2485 mEmptyShadeView.setWillBeGone(true);
2486 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
2487 } else {
2488 mEmptyShadeView.setInvisible();
2489 onFinishedRunnable.run();
2490 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002491 }
2492 }
2493 }
2494
Dan Sandlereceda3d2014-07-21 15:35:01 -04002495 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002496 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002497 int newVisibility = visible ? VISIBLE : GONE;
2498 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002499 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002500 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002501 mDismissView.cancelAnimation();
2502 } else {
2503 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002504 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002505 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002506 mDismissView.setWillBeGone(false);
2507 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002508 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002509 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01002510 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002511 @Override
2512 public void run() {
2513 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002514 mDismissView.setWillBeGone(false);
2515 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002516 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002517 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01002518 };
Selim Cinek20867102014-12-10 17:09:17 +01002519 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01002520 mDismissView.setWillBeGone(true);
2521 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
2522 } else {
2523 dimissHideFinishRunnable.run();
2524 mDismissView.showClearButton();
2525 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002526 }
2527 }
2528 }
2529
2530 public void setDismissAllInProgress(boolean dismissAllInProgress) {
2531 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek7d5f3742014-11-07 18:07:49 +01002532 mDismissView.setDismissAllInProgress(dismissAllInProgress);
Selim Cineka272dfe2015-02-20 18:12:28 +01002533 if (dismissAllInProgress) {
2534 disableClipOptimization();
2535 }
2536 }
2537
2538 private void disableClipOptimization() {
2539 final int count = getChildCount();
2540 for (int i = 0; i < count; i++) {
2541 ExpandableView child = (ExpandableView) getChildAt(i);
2542 if (child.getVisibility() == GONE) {
2543 continue;
2544 }
2545 child.setClipTopOptimization(0);
2546 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002547 }
2548
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002549 public boolean isDismissViewNotGone() {
2550 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2551 }
2552
2553 public boolean isDismissViewVisible() {
2554 return mDismissView.isVisible();
2555 }
2556
2557 public int getDismissViewHeight() {
Jorim Jaggi1d49ec92014-08-25 18:44:01 +02002558 int height = mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2559
2560 // Hack: Accommodate for additional distance when we only have one notification and the
2561 // dismiss all button.
2562 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
2563 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
2564 height += mCollapseSecondCardPadding;
2565 }
2566 return height;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002567 }
2568
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01002569 public int getEmptyShadeViewHeight() {
2570 return mEmptyShadeView.getHeight();
2571 }
2572
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002573 public float getBottomMostNotificationBottom() {
2574 final int count = getChildCount();
2575 float max = 0;
2576 for (int childIdx = 0; childIdx < count; childIdx++) {
2577 ExpandableView child = (ExpandableView) getChildAt(childIdx);
2578 if (child.getVisibility() == GONE) {
2579 continue;
2580 }
2581 float bottom = child.getTranslationY() + child.getActualHeight();
2582 if (bottom > max) {
2583 max = bottom;
2584 }
2585 }
Selim Cinekd2281152015-04-10 14:37:46 -07002586 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002587 }
2588
John Spurlockbf370992014-06-17 13:58:31 -04002589 /**
Selim Cinek3afd00e2014-08-11 22:32:57 +02002590 * @param qsMinHeight The minimum height of the quick settings including padding
2591 * See {@link StackScrollAlgorithm#updateIsSmallScreen}.
2592 */
2593 public void updateIsSmallScreen(int qsMinHeight) {
2594 mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight);
2595 }
2596
Selim Cinek19c8c702014-08-25 22:09:19 +02002597 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
2598 this.mPhoneStatusBar = phoneStatusBar;
2599 }
2600
Selim Cinekb5605e52015-02-20 18:21:41 +01002601 public void setGroupManager(NotificationGroupManager groupManager) {
2602 this.mGroupManager = groupManager;
2603 }
2604
Selim Cinekd9acca52014-09-01 22:33:25 +02002605 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01002606 requestAnimateEverything();
2607 }
2608
2609 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02002610 if (mIsExpanded && mAnimationsEnabled) {
2611 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01002612 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02002613 requestChildrenUpdate();
2614 }
2615 }
2616
Selim Cinek04fb2582015-06-02 19:58:09 +02002617 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01002618 int childCount = getChildCount();
2619 for (int i = childCount - 1; i >= 0; i--) {
2620 ExpandableView child = (ExpandableView) getChildAt(i);
2621 if (child.getVisibility() != View.GONE) {
2622 float childTop = child.getY();
2623 if (childTop > touchY) {
2624 // we are above a notification entirely let's abort
2625 return false;
2626 }
2627 boolean belowChild = touchY > childTop + child.getActualHeight();
2628 if (child == mDismissView) {
2629 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
2630 touchY - childTop)) {
2631 // We clicked on the dismiss button
2632 return false;
2633 }
2634 } else if (child == mEmptyShadeView) {
2635 // We arrived at the empty shade view, for which we accept all clicks
2636 return true;
2637 } else if (!belowChild){
2638 // We are on a child
2639 return false;
2640 }
2641 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002642 }
Selim Cinek04fb2582015-06-02 19:58:09 +02002643 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002644 }
2645
Selim Cinekb5605e52015-02-20 18:21:41 +01002646 private void updateExpandButtons() {
2647 for (int i = 0; i < getChildCount(); i++) {
2648 View child = getChildAt(i);
2649 if (child instanceof ExpandableNotificationRow) {
2650 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2651 row.updateExpandButton();
2652 }
2653 }
2654 }
2655
2656 @Override
2657 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
2658 boolean animated = mAnimationsEnabled && mIsExpanded;
2659 if (animated) {
2660 mExpandedGroupView = changedRow;
2661 mNeedsAnimation = true;
2662 }
2663 changedRow.setChildrenExpanded(expanded, animated);
2664 onHeightChanged(changedRow, false /* needsAnimation */);
2665 }
2666
2667 @Override
2668 public void onGroupsProhibitedChanged() {
2669 updateExpandButtons();
2670 }
2671
2672 @Override
2673 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
2674 for (NotificationData.Entry entry : group.children) {
2675 ExpandableNotificationRow row = entry.row;
2676 if (indexOfChild(row) != -1) {
2677 removeView(row);
2678 group.summary.row.addChildNotification(row);
2679 }
2680 }
2681 }
2682
2683 public void generateChildOrderChangedEvent() {
2684 if (mIsExpanded && mAnimationsEnabled) {
2685 mGenerateChildOrderChangedEvent = true;
2686 mNeedsAnimation = true;
2687 requestChildrenUpdate();
2688 }
2689 }
2690
Selim Cinek684a4422015-04-15 16:18:39 -07002691 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002692 mAnimationFinishedRunnables.add(runnable);
2693 }
2694
2695 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
2696 mHeadsUpManager = headsUpManager;
2697 mAmbientState.setHeadsUpManager(headsUpManager);
Selim Cinekd2281152015-04-10 14:37:46 -07002698 mStackScrollAlgorithm.setHeadsUpManager(headsUpManager);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002699 }
2700
2701 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
2702 if (mAnimationsEnabled) {
2703 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
2704 mNeedsAnimation = true;
2705 requestChildrenUpdate();
2706 }
2707 }
2708
2709 public void setShadeExpanded(boolean shadeExpanded) {
2710 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07002711 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002712 }
2713
Selim Cineka59ecc32015-04-07 10:51:49 -07002714 /**
2715 * Set the boundary for the bottom heads up position. The heads up will always be above this
2716 * position.
2717 *
2718 * @param height the height of the screen
2719 * @param bottomBarHeight the height of the bar on the bottom
2720 */
2721 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
2722 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
2723 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002724 requestChildrenUpdate();
2725 }
2726
2727 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
2728 mTrackingHeadsUp = trackingHeadsUp;
2729 }
2730
Selim Cinekaac93252015-04-14 20:04:12 -07002731 public void setScrimController(ScrimController scrimController) {
2732 mScrimController = scrimController;
2733 }
2734
Selim Cinekbbc580b2015-06-03 14:11:03 +02002735 public void forceNoOverlappingRendering(boolean force) {
2736 mForceNoOverlappingRendering = force;
2737 }
2738
2739 @Override
2740 public boolean hasOverlappingRendering() {
2741 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
2742 }
2743
Selim Cinek3afd00e2014-08-11 22:32:57 +02002744 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002745 * A listener that is notified when some child locations might have changed.
2746 */
2747 public interface OnChildLocationsChangedListener {
2748 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2749 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002750
Jorim Jaggi290600a2014-05-30 17:02:20 +02002751 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002752 * A listener that is notified when the empty space below the notifications is clicked on
2753 */
2754 public interface OnEmptySpaceClickListener {
2755 public void onEmptySpaceClicked(float x, float y);
2756 }
2757
2758 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02002759 * A listener that gets notified when the overscroll at the top has changed.
2760 */
2761 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002762
2763 /**
2764 * Notifies a listener that the overscroll has changed.
2765 *
2766 * @param amount the amount of overscroll, in pixels
2767 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2768 * unrubberbanded motion to directly expand overscroll view (e.g expand
2769 * QS)
2770 */
2771 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02002772
2773 /**
2774 * Notify a listener that the scroller wants to escape from the scrolling motion and
2775 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2776 *
2777 * @param velocity The velocity that the Scroller had when over flinging
2778 * @param open Should the fling open or close the overscroll view.
2779 */
2780 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002781 }
2782
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002783 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02002784
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002785 static AnimationFilter[] FILTERS = new AnimationFilter[] {
2786
2787 // ANIMATION_TYPE_ADD
2788 new AnimationFilter()
2789 .animateAlpha()
2790 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002791 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002792 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002793 .animateZ()
2794 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002795
2796 // ANIMATION_TYPE_REMOVE
2797 new AnimationFilter()
2798 .animateAlpha()
2799 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002800 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002801 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002802 .animateZ()
2803 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002804
2805 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2806 new AnimationFilter()
2807 .animateAlpha()
2808 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002809 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002810 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002811 .animateZ()
2812 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002813
2814 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2815 new AnimationFilter()
2816 .animateAlpha()
2817 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002818 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002819 .animateY()
2820 .animateDimmed()
2821 .animateScale()
2822 .animateZ(),
2823
2824 // ANIMATION_TYPE_START_DRAG
2825 new AnimationFilter()
2826 .animateAlpha(),
2827
2828 // ANIMATION_TYPE_SNAP_BACK
2829 new AnimationFilter()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002830 .animateAlpha()
2831 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002832
2833 // ANIMATION_TYPE_ACTIVATED_CHILD
2834 new AnimationFilter()
2835 .animateScale()
2836 .animateAlpha(),
2837
2838 // ANIMATION_TYPE_DIMMED
2839 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002840 .animateY()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002841 .animateScale()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002842 .animateDimmed(),
2843
2844 // ANIMATION_TYPE_CHANGE_POSITION
2845 new AnimationFilter()
2846 .animateAlpha()
2847 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002848 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002849 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04002850 .animateZ(),
2851
2852 // ANIMATION_TYPE_DARK
2853 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01002854 .animateDark()
2855 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002856
2857 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2858 new AnimationFilter()
2859 .animateAlpha()
2860 .animateHeight()
2861 .animateTopInset()
2862 .animateY()
2863 .animateDimmed()
2864 .animateScale()
Jorim Jaggiae441282014-08-01 02:45:18 +02002865 .animateZ()
2866 .hasDelays(),
2867
2868 // ANIMATION_TYPE_HIDE_SENSITIVE
2869 new AnimationFilter()
2870 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02002871
2872 // ANIMATION_TYPE_VIEW_RESIZE
2873 new AnimationFilter()
2874 .animateAlpha()
2875 .animateHeight()
2876 .animateTopInset()
2877 .animateY()
2878 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02002879
Selim Cinekb5605e52015-02-20 18:21:41 +01002880 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
2881 new AnimationFilter()
2882 .animateAlpha()
2883 .animateHeight()
2884 .animateTopInset()
2885 .animateY()
2886 .animateZ(),
2887
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002888 // ANIMATION_TYPE_HEADS_UP_APPEAR
2889 new AnimationFilter()
2890 .animateAlpha()
2891 .animateHeight()
2892 .animateTopInset()
2893 .animateY()
2894 .animateZ(),
2895
2896 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
2897 new AnimationFilter()
2898 .animateAlpha()
2899 .animateHeight()
2900 .animateTopInset()
2901 .animateY()
2902 .animateZ(),
2903
Selim Cineka59ecc32015-04-07 10:51:49 -07002904 // ANIMATION_TYPE_HEADS_UP_OTHER
2905 new AnimationFilter()
2906 .animateAlpha()
2907 .animateHeight()
2908 .animateTopInset()
2909 .animateY()
2910 .animateZ(),
2911
Selim Cinekd9acca52014-09-01 22:33:25 +02002912 // ANIMATION_TYPE_EVERYTHING
2913 new AnimationFilter()
2914 .animateAlpha()
2915 .animateDark()
2916 .animateScale()
2917 .animateDimmed()
2918 .animateHideSensitive()
2919 .animateHeight()
2920 .animateTopInset()
2921 .animateY()
2922 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002923 };
2924
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002925 static int[] LENGTHS = new int[] {
2926
2927 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002928 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002929
2930 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002931 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002932
2933 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2934 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2935
2936 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2937 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2938
2939 // ANIMATION_TYPE_START_DRAG
2940 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2941
2942 // ANIMATION_TYPE_SNAP_BACK
2943 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2944
2945 // ANIMATION_TYPE_ACTIVATED_CHILD
2946 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2947
2948 // ANIMATION_TYPE_DIMMED
2949 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002950
2951 // ANIMATION_TYPE_CHANGE_POSITION
2952 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04002953
2954 // ANIMATION_TYPE_DARK
2955 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002956
2957 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2958 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02002959
2960 // ANIMATION_TYPE_HIDE_SENSITIVE
2961 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02002962
2963 // ANIMATION_TYPE_VIEW_RESIZE
2964 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02002965
Selim Cinekb5605e52015-02-20 18:21:41 +01002966 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
2967 StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
2968
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002969 // ANIMATION_TYPE_HEADS_UP_APPEAR
2970 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
2971
2972 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
2973 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
2974
Selim Cineka59ecc32015-04-07 10:51:49 -07002975 // ANIMATION_TYPE_HEADS_UP_OTHER
2976 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2977
Selim Cinekd9acca52014-09-01 22:33:25 +02002978 // ANIMATION_TYPE_EVERYTHING
2979 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002980 };
2981
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002982 static final int ANIMATION_TYPE_ADD = 0;
2983 static final int ANIMATION_TYPE_REMOVE = 1;
2984 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
2985 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
2986 static final int ANIMATION_TYPE_START_DRAG = 4;
2987 static final int ANIMATION_TYPE_SNAP_BACK = 5;
2988 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
2989 static final int ANIMATION_TYPE_DIMMED = 7;
2990 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04002991 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002992 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02002993 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02002994 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01002995 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002996 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
2997 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Selim Cineka59ecc32015-04-07 10:51:49 -07002998 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16;
2999 static final int ANIMATION_TYPE_EVERYTHING = 17;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003000
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003001 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3002 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3003
Selim Cinek572bbd42014-04-25 16:43:27 +02003004 final long eventStartTime;
3005 final View changingView;
3006 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003007 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003008 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003009 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003010 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07003011 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02003012
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003013 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003014 this(view, type, LENGTHS[type]);
3015 }
3016
3017 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003018 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3019 changingView = view;
3020 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003021 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003022 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003023 }
3024
3025 /**
3026 * Combines the length of several animation events into a single value.
3027 *
3028 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003029 * @return The combined length. Depending on the event types, this might be the maximum of
3030 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003031 */
3032 static long combineLength(ArrayList<AnimationEvent> events) {
3033 long length = 0;
3034 int size = events.size();
3035 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003036 AnimationEvent event = events.get(i);
3037 length = Math.max(length, event.length);
3038 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3039 return event.length;
3040 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003041 }
3042 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02003043 }
3044 }
3045
Selim Cinek67b22602014-03-10 15:40:16 +01003046}