blob: eadf951fcd30489cbefafc4feffd82283c9e8e75 [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
19import android.content.Context;
20import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010021import android.graphics.Canvas;
Selim Cinek67b22602014-03-10 15:40:16 +010022import android.graphics.Paint;
23import android.util.AttributeSet;
24import android.util.Log;
Selim Cinek67b22602014-03-10 15:40:16 +010025import android.view.MotionEvent;
26import android.view.VelocityTracker;
27import android.view.View;
28import android.view.ViewConfiguration;
29import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020030import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020031import android.view.animation.AnimationUtils;
Selim Cinek67b22602014-03-10 15:40:16 +010032import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020033
Selim Cinek67b22602014-03-10 15:40:16 +010034import com.android.systemui.ExpandHelper;
35import com.android.systemui.R;
36import com.android.systemui.SwipeHelper;
Selim Cineka32ab602014-06-11 15:06:01 +020037import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040038import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020039import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010040import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020041import com.android.systemui.statusbar.ExpandableView;
Selim Cinekc27437b2014-05-14 10:23:33 +020042import com.android.systemui.statusbar.SpeedBumpView;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010043import com.android.systemui.statusbar.policy.ScrollAdapter;
Jorim Jaggi290600a2014-05-30 17:02:20 +020044import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
Selim Cinek67b22602014-03-10 15:40:16 +010045
Selim Cinek572bbd42014-04-25 16:43:27 +020046import java.util.ArrayList;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020047import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020048
Selim Cinek67b22602014-03-10 15:40:16 +010049/**
50 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
51 */
52public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020053 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
54 ExpandableView.OnHeightChangedListener {
Selim Cinek67b22602014-03-10 15:40:16 +010055
56 private static final String TAG = "NotificationStackScrollLayout";
57 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020058 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
59 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020060 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010061
62 /**
63 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
64 */
65 private static final int INVALID_POINTER = -1;
66
Selim Cinek1408eb52014-06-02 14:45:38 +020067 private ExpandHelper mExpandHelper;
Selim Cinek67b22602014-03-10 15:40:16 +010068 private SwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +020069 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +010070 private int mCurrentStackHeight = Integer.MAX_VALUE;
71 private int mOwnScrollY;
72 private int mMaxLayoutHeight;
73
74 private VelocityTracker mVelocityTracker;
75 private OverScroller mScroller;
76 private int mTouchSlop;
77 private int mMinimumVelocity;
78 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +010079 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020080 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +010081 private boolean mIsBeingDragged;
82 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +020083 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +010084 private int mActivePointerId;
85
86 private int mSidePaddings;
87 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +010088 private int mContentHeight;
89 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +020090 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +010091 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +010092 private int mPaddingBetweenElements;
Selim Cineka5eaa602014-05-12 21:27:47 +020093 private int mPaddingBetweenElementsDimmed;
94 private int mPaddingBetweenElementsNormal;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +020095 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +020096 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +010097
98 /**
99 * The algorithm which calculates the properties for our children
100 */
101 private StackScrollAlgorithm mStackScrollAlgorithm;
102
103 /**
104 * The current State this Layout is in
105 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200106 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200107 private AmbientState mAmbientState = new AmbientState();
Selim Cinek572bbd42014-04-25 16:43:27 +0200108 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
109 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
Selim Cinekeb973562014-05-02 17:07:49 +0200110 private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
111 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200112 private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200113 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200114 private ArrayList<AnimationEvent> mAnimationEvents
115 = new ArrayList<AnimationEvent>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200116 private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
117 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200118 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200119 private boolean mChangePositionInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200120
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200121 /**
122 * The raw amount of the overScroll on the top, which is not rubber-banded.
123 */
124 private float mOverScrolledTopPixels;
125
126 /**
127 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
128 */
129 private float mOverScrolledBottomPixels;
130
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200131 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200132 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200133 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200134 private boolean mNeedsAnimation;
135 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200136 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200137 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400138 private boolean mDarkNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200139 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200140 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200141 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200142 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200143 private SpeedBumpView mSpeedBumpView;
144 private boolean mIsExpansionChanging;
Selim Cinek1408eb52014-06-02 14:45:38 +0200145 private boolean mExpandingNotification;
146 private boolean mExpandedInThisMotion;
147 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400148 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200149 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400150 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200151
152 /**
153 * Was the scroller scrolled to the top when the down motion was observed?
154 */
155 private boolean mScrolledToTopOnFirstDown;
156
157 /**
158 * The minimal amount of over scroll which is needed in order to switch to the quick settings
159 * when over scrolling on a expanded card.
160 */
161 private float mMinTopOverScrollToEscape;
162 private int mIntrinsicPadding;
163 private int mNotificationTopPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200164 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200165 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200166 private boolean mRequestViewResizeAnimationOnLayout;
167 private boolean mNeedViewResizeAnimation;
Selim Cinek1408eb52014-06-02 14:45:38 +0200168
169 /**
170 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
171 * This is needed to avoid scrolling too far after the notification was collapsed in the same
172 * motion.
173 */
174 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400175 private SwipeHelper.LongPressListener mLongPressListener;
Selim Cinek1408eb52014-06-02 14:45:38 +0200176
177 /**
178 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
179 * animating.
180 */
181 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200182 private ViewGroup mScrollView;
183 private boolean mInterceptDelegateEnabled;
184 private boolean mDelegateToScrollView;
Selim Cinekf7a14c02014-07-07 14:01:46 +0200185 private boolean mDisallowScrollingInThisMotion;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200186 private long mGoToFullShadeDelay;
Jorim Jaggi56306252014-07-03 00:40:09 +0200187
Selim Cinek1f553cf2014-05-02 12:01:36 +0200188 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200189 = new ViewTreeObserver.OnPreDrawListener() {
190 @Override
191 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200192 updateChildren();
193 mChildrenUpdateRequested = false;
194 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200195 return true;
196 }
197 };
Selim Cinek67b22602014-03-10 15:40:16 +0100198
199 public NotificationStackScrollLayout(Context context) {
200 this(context, null);
201 }
202
203 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
204 this(context, attrs, 0);
205 }
206
207 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
208 this(context, attrs, defStyleAttr, 0);
209 }
210
211 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
212 int defStyleRes) {
213 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200214 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
215 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
216 mExpandHelper = new ExpandHelper(getContext(), this,
217 minHeight, maxHeight);
218 mExpandHelper.setEventSource(this);
219 mExpandHelper.setScrollAdapter(this);
220
221 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
222 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinek67b22602014-03-10 15:40:16 +0100223 initView(context);
224 if (DEBUG) {
225 setWillNotDraw(false);
226 mDebugPaint = new Paint();
227 mDebugPaint.setColor(0xffff0000);
228 mDebugPaint.setStrokeWidth(2);
229 mDebugPaint.setStyle(Paint.Style.STROKE);
230 }
231 }
232
233 @Override
234 protected void onDraw(Canvas canvas) {
235 if (DEBUG) {
236 int y = mCollapsedSize;
237 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200238 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200239 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200240 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
241 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100242 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
243 y = (int) getLayoutHeight();
244 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200245 y = getHeight() - getEmptyBottomMargin();
246 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100247 }
248 }
249
250 private void initView(Context context) {
251 mScroller = new OverScroller(getContext());
252 setFocusable(true);
253 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200254 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100255 final ViewConfiguration configuration = ViewConfiguration.get(context);
256 mTouchSlop = configuration.getScaledTouchSlop();
257 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
258 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100259 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100260
261 mSidePaddings = context.getResources()
262 .getDimensionPixelSize(R.dimen.notification_side_padding);
Selim Cinek67b22602014-03-10 15:40:16 +0100263 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200264 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100265 mBottomStackPeekSize = context.getResources()
266 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinek67b22602014-03-10 15:40:16 +0100267 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200268 mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed());
Selim Cineka5eaa602014-05-12 21:27:47 +0200269 mPaddingBetweenElementsDimmed = context.getResources()
270 .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
271 mPaddingBetweenElementsNormal = context.getResources()
272 .getDimensionPixelSize(R.dimen.notification_padding);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200273 updatePadding(mAmbientState.isDimmed());
Selim Cinek1408eb52014-06-02 14:45:38 +0200274 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
275 R.dimen.min_top_overscroll_to_qs);
276 mNotificationTopPadding = getResources().getDimensionPixelSize(
277 R.dimen.notifications_top_padding);
Selim Cinekd83771e2014-07-04 16:45:31 +0200278 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
279 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200280 }
281
282 private void updatePadding(boolean dimmed) {
283 mPaddingBetweenElements = dimmed
284 ? mPaddingBetweenElementsDimmed
285 : mPaddingBetweenElementsNormal;
286 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
287 updateContentHeight();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200288 notifyHeightChangeListener(null);
289 }
290
291 private void notifyHeightChangeListener(ExpandableView view) {
292 if (mOnHeightChangedListener != null) {
293 mOnHeightChangedListener.onHeightChanged(view);
294 }
Selim Cinek67b22602014-03-10 15:40:16 +0100295 }
296
297 @Override
298 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
299 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
300 int mode = MeasureSpec.getMode(widthMeasureSpec);
301 int size = MeasureSpec.getSize(widthMeasureSpec);
302 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
303 measureChildren(childMeasureSpec, heightMeasureSpec);
304 }
305
306 @Override
307 protected void onLayout(boolean changed, int l, int t, int r, int b) {
308
309 // we layout all our children centered on the top
310 float centerX = getWidth() / 2.0f;
311 for (int i = 0; i < getChildCount(); i++) {
312 View child = getChildAt(i);
313 float width = child.getMeasuredWidth();
314 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100315 child.layout((int) (centerX - width / 2.0f),
316 0,
317 (int) (centerX + width / 2.0f),
318 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100319 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200320 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100321 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200322 clampScrollPosition();
Selim Cineka5e211b2014-08-11 17:35:48 +0200323 requestAnimationOnViewResize();
Selim Cinek319bdc42014-05-01 23:01:58 +0200324 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100325 }
326
Selim Cineka5e211b2014-08-11 17:35:48 +0200327 private void requestAnimationOnViewResize() {
328 if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) {
329 mNeedViewResizeAnimation = true;
330 mNeedsAnimation = true;
331 }
332 mRequestViewResizeAnimationOnLayout = false;
333 }
334
Selim Cinekc27437b2014-05-14 10:23:33 +0200335 public void updateSpeedBumpIndex(int newIndex) {
336 int currentIndex = indexOfChild(mSpeedBumpView);
337
338 // If we are currently layouted before the new speed bump index, we have to decrease it.
339 boolean validIndex = newIndex > 0;
340 if (newIndex > getChildCount() - 1) {
341 validIndex = false;
342 newIndex = -1;
343 }
344 if (validIndex && currentIndex != newIndex) {
345 changeViewPosition(mSpeedBumpView, newIndex);
346 }
347 updateSpeedBump(validIndex);
348 mAmbientState.setSpeedBumpIndex(newIndex);
349 }
350
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200351 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
352 mListener = listener;
353 }
354
355 /**
356 * Returns the location the given child is currently rendered at.
357 *
358 * @param child the child to get the location for
359 * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants
360 */
361 public int getChildLocation(View child) {
362 ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
363 if (childViewState == null) {
364 return ViewState.LOCATION_UNKNOWN;
365 }
366 return childViewState.location;
367 }
368
Selim Cinek67b22602014-03-10 15:40:16 +0100369 private void setMaxLayoutHeight(int maxLayoutHeight) {
370 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200371 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100372 }
373
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200374 private void updateAlgorithmHeightAndPadding() {
Selim Cinek67b22602014-03-10 15:40:16 +0100375 mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200376 mStackScrollAlgorithm.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100377 }
378
379 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200380 * @return whether the height of the layout needs to be adapted, in order to ensure that the
381 * last child is not in the bottom stack.
382 */
383 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200384 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200385 }
386
Selim Cinek4a1ac842014-05-01 15:51:58 +0200387 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100388 * Updates the children views according to the stack scroll algorithm. Call this whenever
389 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
390 */
391 private void updateChildren() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200392 mAmbientState.setScrollY(mOwnScrollY);
393 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200394 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200395 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100396 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200397 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100398 }
399 }
400
Selim Cinek319bdc42014-05-01 23:01:58 +0200401 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200402 if (!mChildrenUpdateRequested) {
403 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
404 mChildrenUpdateRequested = true;
405 invalidate();
406 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200407 }
408
Selim Cinek67b22602014-03-10 15:40:16 +0100409 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200410 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100411 }
412
Selim Cinekf7a14c02014-07-07 14:01:46 +0200413 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100414 int scrollRange = getScrollRange();
415 if (scrollRange < mOwnScrollY) {
416 mOwnScrollY = scrollRange;
417 }
418 }
419
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200420 public int getTopPadding() {
421 return mTopPadding;
422 }
423
Selim Cinek1408eb52014-06-02 14:45:38 +0200424 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200425 if (mTopPadding != topPadding) {
426 mTopPadding = topPadding;
427 updateAlgorithmHeightAndPadding();
428 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200429 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200430 mTopPaddingNeedsAnimation = true;
431 mNeedsAnimation = true;
432 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200433 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200434 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200435 }
436 }
437
438 /**
439 * Update the height of the stack to a new height.
440 *
441 * @param height the new height of the stack
442 */
443 public void setStackHeight(float height) {
444 setIsExpanded(height > 0.0f);
445 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200446 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200447 int stackHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +0200448 if (newStackHeight - mTopPadding >= minStackHeight || getNotGoneChildCount() == 0) {
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200449 setTranslationY(mTopPaddingOverflow);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200450 stackHeight = newStackHeight;
451 } else {
452
453 // We did not reach the position yet where we actually start growing,
454 // so we translate the stack upwards.
455 int translationY = (newStackHeight - minStackHeight);
456 // A slight parallax effect is introduced in order for the stack to catch up with
457 // the top card.
458 float partiallyThere = (float) (newStackHeight - mTopPadding) / minStackHeight;
459 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekd83771e2014-07-04 16:45:31 +0200460 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
461 mCollapseSecondCardPadding);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200462 setTranslationY(translationY - mTopPadding);
463 stackHeight = (int) (height - (translationY - mTopPadding));
464 }
465 if (stackHeight != mCurrentStackHeight) {
466 mCurrentStackHeight = stackHeight;
467 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200468 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200469 }
Selim Cinek67b22602014-03-10 15:40:16 +0100470 }
471
472 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100473 * 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 +0100474 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
475 *
476 * @return either the layout height or the externally defined height, whichever is smaller
477 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200478 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100479 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
480 }
481
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100482 public int getItemHeight() {
483 return mCollapsedSize;
484 }
485
486 public int getBottomStackPeekSize() {
487 return mBottomStackPeekSize;
488 }
489
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200490 public int getCollapseSecondCardPadding() {
491 return mCollapseSecondCardPadding;
492 }
493
Dan Sandler4247a5c2014-07-23 15:58:08 -0400494 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100495 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400496 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100497 }
498
Jorim Jaggi56306252014-07-03 00:40:09 +0200499 public void setScrollView(ViewGroup scrollView) {
500 mScrollView = scrollView;
501 }
502
503 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
504 mInterceptDelegateEnabled = interceptDelegateEnabled;
505 }
506
Selim Cinek67b22602014-03-10 15:40:16 +0100507 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400508 if (mDismissAllInProgress) {
509 return;
510 }
Selim Cinek67b22602014-03-10 15:40:16 +0100511 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
512 final View veto = v.findViewById(R.id.veto);
513 if (veto != null && veto.getVisibility() != View.GONE) {
514 veto.performClick();
515 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100516 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200517 if (mDragAnimPendingChildren.contains(v)) {
518 // We start the swipe and finish it in the same frame, we don't want any animation
519 // for the drag
520 mDragAnimPendingChildren.remove(v);
521 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200522 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200523 mAmbientState.onDragFinished(v);
Selim Cinekeb973562014-05-02 17:07:49 +0200524 }
525
526 @Override
527 public void onChildSnappedBack(View animView) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200528 mAmbientState.onDragFinished(animView);
Selim Cinekeb973562014-05-02 17:07:49 +0200529 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200530 if (mAnimationsEnabled) {
531 mSnappedBackChildren.add(animView);
532 mNeedsAnimation = true;
533 }
Selim Cinekeb973562014-05-02 17:07:49 +0200534 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200535 } else {
536 // We start the swipe and snap back in the same frame, we don't want any animation
537 mDragAnimPendingChildren.remove(animView);
538 }
Selim Cinek67b22602014-03-10 15:40:16 +0100539 }
540
Adrian Roos5d9cc662014-05-28 17:08:13 +0200541 @Override
542 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
543 return false;
544 }
545
Selim Cinek67b22602014-03-10 15:40:16 +0100546 public void onBeginDrag(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100547 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200548 mAmbientState.onBeginDrag(v);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200549 if (mAnimationsEnabled) {
550 mDragAnimPendingChildren.add(v);
551 mNeedsAnimation = true;
552 }
Selim Cinekeb973562014-05-02 17:07:49 +0200553 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100554 }
555
556 public void onDragCancelled(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100557 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100558 }
559
560 public View getChildAtPosition(MotionEvent ev) {
561 return getChildAtPosition(ev.getX(), ev.getY());
562 }
563
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200564 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100565 int[] location = new int[2];
566 getLocationOnScreen(location);
Selim Cinek319bdc42014-05-01 23:01:58 +0200567 return getChildAtPosition(touchX - location[0], touchY - location[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100568 }
569
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200570 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100571 // find the view under the pointer, accounting for GONE views
572 final int count = getChildCount();
573 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200574 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek67b22602014-03-10 15:40:16 +0100575 if (slidingChild.getVisibility() == GONE) {
576 continue;
577 }
Selim Cinek89faff12014-06-19 16:29:04 -0700578 float childTop = slidingChild.getTranslationY();
579 float top = childTop + slidingChild.getClipTopAmount();
Jorim Jaggi56306252014-07-03 00:40:09 +0200580 float bottom = top + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200581
582 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
583 // camera affordance).
584 int left = 0;
585 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100586
587 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
588 return slidingChild;
589 }
590 }
591 return null;
592 }
593
594 public boolean canChildBeExpanded(View v) {
595 return v instanceof ExpandableNotificationRow
596 && ((ExpandableNotificationRow) v).isExpandable();
597 }
598
599 public void setUserExpandedChild(View v, boolean userExpanded) {
600 if (v instanceof ExpandableNotificationRow) {
601 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
602 }
603 }
604
605 public void setUserLockedChild(View v, boolean userLocked) {
606 if (v instanceof ExpandableNotificationRow) {
607 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
608 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200609 removeLongPressCallback();
610 requestDisallowInterceptTouchEvent(true);
611 }
612
613 @Override
614 public void expansionStateChanged(boolean isExpanding) {
615 mExpandingNotification = isExpanding;
616 if (!mExpandedInThisMotion) {
617 mMaxScrollAfterExpand = mOwnScrollY;
618 mExpandedInThisMotion = true;
619 }
620 }
621
622 public void setScrollingEnabled(boolean enable) {
623 mScrollingEnabled = enable;
624 }
625
626 public void setExpandingEnabled(boolean enable) {
627 mExpandHelper.setEnabled(enable);
628 }
629
630 private boolean isScrollingEnabled() {
631 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100632 }
633
634 public View getChildContentView(View v) {
635 return v;
636 }
637
638 public boolean canChildBeDismissed(View v) {
639 final View veto = v.findViewById(R.id.veto);
640 return (veto != null && veto.getVisibility() != View.GONE);
641 }
642
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100643 private void setSwipingInProgress(boolean isSwiped) {
644 mSwipingInProgress = isSwiped;
645 if(isSwiped) {
646 requestDisallowInterceptTouchEvent(true);
647 }
Selim Cinek67b22602014-03-10 15:40:16 +0100648 }
649
650 @Override
651 protected void onConfigurationChanged(Configuration newConfig) {
652 super.onConfigurationChanged(newConfig);
653 float densityScale = getResources().getDisplayMetrics().density;
654 mSwipeHelper.setDensityScale(densityScale);
655 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
656 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
657 initView(getContext());
658 }
659
Dan Sandlereceda3d2014-07-21 15:35:01 -0400660 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
661 child.setClipBounds(null);
662 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100663 }
664
665 @Override
666 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200667 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
668 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Jorim Jaggi56306252014-07-03 00:40:09 +0200669 if (mDelegateToScrollView) {
670 if (isCancelOrUp) {
671 mDelegateToScrollView = false;
672 }
673 transformTouchEvent(ev, this, mScrollView);
674 return mScrollView.onTouchEvent(ev);
675 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200676 boolean expandWantsIt = false;
Jorim Jaggi341d1822014-07-03 23:34:13 +0200677 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200678 if (isCancelOrUp) {
679 mExpandHelper.onlyObserveMovements(false);
680 }
681 boolean wasExpandingBefore = mExpandingNotification;
682 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200683 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
684 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200685 dispatchDownEventToScroller(ev);
686 }
687 }
Selim Cinek67b22602014-03-10 15:40:16 +0100688 boolean scrollerWantsIt = false;
Selim Cinekf7a14c02014-07-07 14:01:46 +0200689 if (!mSwipingInProgress && !mExpandingNotification && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100690 scrollerWantsIt = onScrollTouch(ev);
691 }
692 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200693 if (!mIsBeingDragged
694 && !mExpandingNotification
695 && !mExpandedInThisMotion
696 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100697 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
698 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200699 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
700 }
701
702 private void dispatchDownEventToScroller(MotionEvent ev) {
703 MotionEvent downEvent = MotionEvent.obtain(ev);
704 downEvent.setAction(MotionEvent.ACTION_DOWN);
705 onScrollTouch(downEvent);
706 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100707 }
708
709 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200710 if (!isScrollingEnabled()) {
711 return false;
712 }
Selim Cinek67b22602014-03-10 15:40:16 +0100713 initVelocityTrackerIfNotExists();
714 mVelocityTracker.addMovement(ev);
715
716 final int action = ev.getAction();
717
718 switch (action & MotionEvent.ACTION_MASK) {
719 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +0200720 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +0100721 return false;
722 }
723 boolean isBeingDragged = !mScroller.isFinished();
724 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +0100725
726 /*
727 * If being flinged and user touches, stop the fling. isFinished
728 * will be false if being flinged.
729 */
730 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200731 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100732 }
733
734 // Remember where the motion event started
735 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +0200736 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +0100737 mActivePointerId = ev.getPointerId(0);
738 break;
739 }
740 case MotionEvent.ACTION_MOVE:
741 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
742 if (activePointerIndex == -1) {
743 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
744 break;
745 }
746
747 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +0200748 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +0100749 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200750 final int xDiff = Math.abs(x - mDownX);
751 final int yDiff = Math.abs(deltaY);
752 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +0100753 setIsBeingDragged(true);
754 if (deltaY > 0) {
755 deltaY -= mTouchSlop;
756 } else {
757 deltaY += mTouchSlop;
758 }
759 }
760 if (mIsBeingDragged) {
761 // Scroll to follow the motion event
762 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200763 int range = getScrollRange();
764 if (mExpandedInThisMotion) {
765 range = Math.min(range, mMaxScrollAfterExpand);
766 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200767
768 float scrollAmount;
769 if (deltaY < 0) {
770 scrollAmount = overScrollDown(deltaY);
771 } else {
772 scrollAmount = overScrollUp(deltaY, range);
773 }
Selim Cinek67b22602014-03-10 15:40:16 +0100774
775 // Calling overScrollBy will call onOverScrolled, which
776 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200777 if (scrollAmount != 0.0f) {
778 // The scrolling motion could not be compensated with the
779 // existing overScroll, we have to scroll the view
780 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
781 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +0100782 }
Selim Cinek67b22602014-03-10 15:40:16 +0100783 }
784 break;
785 case MotionEvent.ACTION_UP:
786 if (mIsBeingDragged) {
787 final VelocityTracker velocityTracker = mVelocityTracker;
788 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
789 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
790
Selim Cinek1408eb52014-06-02 14:45:38 +0200791 if (shouldOverScrollFling(initialVelocity)) {
792 onOverScrollFling(true, initialVelocity);
793 } else {
794 if (getChildCount() > 0) {
795 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
796 float currentOverScrollTop = getCurrentOverScrollAmount(true);
797 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
798 fling(-initialVelocity);
799 } else {
800 onOverScrollFling(false, initialVelocity);
801 }
802 } else {
803 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
804 getScrollRange())) {
805 postInvalidateOnAnimation();
806 }
Selim Cinek67b22602014-03-10 15:40:16 +0100807 }
808 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200809 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700810
811 mActivePointerId = INVALID_POINTER;
812 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +0100813 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700814
Selim Cinek67b22602014-03-10 15:40:16 +0100815 break;
816 case MotionEvent.ACTION_CANCEL:
817 if (mIsBeingDragged && getChildCount() > 0) {
818 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
819 postInvalidateOnAnimation();
820 }
821 mActivePointerId = INVALID_POINTER;
822 endDrag();
823 }
824 break;
825 case MotionEvent.ACTION_POINTER_DOWN: {
826 final int index = ev.getActionIndex();
827 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +0200828 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +0100829 mActivePointerId = ev.getPointerId(index);
830 break;
831 }
832 case MotionEvent.ACTION_POINTER_UP:
833 onSecondaryPointerUp(ev);
834 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +0200835 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +0100836 break;
837 }
838 return true;
839 }
840
Selim Cinek1408eb52014-06-02 14:45:38 +0200841 private void onOverScrollFling(boolean open, int initialVelocity) {
842 if (mOverscrollTopChangedListener != null) {
843 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
844 }
845 mDontReportNextOverScroll = true;
846 setOverScrollAmount(0.0f, true, false);
847 }
848
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200849 /**
850 * Perform a scroll upwards and adapt the overscroll amounts accordingly
851 *
852 * @param deltaY The amount to scroll upwards, has to be positive.
853 * @return The amount of scrolling to be performed by the scroller,
854 * not handled by the overScroll amount.
855 */
856 private float overScrollUp(int deltaY, int range) {
857 deltaY = Math.max(deltaY, 0);
858 float currentTopAmount = getCurrentOverScrollAmount(true);
859 float newTopAmount = currentTopAmount - deltaY;
860 if (currentTopAmount > 0) {
861 setOverScrollAmount(newTopAmount, true /* onTop */,
862 false /* animate */);
863 }
864 // Top overScroll might not grab all scrolling motion,
865 // we have to scroll as well.
866 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
867 float newScrollY = mOwnScrollY + scrollAmount;
868 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200869 if (!mExpandedInThisMotion) {
870 float currentBottomPixels = getCurrentOverScrolledPixels(false);
871 // We overScroll on the top
872 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
873 false /* onTop */,
874 false /* animate */);
875 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200876 mOwnScrollY = range;
877 scrollAmount = 0.0f;
878 }
879 return scrollAmount;
880 }
881
882 /**
883 * Perform a scroll downward and adapt the overscroll amounts accordingly
884 *
885 * @param deltaY The amount to scroll downwards, has to be negative.
886 * @return The amount of scrolling to be performed by the scroller,
887 * not handled by the overScroll amount.
888 */
889 private float overScrollDown(int deltaY) {
890 deltaY = Math.min(deltaY, 0);
891 float currentBottomAmount = getCurrentOverScrollAmount(false);
892 float newBottomAmount = currentBottomAmount + deltaY;
893 if (currentBottomAmount > 0) {
894 setOverScrollAmount(newBottomAmount, false /* onTop */,
895 false /* animate */);
896 }
897 // Bottom overScroll might not grab all scrolling motion,
898 // we have to scroll as well.
899 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
900 float newScrollY = mOwnScrollY + scrollAmount;
901 if (newScrollY < 0) {
902 float currentTopPixels = getCurrentOverScrolledPixels(true);
903 // We overScroll on the top
904 setOverScrolledPixels(currentTopPixels - newScrollY,
905 true /* onTop */,
906 false /* animate */);
907 mOwnScrollY = 0;
908 scrollAmount = 0.0f;
909 }
910 return scrollAmount;
911 }
912
Selim Cinek67b22602014-03-10 15:40:16 +0100913 private void onSecondaryPointerUp(MotionEvent ev) {
914 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
915 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
916 final int pointerId = ev.getPointerId(pointerIndex);
917 if (pointerId == mActivePointerId) {
918 // This was our active pointer going up. Choose a new
919 // active pointer and adjust accordingly.
920 // TODO: Make this decision more intelligent.
921 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
922 mLastMotionY = (int) ev.getY(newPointerIndex);
923 mActivePointerId = ev.getPointerId(newPointerIndex);
924 if (mVelocityTracker != null) {
925 mVelocityTracker.clear();
926 }
927 }
928 }
929
930 private void initVelocityTrackerIfNotExists() {
931 if (mVelocityTracker == null) {
932 mVelocityTracker = VelocityTracker.obtain();
933 }
934 }
935
936 private void recycleVelocityTracker() {
937 if (mVelocityTracker != null) {
938 mVelocityTracker.recycle();
939 mVelocityTracker = null;
940 }
941 }
942
943 private void initOrResetVelocityTracker() {
944 if (mVelocityTracker == null) {
945 mVelocityTracker = VelocityTracker.obtain();
946 } else {
947 mVelocityTracker.clear();
948 }
949 }
950
951 @Override
952 public void computeScroll() {
953 if (mScroller.computeScrollOffset()) {
954 // This is called at drawing time by ViewGroup.
955 int oldX = mScrollX;
956 int oldY = mOwnScrollY;
957 int x = mScroller.getCurrX();
958 int y = mScroller.getCurrY();
959
960 if (oldX != x || oldY != y) {
961 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200962 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
963 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +0200964 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200965 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
966 }
967 }
Selim Cinek67b22602014-03-10 15:40:16 +0100968
969 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200970 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +0100971 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +0100972 }
973
974 // Keep on drawing until the animation has finished.
975 postInvalidateOnAnimation();
976 }
977 }
978
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200979 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +0200980 protected boolean overScrollBy(int deltaX, int deltaY,
981 int scrollX, int scrollY,
982 int scrollRangeX, int scrollRangeY,
983 int maxOverScrollX, int maxOverScrollY,
984 boolean isTouchEvent) {
985
986 int newScrollY = scrollY + deltaY;
987
988 final int top = -maxOverScrollY;
989 final int bottom = maxOverScrollY + scrollRangeY;
990
991 boolean clampedY = false;
992 if (newScrollY > bottom) {
993 newScrollY = bottom;
994 clampedY = true;
995 } else if (newScrollY < top) {
996 newScrollY = top;
997 clampedY = true;
998 }
999
1000 onOverScrolled(0, newScrollY, false, clampedY);
1001
1002 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001003 }
1004
1005 /**
1006 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1007 * overscroll effect based on numPixels. By default this will also cancel animations on the
1008 * same overScroll edge.
1009 *
1010 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1011 * the rubber-banding logic.
1012 * @param onTop Should the effect be applied on top of the scroller.
1013 * @param animate Should an animation be performed.
1014 */
1015 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001016 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001017 }
1018
1019 /**
1020 * Set the effective overScroll amount which will be directly reflected in the layout.
1021 * By default this will also cancel animations on the same overScroll edge.
1022 *
1023 * @param amount The amount to overScroll by.
1024 * @param onTop Should the effect be applied on top of the scroller.
1025 * @param animate Should an animation be performed.
1026 */
1027 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1028 setOverScrollAmount(amount, onTop, animate, true);
1029 }
1030
1031 /**
1032 * Set the effective overScroll amount which will be directly reflected in the layout.
1033 *
1034 * @param amount The amount to overScroll by.
1035 * @param onTop Should the effect be applied on top of the scroller.
1036 * @param animate Should an animation be performed.
1037 * @param cancelAnimators Should running animations be cancelled.
1038 */
1039 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1040 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001041 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1042 }
1043
1044 /**
1045 * Set the effective overScroll amount which will be directly reflected in the layout.
1046 *
1047 * @param amount The amount to overScroll by.
1048 * @param onTop Should the effect be applied on top of the scroller.
1049 * @param animate Should an animation be performed.
1050 * @param cancelAnimators Should running animations be cancelled.
1051 * @param isRubberbanded The value which will be passed to
1052 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1053 */
1054 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1055 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001056 if (cancelAnimators) {
1057 mStateAnimator.cancelOverScrollAnimators(onTop);
1058 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001059 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001060 }
1061
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001062 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1063 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001064 amount = Math.max(0, amount);
1065 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001066 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001067 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001068 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001069 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001070 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001071 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001072 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001073 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001074 }
1075 }
1076
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001077 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001078 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1079 if (mDontReportNextOverScroll) {
1080 mDontReportNextOverScroll = false;
1081 return;
1082 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001083 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001084 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001085 }
1086 }
1087
1088 public void setOverscrollTopChangedListener(
1089 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1090 mOverscrollTopChangedListener = overscrollTopChangedListener;
1091 }
1092
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001093 public float getCurrentOverScrollAmount(boolean top) {
1094 return mAmbientState.getOverScrollAmount(top);
1095 }
1096
1097 public float getCurrentOverScrolledPixels(boolean top) {
1098 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1099 }
1100
1101 private void setOverScrolledPixels(float amount, boolean onTop) {
1102 if (onTop) {
1103 mOverScrolledTopPixels = amount;
1104 } else {
1105 mOverScrolledBottomPixels = amount;
1106 }
1107 }
1108
Selim Cinek319bdc42014-05-01 23:01:58 +02001109 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001110 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001111 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001112 }
1113
1114 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001115 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001116 // Treat animating scrolls differently; see #computeScroll() for why.
1117 if (!mScroller.isFinished()) {
1118 final int oldX = mScrollX;
1119 final int oldY = mOwnScrollY;
1120 mScrollX = scrollX;
1121 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001122 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001123 springBack();
1124 } else {
1125 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1126 invalidateParentIfNeeded();
1127 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001128 float overScrollTop = getCurrentOverScrollAmount(true);
1129 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001130 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001131 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001132 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001133 }
Selim Cinek67b22602014-03-10 15:40:16 +01001134 }
Selim Cinek67b22602014-03-10 15:40:16 +01001135 } else {
1136 customScrollTo(scrollY);
1137 scrollTo(scrollX, mScrollY);
1138 }
1139 }
1140
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001141 private void springBack() {
1142 int scrollRange = getScrollRange();
1143 boolean overScrolledTop = mOwnScrollY <= 0;
1144 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1145 if (overScrolledTop || overScrolledBottom) {
1146 boolean onTop;
1147 float newAmount;
1148 if (overScrolledTop) {
1149 onTop = true;
1150 newAmount = -mOwnScrollY;
1151 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001152 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001153 } else {
1154 onTop = false;
1155 newAmount = mOwnScrollY - scrollRange;
1156 mOwnScrollY = scrollRange;
1157 }
1158 setOverScrollAmount(newAmount, onTop, false);
1159 setOverScrollAmount(0.0f, onTop, true);
1160 mScroller.forceFinished(true);
1161 }
1162 }
1163
Selim Cinek67b22602014-03-10 15:40:16 +01001164 private int getScrollRange() {
1165 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001166 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001167 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001168 int contentHeight = getContentHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001169 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cineka5eaa602014-05-12 21:27:47 +02001170 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1171 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001172 if (scrollRange > 0) {
1173 View lastChild = getLastChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001174 // We want to at least be able collapse the first item and not ending in a weird
1175 // end state.
1176 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1177 }
Selim Cinek67b22602014-03-10 15:40:16 +01001178 }
1179 return scrollRange;
1180 }
1181
Selim Cinek343e6e22014-04-11 21:23:30 +02001182 /**
1183 * @return the first child which has visibility unequal to GONE
1184 */
1185 private View getFirstChildNotGone() {
1186 int childCount = getChildCount();
1187 for (int i = 0; i < childCount; i++) {
1188 View child = getChildAt(i);
1189 if (child.getVisibility() != View.GONE) {
1190 return child;
1191 }
1192 }
1193 return null;
1194 }
1195
Selim Cinek4a1ac842014-05-01 15:51:58 +02001196 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001197 * @return The first child which has visibility unequal to GONE which is currently below the
1198 * given translationY or equal to it.
1199 */
1200 private View getFirstChildBelowTranlsationY(float translationY) {
1201 int childCount = getChildCount();
1202 for (int i = 0; i < childCount; i++) {
1203 View child = getChildAt(i);
1204 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1205 return child;
1206 }
1207 }
1208 return null;
1209 }
1210
1211 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001212 * @return the last child which has visibility unequal to GONE
1213 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001214 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001215 int childCount = getChildCount();
1216 for (int i = childCount - 1; i >= 0; i--) {
1217 View child = getChildAt(i);
1218 if (child.getVisibility() != View.GONE) {
1219 return child;
1220 }
1221 }
1222 return null;
1223 }
1224
Jorim Jaggi069cd032014-05-15 03:09:01 +02001225 /**
1226 * @return the number of children which have visibility unequal to GONE
1227 */
1228 public int getNotGoneChildCount() {
1229 int childCount = getChildCount();
1230 int count = 0;
1231 for (int i = 0; i < childCount; i++) {
1232 View child = getChildAt(i);
1233 if (child.getVisibility() != View.GONE) {
1234 count++;
1235 }
1236 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001237 if (mDismissView.willBeGone()) {
1238 count--;
1239 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02001240 if (mEmptyShadeView.willBeGone()) {
1241 count--;
1242 }
Jorim Jaggi069cd032014-05-15 03:09:01 +02001243 return count;
1244 }
1245
Selim Cinek343e6e22014-04-11 21:23:30 +02001246 private int getMaxExpandHeight(View view) {
1247 if (view instanceof ExpandableNotificationRow) {
1248 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001249 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001250 }
1251 return view.getHeight();
1252 }
1253
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001254 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001255 return mContentHeight;
1256 }
1257
1258 private void updateContentHeight() {
1259 int height = 0;
1260 for (int i = 0; i < getChildCount(); i++) {
1261 View child = getChildAt(i);
Jorim Jaggid4a57442014-04-10 02:45:55 +02001262 if (child.getVisibility() != View.GONE) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001263 if (height != 0) {
1264 // add the padding before this element
Jorim Jaggid4a57442014-04-10 02:45:55 +02001265 height += mPaddingBetweenElements;
1266 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001267 if (child instanceof ExpandableNotificationRow) {
1268 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001269 height += row.getIntrinsicHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001270 } else if (child instanceof ExpandableView) {
1271 ExpandableView expandableView = (ExpandableView) child;
1272 height += expandableView.getActualHeight();
1273 }
Selim Cinek67b22602014-03-10 15:40:16 +01001274 }
1275 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001276 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001277 }
1278
1279 /**
1280 * Fling the scroll view
1281 *
1282 * @param velocityY The initial velocity in the Y direction. Positive
1283 * numbers mean that the finger/cursor is moving down the screen,
1284 * which means we want to scroll towards the top.
1285 */
1286 private void fling(int velocityY) {
1287 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001288 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001289
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001290 float topAmount = getCurrentOverScrollAmount(true);
1291 float bottomAmount = getCurrentOverScrollAmount(false);
1292 if (velocityY < 0 && topAmount > 0) {
1293 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001294 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001295 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001296 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001297 * mOverflingDistance + topAmount;
1298 } else if (velocityY > 0 && bottomAmount > 0) {
1299 mOwnScrollY += bottomAmount;
1300 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001301 mMaxOverScroll = Math.abs(velocityY) / 1000f
1302 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1303 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001304 } else {
1305 // it will be set once we reach the boundary
1306 mMaxOverScroll = 0.0f;
1307 }
1308 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek4195dd02014-05-19 18:16:14 +02001309 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001310
1311 postInvalidateOnAnimation();
1312 }
1313 }
1314
Selim Cinek1408eb52014-06-02 14:45:38 +02001315 /**
1316 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1317 * overScroll view (i.e QS).
1318 */
1319 private boolean shouldOverScrollFling(int initialVelocity) {
1320 float topOverScroll = getCurrentOverScrollAmount(true);
1321 return mScrolledToTopOnFirstDown
1322 && !mExpandedInThisMotion
1323 && topOverScroll > mMinTopOverScrollToEscape
1324 && initialVelocity > 0;
1325 }
1326
1327 public void updateTopPadding(float qsHeight, int scrollY, boolean animate) {
1328 float start = qsHeight - scrollY + mNotificationTopPadding;
1329 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001330 int minStackHeight = getMinStackHeight();
1331 if (stackHeight <= minStackHeight) {
1332 float overflow = minStackHeight - stackHeight;
1333 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001334 start = getHeight() - stackHeight;
1335 setTranslationY(overflow);
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001336 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001337 } else {
1338 setTranslationY(0);
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001339 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001340 }
1341 setTopPadding(clampPadding((int) start), animate);
1342 }
1343
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001344 public int getNotificationTopPadding() {
1345 return mNotificationTopPadding;
1346 }
1347
1348 public int getMinStackHeight() {
Selim Cinekd83771e2014-07-04 16:45:31 +02001349 return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001350 }
1351
1352 public float getTopPaddingOverflow() {
1353 return mTopPaddingOverflow;
1354 }
1355
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001356 public int getPeekHeight() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001357 return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
1358 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001359 }
1360
Selim Cinek1408eb52014-06-02 14:45:38 +02001361 private int clampPadding(int desiredPadding) {
1362 return Math.max(desiredPadding, mIntrinsicPadding);
1363 }
1364
Selim Cinekfed1ab62014-06-17 14:10:33 -07001365 private float getRubberBandFactor(boolean onTop) {
1366 if (!onTop) {
1367 return RUBBER_BAND_FACTOR_NORMAL;
1368 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001369 if (mExpandedInThisMotion) {
1370 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
1371 } else if (mIsExpansionChanging) {
1372 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1373 } else if (mScrolledToTopOnFirstDown) {
1374 return 1.0f;
1375 }
1376 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001377 }
1378
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001379 /**
1380 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1381 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1382 * overscroll view (e.g. expand QS).
1383 */
1384 private boolean isRubberbanded(boolean onTop) {
1385 return !onTop || mExpandedInThisMotion || mIsExpansionChanging
1386 || !mScrolledToTopOnFirstDown;
1387 }
1388
Selim Cinek67b22602014-03-10 15:40:16 +01001389 private void endDrag() {
1390 setIsBeingDragged(false);
1391
1392 recycleVelocityTracker();
1393
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001394 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1395 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1396 }
1397 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1398 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1399 }
Selim Cinek67b22602014-03-10 15:40:16 +01001400 }
1401
Jorim Jaggi56306252014-07-03 00:40:09 +02001402 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1403 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1404 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1405 }
1406
Selim Cinek67b22602014-03-10 15:40:16 +01001407 @Override
1408 public boolean onInterceptTouchEvent(MotionEvent ev) {
Jorim Jaggi56306252014-07-03 00:40:09 +02001409 if (mInterceptDelegateEnabled) {
1410 transformTouchEvent(ev, this, mScrollView);
1411 if (mScrollView.onInterceptTouchEvent(ev)) {
1412 mDelegateToScrollView = true;
1413 removeLongPressCallback();
1414 return true;
1415 }
1416 transformTouchEvent(ev, mScrollView, this);
1417 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001418 initDownStates(ev);
1419 boolean expandWantsIt = false;
Jorim Jaggi341d1822014-07-03 23:34:13 +02001420 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001421 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1422 }
Selim Cinek67b22602014-03-10 15:40:16 +01001423 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001424 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001425 scrollWantsIt = onInterceptTouchEventScroll(ev);
1426 }
1427 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001428 if (!mIsBeingDragged
1429 && !mExpandingNotification
1430 && !mExpandedInThisMotion
1431 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001432 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1433 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001434 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1435 }
1436
1437 private void initDownStates(MotionEvent ev) {
1438 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1439 mExpandedInThisMotion = false;
1440 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001441 mDisallowScrollingInThisMotion = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001442 }
Selim Cinek67b22602014-03-10 15:40:16 +01001443 }
1444
Christoph Studer068f5922014-04-08 17:43:07 -04001445 @Override
1446 protected void onViewRemoved(View child) {
1447 super.onViewRemoved(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001448 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001449 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001450 // This is only a position change, don't do anything special
1451 return;
1452 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001453 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04001454 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001455 updateScrollStateForRemovedChild(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001456 boolean animationGenerated = generateRemoveAnimation(child);
1457 if (animationGenerated && !mSwipedOutViews.contains(child)) {
1458 // Add this view to an overlay in order to ensure that it will still be temporary
1459 // drawn when removed
1460 getOverlay().add(child);
1461 }
Selim Cinekc27437b2014-05-14 10:23:33 +02001462 }
1463
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001464 /**
1465 * Generate a remove animation for a child view.
1466 *
1467 * @param child The view to generate the remove animation for.
1468 * @return Whether an animation was generated.
1469 */
1470 private boolean generateRemoveAnimation(View child) {
Jorim Jaggi75c95042014-05-16 19:09:59 +02001471 if (mIsExpanded && mAnimationsEnabled) {
Selim Cinekf4c19962014-05-01 21:55:31 +02001472 if (!mChildrenToAddAnimated.contains(child)) {
1473 // Generate Animations
1474 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001475 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001476 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02001477 } else {
1478 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001479 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001480 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02001481 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001482 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001483 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001484 }
1485
1486 /**
1487 * Updates the scroll position when a child was removed
1488 *
1489 * @param removedChild the removed child
1490 */
1491 private void updateScrollStateForRemovedChild(View removedChild) {
1492 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinekd7c4e002014-07-04 18:36:42 +02001493 int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
Selim Cinek572bbd42014-04-25 16:43:27 +02001494 int endPosition = startingPosition + childHeight;
1495 if (endPosition <= mOwnScrollY) {
1496 // This child is fully scrolled of the top, so we have to deduct its height from the
1497 // scrollPosition
1498 mOwnScrollY -= childHeight;
1499 } else if (startingPosition < mOwnScrollY) {
1500 // This child is currently being scrolled into, set the scroll position to the start of
1501 // this child
1502 mOwnScrollY = startingPosition;
1503 }
1504 }
1505
Selim Cinekd7c4e002014-07-04 18:36:42 +02001506 private int getIntrinsicHeight(View view) {
1507 if (view instanceof ExpandableView) {
1508 ExpandableView expandableView = (ExpandableView) view;
1509 return expandableView.getIntrinsicHeight();
1510 }
1511 return view.getHeight();
1512 }
1513
Selim Cinek572bbd42014-04-25 16:43:27 +02001514 private int getPositionInLinearLayout(View requestedChild) {
1515 int position = 0;
1516 for (int i = 0; i < getChildCount(); i++) {
1517 View child = getChildAt(i);
1518 if (child == requestedChild) {
1519 return position;
1520 }
1521 if (child.getVisibility() != View.GONE) {
1522 position += child.getHeight();
1523 if (i < getChildCount()-1) {
1524 position += mPaddingBetweenElements;
1525 }
1526 }
1527 }
1528 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02001529 }
1530
1531 @Override
1532 protected void onViewAdded(View child) {
1533 super.onViewAdded(child);
1534 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02001535 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00001536 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek572bbd42014-04-25 16:43:27 +02001537 }
1538
Jorim Jaggi75c95042014-05-16 19:09:59 +02001539 public void setAnimationsEnabled(boolean animationsEnabled) {
1540 mAnimationsEnabled = animationsEnabled;
1541 }
1542
1543 public boolean isAddOrRemoveAnimationPending() {
1544 return mNeedsAnimation
1545 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1546 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001547 /**
1548 * Generate an animation for an added child view.
1549 *
1550 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00001551 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001552 */
Jorim Jaggif6411742014-08-05 17:10:43 +00001553 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001554 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001555 // Generate Animations
1556 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00001557 if (fromMoreCard) {
1558 mFromMoreCardAdditions.add(child);
1559 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001560 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02001561 }
1562 }
1563
1564 /**
1565 * Change the position of child to a new location
1566 *
1567 * @param child the view to change the position for
1568 * @param newIndex the new index
1569 */
1570 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04001571 int currentIndex = indexOfChild(child);
1572 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001573 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02001574 removeView(child);
1575 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001576 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04001577 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001578 mChildrenChangingPositions.add(child);
1579 mNeedsAnimation = true;
1580 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001581 }
1582 }
1583
Selim Cinekf4c19962014-05-01 21:55:31 +02001584 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001585 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001586 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001587 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001588 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001589 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001590 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1591 mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001592 mAnimationEvents.clear();
Selim Cinekf4c19962014-05-01 21:55:31 +02001593 } else {
1594 applyCurrentState();
1595 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001596 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02001597 }
1598
1599 private void generateChildHierarchyEvents() {
Selim Cinek572bbd42014-04-25 16:43:27 +02001600 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001601 generateChildAdditionEvents();
1602 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02001603 generateSnapBackEvents();
1604 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001605 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001606 generateActivateEvent();
1607 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02001608 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04001609 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001610 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02001611 generateViewResizeEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001612 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001613 }
1614
Selim Cineka5e211b2014-08-11 17:35:48 +02001615 private void generateViewResizeEvent() {
1616 if (mNeedViewResizeAnimation) {
1617 mAnimationEvents.add(
1618 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
1619 }
1620 mNeedViewResizeAnimation = false;
1621 }
1622
Selim Cinekeb973562014-05-02 17:07:49 +02001623 private void generateSnapBackEvents() {
1624 for (View child : mSnappedBackChildren) {
1625 mAnimationEvents.add(new AnimationEvent(child,
1626 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1627 }
1628 mSnappedBackChildren.clear();
1629 }
1630
1631 private void generateDragEvents() {
1632 for (View child : mDragAnimPendingChildren) {
1633 mAnimationEvents.add(new AnimationEvent(child,
1634 AnimationEvent.ANIMATION_TYPE_START_DRAG));
1635 }
1636 mDragAnimPendingChildren.clear();
1637 }
1638
Selim Cinek572bbd42014-04-25 16:43:27 +02001639 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001640 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001641 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1642 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001643 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1644 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001645 AnimationEvent event = new AnimationEvent(child, animationType);
1646
1647 // we need to know the view after this one
1648 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1649 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02001650 }
1651 mSwipedOutViews.clear();
1652 mChildrenToRemoveAnimated.clear();
1653 }
1654
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001655 private void generatePositionChangeEvents() {
1656 for (View child : mChildrenChangingPositions) {
1657 mAnimationEvents.add(new AnimationEvent(child,
1658 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1659 }
1660 mChildrenChangingPositions.clear();
1661 }
1662
Selim Cinek572bbd42014-04-25 16:43:27 +02001663 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001664 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001665 if (mFromMoreCardAdditions.contains(child)) {
1666 mAnimationEvents.add(new AnimationEvent(child,
1667 AnimationEvent.ANIMATION_TYPE_ADD,
1668 StackStateAnimator.ANIMATION_DURATION_STANDARD));
1669 } else {
1670 mAnimationEvents.add(new AnimationEvent(child,
1671 AnimationEvent.ANIMATION_TYPE_ADD));
1672 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001673 }
1674 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001675 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04001676 }
1677
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001678 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02001679 if (mTopPaddingNeedsAnimation) {
1680 mAnimationEvents.add(
1681 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
1682 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001683 mTopPaddingNeedsAnimation = false;
1684 }
1685
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001686 private void generateActivateEvent() {
1687 if (mActivateNeedsAnimation) {
1688 mAnimationEvents.add(
1689 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
1690 }
1691 mActivateNeedsAnimation = false;
1692 }
1693
1694 private void generateDimmedEvent() {
1695 if (mDimmedNeedsAnimation) {
1696 mAnimationEvents.add(
1697 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
1698 }
1699 mDimmedNeedsAnimation = false;
1700 }
1701
Jorim Jaggiae441282014-08-01 02:45:18 +02001702 private void generateHideSensitiveEvent() {
1703 if (mHideSensitiveNeedsAnimation) {
1704 mAnimationEvents.add(
1705 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
1706 }
1707 mHideSensitiveNeedsAnimation = false;
1708 }
1709
John Spurlockbf370992014-06-17 13:58:31 -04001710 private void generateDarkEvent() {
1711 if (mDarkNeedsAnimation) {
1712 mAnimationEvents.add(
1713 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
1714 }
1715 mDarkNeedsAnimation = false;
1716 }
1717
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001718 private void generateGoToFullShadeEvent() {
1719 if (mGoToFullShadeNeedsAnimation) {
1720 mAnimationEvents.add(
1721 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
1722 }
1723 mGoToFullShadeNeedsAnimation = false;
1724 }
1725
Selim Cinek67b22602014-03-10 15:40:16 +01001726 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001727 if (!isScrollingEnabled()) {
1728 return false;
1729 }
Selim Cinek67b22602014-03-10 15:40:16 +01001730 /*
1731 * This method JUST determines whether we want to intercept the motion.
1732 * If we return true, onMotionEvent will be called and we do the actual
1733 * scrolling there.
1734 */
1735
1736 /*
1737 * Shortcut the most recurring case: the user is in the dragging
1738 * state and he is moving his finger. We want to intercept this
1739 * motion.
1740 */
1741 final int action = ev.getAction();
1742 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
1743 return true;
1744 }
1745
Selim Cinek67b22602014-03-10 15:40:16 +01001746 switch (action & MotionEvent.ACTION_MASK) {
1747 case MotionEvent.ACTION_MOVE: {
1748 /*
1749 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1750 * whether the user has moved far enough from his original down touch.
1751 */
1752
1753 /*
1754 * Locally do absolute value. mLastMotionY is set to the y value
1755 * of the down event.
1756 */
1757 final int activePointerId = mActivePointerId;
1758 if (activePointerId == INVALID_POINTER) {
1759 // If we don't have a valid id, the touch down wasn't on content.
1760 break;
1761 }
1762
1763 final int pointerIndex = ev.findPointerIndex(activePointerId);
1764 if (pointerIndex == -1) {
1765 Log.e(TAG, "Invalid pointerId=" + activePointerId
1766 + " in onInterceptTouchEvent");
1767 break;
1768 }
1769
1770 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001771 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001772 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02001773 final int xDiff = Math.abs(x - mDownX);
1774 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001775 setIsBeingDragged(true);
1776 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001777 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01001778 initVelocityTrackerIfNotExists();
1779 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01001780 }
1781 break;
1782 }
1783
1784 case MotionEvent.ACTION_DOWN: {
1785 final int y = (int) ev.getY();
1786 if (getChildAtPosition(ev.getX(), y) == null) {
1787 setIsBeingDragged(false);
1788 recycleVelocityTracker();
1789 break;
1790 }
1791
1792 /*
1793 * Remember location of down touch.
1794 * ACTION_DOWN always refers to pointer index 0.
1795 */
1796 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001797 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001798 mActivePointerId = ev.getPointerId(0);
Selim Cinek1408eb52014-06-02 14:45:38 +02001799 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01001800
1801 initOrResetVelocityTracker();
1802 mVelocityTracker.addMovement(ev);
1803 /*
1804 * If being flinged and user touches the screen, initiate drag;
1805 * otherwise don't. mScroller.isFinished should be false when
1806 * being flinged.
1807 */
1808 boolean isBeingDragged = !mScroller.isFinished();
1809 setIsBeingDragged(isBeingDragged);
1810 break;
1811 }
1812
1813 case MotionEvent.ACTION_CANCEL:
1814 case MotionEvent.ACTION_UP:
1815 /* Release the drag */
1816 setIsBeingDragged(false);
1817 mActivePointerId = INVALID_POINTER;
1818 recycleVelocityTracker();
1819 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1820 postInvalidateOnAnimation();
1821 }
1822 break;
1823 case MotionEvent.ACTION_POINTER_UP:
1824 onSecondaryPointerUp(ev);
1825 break;
1826 }
1827
1828 /*
1829 * The only time we want to intercept motion events is if we are in the
1830 * drag mode.
1831 */
1832 return mIsBeingDragged;
1833 }
1834
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001835 /**
1836 * @return Whether the specified motion event is actually happening over the content.
1837 */
1838 private boolean isInContentBounds(MotionEvent event) {
1839 return event.getY() < getHeight() - getEmptyBottomMargin();
1840 }
1841
Selim Cinek67b22602014-03-10 15:40:16 +01001842 private void setIsBeingDragged(boolean isDragged) {
1843 mIsBeingDragged = isDragged;
1844 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001845 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02001846 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01001847 }
1848 }
1849
1850 @Override
1851 public void onWindowFocusChanged(boolean hasWindowFocus) {
1852 super.onWindowFocusChanged(hasWindowFocus);
1853 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001854 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01001855 }
1856 }
Selim Cinekfab078b2014-03-27 22:45:58 +01001857
Selim Cinek1408eb52014-06-02 14:45:38 +02001858 public void removeLongPressCallback() {
1859 mSwipeHelper.removeLongPressCallback();
1860 }
1861
Selim Cinekfab078b2014-03-27 22:45:58 +01001862 @Override
1863 public boolean isScrolledToTop() {
1864 return mOwnScrollY == 0;
1865 }
1866
1867 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001868 public boolean isScrolledToBottom() {
1869 return mOwnScrollY >= getScrollRange();
1870 }
1871
1872 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01001873 public View getHostView() {
1874 return this;
1875 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001876
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001877 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001878 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02001879 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001880 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02001881 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001882 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02001883 }
1884 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001885 }
1886
Selim Cinek1685e632014-04-08 02:27:49 +02001887 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02001888 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02001889 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
1890 }
1891
1892 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02001893 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02001894 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02001895 if (!mIsExpanded) {
1896 mOwnScrollY = 0;
1897 }
Selim Cinek1685e632014-04-08 02:27:49 +02001898 }
1899
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001900 private void setIsExpanded(boolean isExpanded) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001901 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02001902 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinek1685e632014-04-08 02:27:49 +02001903 }
1904
Jorim Jaggibe565df2014-04-28 17:51:23 +02001905 @Override
1906 public void onHeightChanged(ExpandableView view) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001907 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001908 updateScrollPositionOnExpandInBottom(view);
1909 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02001910 notifyHeightChangeListener(view);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001911 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001912 }
1913
Selim Cineka5e211b2014-08-11 17:35:48 +02001914 @Override
1915 public void onReset(ExpandableView view) {
1916 mRequestViewResizeAnimationOnLayout = true;
Selim Cinek31094df2014-08-14 19:28:15 +02001917 mStackScrollAlgorithm.onReset(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02001918 }
1919
Selim Cinekf7a14c02014-07-07 14:01:46 +02001920 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
1921 if (view instanceof ExpandableNotificationRow) {
1922 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1923 if (row.isUserLocked()) {
1924 // We are actually expanding this view
1925 float endPosition = row.getTranslationY() + row.getActualHeight();
1926 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
1927 mBottomStackSlowDownHeight;
1928 if (endPosition > stackEnd) {
1929 mOwnScrollY += endPosition - stackEnd;
1930 mDisallowScrollingInThisMotion = true;
1931 }
1932 }
1933 }
1934 }
1935
Jorim Jaggibe565df2014-04-28 17:51:23 +02001936 public void setOnHeightChangedListener(
1937 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
1938 this.mOnHeightChangedListener = mOnHeightChangedListener;
1939 }
1940
Selim Cinek572bbd42014-04-25 16:43:27 +02001941 public void onChildAnimationFinished() {
Selim Cinek319bdc42014-05-01 23:01:58 +02001942 requestChildrenUpdate();
Selim Cinek572bbd42014-04-25 16:43:27 +02001943 }
1944
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001945 /**
1946 * See {@link AmbientState#setDimmed}.
1947 */
1948 public void setDimmed(boolean dimmed, boolean animate) {
Selim Cinek34c0a8d2014-05-12 00:01:43 +02001949 mStackScrollAlgorithm.setDimmed(dimmed);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001950 mAmbientState.setDimmed(dimmed);
Selim Cineka5eaa602014-05-12 21:27:47 +02001951 updatePadding(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02001952 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001953 mDimmedNeedsAnimation = true;
1954 mNeedsAnimation = true;
1955 }
1956 requestChildrenUpdate();
1957 }
1958
Jorim Jaggiae441282014-08-01 02:45:18 +02001959 public void setHideSensitive(boolean hideSensitive, boolean animate) {
1960 if (hideSensitive != mAmbientState.isHideSensitive()) {
1961 int childCount = getChildCount();
1962 for (int i = 0; i < childCount; i++) {
1963 ExpandableView v = (ExpandableView) getChildAt(i);
1964 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
1965 }
1966 mAmbientState.setHideSensitive(hideSensitive);
1967 if (animate && mAnimationsEnabled) {
1968 mHideSensitiveNeedsAnimation = true;
1969 mNeedsAnimation = true;
1970 }
1971 requestChildrenUpdate();
1972 }
1973 }
1974
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001975 /**
1976 * See {@link AmbientState#setActivatedChild}.
1977 */
Selim Cineka32ab602014-06-11 15:06:01 +02001978 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001979 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02001980 if (mAnimationsEnabled) {
1981 mActivateNeedsAnimation = true;
1982 mNeedsAnimation = true;
1983 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001984 requestChildrenUpdate();
1985 }
1986
Selim Cineka32ab602014-06-11 15:06:01 +02001987 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001988 return mAmbientState.getActivatedChild();
1989 }
1990
Selim Cinek572bbd42014-04-25 16:43:27 +02001991 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02001992 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02001993 if (mListener != null) {
1994 mListener.onChildLocationsChanged(this);
1995 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001996 }
1997
Selim Cinekc27437b2014-05-14 10:23:33 +02001998 public void setSpeedBumpView(SpeedBumpView speedBumpView) {
1999 mSpeedBumpView = speedBumpView;
2000 addView(speedBumpView);
2001 }
2002
2003 private void updateSpeedBump(boolean visible) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002004 boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
2005 if (visible != notGoneBefore) {
2006 int newVisibility = visible ? VISIBLE : GONE;
Selim Cinekc27437b2014-05-14 10:23:33 +02002007 mSpeedBumpView.setVisibility(newVisibility);
2008 if (visible) {
Selim Cinekc27437b2014-05-14 10:23:33 +02002009 // Make invisible to ensure that the appear animation is played.
2010 mSpeedBumpView.setInvisible();
Selim Cinekc27437b2014-05-14 10:23:33 +02002011 } else {
Jorim Jaggi8b730062014-07-31 22:19:52 +02002012 // TODO: This doesn't really work, because the view is already set to GONE above.
Selim Cinekc27437b2014-05-14 10:23:33 +02002013 generateRemoveAnimation(mSpeedBumpView);
2014 }
2015 }
2016 }
2017
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002018 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002019 updateSpeedBump(true /* visibility */);
2020 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002021 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002022 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002023 mGoToFullShadeDelay = delay;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002024 mNeedsAnimation = true;
2025 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002026 }
2027
Selim Cinek1408eb52014-06-02 14:45:38 +02002028 public void cancelExpandHelper() {
2029 mExpandHelper.cancel();
2030 }
2031
2032 public void setIntrinsicPadding(int intrinsicPadding) {
2033 mIntrinsicPadding = intrinsicPadding;
2034 }
2035
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002036 public int getIntrinsicPadding() {
2037 return mIntrinsicPadding;
2038 }
2039
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002040 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002041 * @return the y position of the first notification
2042 */
2043 public float getNotificationsTopY() {
2044 return mTopPadding + getTranslationY();
2045 }
2046
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002047 @Override
2048 public boolean shouldDelayChildPressedState() {
2049 return true;
2050 }
2051
Selim Cinekf54090e2014-06-17 17:24:51 -07002052 public void setScrimAlpha(float progress) {
Jorim Jaggi5ad92c52014-07-28 21:07:32 +02002053 if (progress != mAmbientState.getScrimAmount()) {
2054 mAmbientState.setScrimAmount(progress);
2055 requestChildrenUpdate();
2056 }
Selim Cinekf54090e2014-06-17 17:24:51 -07002057 }
2058
Jorim Jaggi457cc352014-06-02 22:47:42 +02002059 /**
John Spurlockbf370992014-06-17 13:58:31 -04002060 * See {@link AmbientState#setDark}.
2061 */
2062 public void setDark(boolean dark, boolean animate) {
2063 mAmbientState.setDark(dark);
2064 if (animate && mAnimationsEnabled) {
2065 mDarkNeedsAnimation = true;
2066 mNeedsAnimation = true;
2067 }
2068 requestChildrenUpdate();
2069 }
2070
Dan Sandlereceda3d2014-07-21 15:35:01 -04002071 public void setDismissView(DismissView dismissView) {
2072 mDismissView = dismissView;
2073 addView(mDismissView);
2074 }
2075
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002076 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2077 mEmptyShadeView = emptyShadeView;
2078 addView(mEmptyShadeView);
2079 }
2080
2081 public void updateEmptyShadeView(boolean visible) {
2082 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2083 int newVisibility = visible ? VISIBLE : GONE;
2084 if (oldVisibility != newVisibility) {
2085 if (oldVisibility == GONE) {
2086 if (mEmptyShadeView.willBeGone()) {
2087 mEmptyShadeView.cancelAnimation();
2088 } else {
2089 mEmptyShadeView.setInvisible();
2090 mEmptyShadeView.setVisibility(newVisibility);
2091 }
2092 mEmptyShadeView.setWillBeGone(false);
2093 updateContentHeight();
2094 } else {
2095 mEmptyShadeView.setWillBeGone(true);
2096 mEmptyShadeView.performVisibilityAnimation(false, new Runnable() {
2097 @Override
2098 public void run() {
2099 mEmptyShadeView.setVisibility(GONE);
2100 mEmptyShadeView.setWillBeGone(false);
2101 updateContentHeight();
2102 }
2103 });
2104 }
2105 }
2106 }
2107
Dan Sandlereceda3d2014-07-21 15:35:01 -04002108 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002109 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002110 int newVisibility = visible ? VISIBLE : GONE;
2111 if (oldVisibility != newVisibility) {
2112 if (oldVisibility == GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002113 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002114 mDismissView.cancelAnimation();
2115 } else {
2116 mDismissView.setInvisible();
2117 mDismissView.setVisibility(newVisibility);
2118 }
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002119 mDismissView.setWillBeGone(false);
2120 updateContentHeight();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002121 } else {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002122 mDismissView.setWillBeGone(true);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002123 mDismissView.performVisibilityAnimation(false, new Runnable() {
2124 @Override
2125 public void run() {
2126 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002127 mDismissView.setWillBeGone(false);
2128 updateContentHeight();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002129 }
2130 });
2131 }
2132 }
2133 }
2134
2135 public void setDismissAllInProgress(boolean dismissAllInProgress) {
2136 mDismissAllInProgress = dismissAllInProgress;
2137 }
2138
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002139 public boolean isDismissViewNotGone() {
2140 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2141 }
2142
2143 public boolean isDismissViewVisible() {
2144 return mDismissView.isVisible();
2145 }
2146
2147 public int getDismissViewHeight() {
2148 return mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2149 }
2150
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002151 public float getBottomMostNotificationBottom() {
2152 final int count = getChildCount();
2153 float max = 0;
2154 for (int childIdx = 0; childIdx < count; childIdx++) {
2155 ExpandableView child = (ExpandableView) getChildAt(childIdx);
2156 if (child.getVisibility() == GONE) {
2157 continue;
2158 }
2159 float bottom = child.getTranslationY() + child.getActualHeight();
2160 if (bottom > max) {
2161 max = bottom;
2162 }
2163 }
2164 return max + getTranslationY();
2165 }
2166
John Spurlockbf370992014-06-17 13:58:31 -04002167 /**
Selim Cinek3afd00e2014-08-11 22:32:57 +02002168 * @param qsMinHeight The minimum height of the quick settings including padding
2169 * See {@link StackScrollAlgorithm#updateIsSmallScreen}.
2170 */
2171 public void updateIsSmallScreen(int qsMinHeight) {
2172 mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight);
2173 }
2174
2175 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002176 * A listener that is notified when some child locations might have changed.
2177 */
2178 public interface OnChildLocationsChangedListener {
2179 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2180 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002181
Jorim Jaggi290600a2014-05-30 17:02:20 +02002182 /**
2183 * A listener that gets notified when the overscroll at the top has changed.
2184 */
2185 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002186
2187 /**
2188 * Notifies a listener that the overscroll has changed.
2189 *
2190 * @param amount the amount of overscroll, in pixels
2191 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2192 * unrubberbanded motion to directly expand overscroll view (e.g expand
2193 * QS)
2194 */
2195 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02002196
2197 /**
2198 * Notify a listener that the scroller wants to escape from the scrolling motion and
2199 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2200 *
2201 * @param velocity The velocity that the Scroller had when over flinging
2202 * @param open Should the fling open or close the overscroll view.
2203 */
2204 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002205 }
2206
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002207 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02002208
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002209 static AnimationFilter[] FILTERS = new AnimationFilter[] {
2210
2211 // ANIMATION_TYPE_ADD
2212 new AnimationFilter()
2213 .animateAlpha()
2214 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002215 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002216 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002217 .animateZ()
2218 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002219
2220 // ANIMATION_TYPE_REMOVE
2221 new AnimationFilter()
2222 .animateAlpha()
2223 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002224 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002225 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002226 .animateZ()
2227 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002228
2229 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2230 new AnimationFilter()
2231 .animateAlpha()
2232 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002233 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002234 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002235 .animateZ()
2236 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002237
2238 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2239 new AnimationFilter()
2240 .animateAlpha()
2241 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002242 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002243 .animateY()
2244 .animateDimmed()
2245 .animateScale()
2246 .animateZ(),
2247
2248 // ANIMATION_TYPE_START_DRAG
2249 new AnimationFilter()
2250 .animateAlpha(),
2251
2252 // ANIMATION_TYPE_SNAP_BACK
2253 new AnimationFilter()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002254 .animateAlpha()
2255 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002256
2257 // ANIMATION_TYPE_ACTIVATED_CHILD
2258 new AnimationFilter()
2259 .animateScale()
2260 .animateAlpha(),
2261
2262 // ANIMATION_TYPE_DIMMED
2263 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002264 .animateY()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002265 .animateScale()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002266 .animateDimmed(),
2267
2268 // ANIMATION_TYPE_CHANGE_POSITION
2269 new AnimationFilter()
2270 .animateAlpha()
2271 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002272 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002273 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04002274 .animateZ(),
2275
2276 // ANIMATION_TYPE_DARK
2277 new AnimationFilter()
2278 .animateDark(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002279
2280 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2281 new AnimationFilter()
2282 .animateAlpha()
2283 .animateHeight()
2284 .animateTopInset()
2285 .animateY()
2286 .animateDimmed()
2287 .animateScale()
Jorim Jaggiae441282014-08-01 02:45:18 +02002288 .animateZ()
2289 .hasDelays(),
2290
2291 // ANIMATION_TYPE_HIDE_SENSITIVE
2292 new AnimationFilter()
2293 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02002294
2295 // ANIMATION_TYPE_VIEW_RESIZE
2296 new AnimationFilter()
2297 .animateAlpha()
2298 .animateHeight()
2299 .animateTopInset()
2300 .animateY()
2301 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002302 };
2303
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002304 static int[] LENGTHS = new int[] {
2305
2306 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002307 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002308
2309 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002310 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002311
2312 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2313 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2314
2315 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2316 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2317
2318 // ANIMATION_TYPE_START_DRAG
2319 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2320
2321 // ANIMATION_TYPE_SNAP_BACK
2322 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2323
2324 // ANIMATION_TYPE_ACTIVATED_CHILD
2325 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2326
2327 // ANIMATION_TYPE_DIMMED
2328 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002329
2330 // ANIMATION_TYPE_CHANGE_POSITION
2331 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04002332
2333 // ANIMATION_TYPE_DARK
2334 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002335
2336 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2337 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02002338
2339 // ANIMATION_TYPE_HIDE_SENSITIVE
2340 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02002341
2342 // ANIMATION_TYPE_VIEW_RESIZE
2343 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002344 };
2345
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002346 static final int ANIMATION_TYPE_ADD = 0;
2347 static final int ANIMATION_TYPE_REMOVE = 1;
2348 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
2349 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
2350 static final int ANIMATION_TYPE_START_DRAG = 4;
2351 static final int ANIMATION_TYPE_SNAP_BACK = 5;
2352 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
2353 static final int ANIMATION_TYPE_DIMMED = 7;
2354 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04002355 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002356 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02002357 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02002358 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002359
Selim Cinek572bbd42014-04-25 16:43:27 +02002360 final long eventStartTime;
2361 final View changingView;
2362 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002363 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002364 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002365 View viewAfterChangingView;
Selim Cinek572bbd42014-04-25 16:43:27 +02002366
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002367 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002368 this(view, type, LENGTHS[type]);
2369 }
2370
2371 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002372 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
2373 changingView = view;
2374 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002375 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002376 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002377 }
2378
2379 /**
2380 * Combines the length of several animation events into a single value.
2381 *
2382 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002383 * @return The combined length. Depending on the event types, this might be the maximum of
2384 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002385 */
2386 static long combineLength(ArrayList<AnimationEvent> events) {
2387 long length = 0;
2388 int size = events.size();
2389 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002390 AnimationEvent event = events.get(i);
2391 length = Math.max(length, event.length);
2392 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
2393 return event.length;
2394 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002395 }
2396 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02002397 }
2398 }
2399
Selim Cinek67b22602014-03-10 15:40:16 +01002400}