blob: 16ff3dc98a209ab4b95ab467ee1fcd8afdc216eb [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 Cinek3a9c10a2014-10-28 14:21:10 +010043import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020044import com.android.systemui.statusbar.StatusBarState;
Selim Cinek19c8c702014-08-25 22:09:19 +020045import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010046import com.android.systemui.statusbar.policy.ScrollAdapter;
Jorim Jaggi290600a2014-05-30 17:02:20 +020047import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
Selim Cinek67b22602014-03-10 15:40:16 +010048
Selim Cinek572bbd42014-04-25 16:43:27 +020049import java.util.ArrayList;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020050import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020051
Selim Cinek67b22602014-03-10 15:40:16 +010052/**
53 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
54 */
55public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020056 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
57 ExpandableView.OnHeightChangedListener {
Selim Cinek67b22602014-03-10 15:40:16 +010058
59 private static final String TAG = "NotificationStackScrollLayout";
60 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020061 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
62 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020063 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010064
65 /**
66 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
67 */
68 private static final int INVALID_POINTER = -1;
69
Selim Cinek1408eb52014-06-02 14:45:38 +020070 private ExpandHelper mExpandHelper;
Selim Cinek67b22602014-03-10 15:40:16 +010071 private SwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +020072 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +010073 private int mCurrentStackHeight = Integer.MAX_VALUE;
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +010074
75 /**
76 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
77 * externally from {@link #setStackHeight}
78 */
79 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +010080 private int mOwnScrollY;
81 private int mMaxLayoutHeight;
82
83 private VelocityTracker mVelocityTracker;
84 private OverScroller mScroller;
85 private int mTouchSlop;
86 private int mMinimumVelocity;
87 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +010088 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020089 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +010090 private boolean mIsBeingDragged;
91 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +020092 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +010093 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010094 private boolean mTouchIsClick;
95 private float mInitialTouchX;
96 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +010097
98 private int mSidePaddings;
99 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100100 private int mContentHeight;
101 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200102 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100103 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100104 private int mPaddingBetweenElements;
Selim Cineka5eaa602014-05-12 21:27:47 +0200105 private int mPaddingBetweenElementsDimmed;
106 private int mPaddingBetweenElementsNormal;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200107 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200108 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100109
110 /**
111 * The algorithm which calculates the properties for our children
112 */
113 private StackScrollAlgorithm mStackScrollAlgorithm;
114
115 /**
116 * The current State this Layout is in
117 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200118 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200119 private AmbientState mAmbientState = new AmbientState();
Selim Cinek572bbd42014-04-25 16:43:27 +0200120 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
121 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
Selim Cinekeb973562014-05-02 17:07:49 +0200122 private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
123 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200124 private ArrayList<View> mChildrenChangingPositions = new ArrayList<View>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200125 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200126 private ArrayList<AnimationEvent> mAnimationEvents
127 = new ArrayList<AnimationEvent>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200128 private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
129 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200130 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200131 private boolean mChangePositionInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200132
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200133 /**
134 * The raw amount of the overScroll on the top, which is not rubber-banded.
135 */
136 private float mOverScrolledTopPixels;
137
138 /**
139 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
140 */
141 private float mOverScrolledBottomPixels;
142
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200143 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200144 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200145 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100146 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200147 private boolean mNeedsAnimation;
148 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200149 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200150 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400151 private boolean mDarkNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200152 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200153 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200154 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200155 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200156 private SpeedBumpView mSpeedBumpView;
157 private boolean mIsExpansionChanging;
Selim Cinek1408eb52014-06-02 14:45:38 +0200158 private boolean mExpandingNotification;
159 private boolean mExpandedInThisMotion;
160 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400161 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200162 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400163 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200164
165 /**
166 * Was the scroller scrolled to the top when the down motion was observed?
167 */
168 private boolean mScrolledToTopOnFirstDown;
169
170 /**
171 * The minimal amount of over scroll which is needed in order to switch to the quick settings
172 * when over scrolling on a expanded card.
173 */
174 private float mMinTopOverScrollToEscape;
175 private int mIntrinsicPadding;
176 private int mNotificationTopPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200177 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200178 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200179 private boolean mRequestViewResizeAnimationOnLayout;
180 private boolean mNeedViewResizeAnimation;
Selim Cinekd9acca52014-09-01 22:33:25 +0200181 private boolean mEverythingNeedsAnimation;
Selim Cinek1408eb52014-06-02 14:45:38 +0200182
183 /**
184 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
185 * This is needed to avoid scrolling too far after the notification was collapsed in the same
186 * motion.
187 */
188 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400189 private SwipeHelper.LongPressListener mLongPressListener;
Selim Cinek1408eb52014-06-02 14:45:38 +0200190
191 /**
192 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
193 * animating.
194 */
195 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200196 private ViewGroup mScrollView;
197 private boolean mInterceptDelegateEnabled;
198 private boolean mDelegateToScrollView;
Selim Cinekf7a14c02014-07-07 14:01:46 +0200199 private boolean mDisallowScrollingInThisMotion;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200200 private long mGoToFullShadeDelay;
Jorim Jaggi56306252014-07-03 00:40:09 +0200201
Selim Cinek1f553cf2014-05-02 12:01:36 +0200202 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200203 = new ViewTreeObserver.OnPreDrawListener() {
204 @Override
205 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200206 updateChildren();
207 mChildrenUpdateRequested = false;
208 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200209 return true;
210 }
211 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200212 private PhoneStatusBar mPhoneStatusBar;
Selim Cinek67b22602014-03-10 15:40:16 +0100213
214 public NotificationStackScrollLayout(Context context) {
215 this(context, null);
216 }
217
218 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
219 this(context, attrs, 0);
220 }
221
222 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
223 this(context, attrs, defStyleAttr, 0);
224 }
225
226 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
227 int defStyleRes) {
228 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200229 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
230 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
231 mExpandHelper = new ExpandHelper(getContext(), this,
232 minHeight, maxHeight);
233 mExpandHelper.setEventSource(this);
234 mExpandHelper.setScrollAdapter(this);
235
236 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
237 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinek67b22602014-03-10 15:40:16 +0100238 initView(context);
239 if (DEBUG) {
240 setWillNotDraw(false);
241 mDebugPaint = new Paint();
242 mDebugPaint.setColor(0xffff0000);
243 mDebugPaint.setStrokeWidth(2);
244 mDebugPaint.setStyle(Paint.Style.STROKE);
245 }
246 }
247
248 @Override
249 protected void onDraw(Canvas canvas) {
250 if (DEBUG) {
251 int y = mCollapsedSize;
252 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200253 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200254 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200255 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
256 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100257 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
258 y = (int) getLayoutHeight();
259 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200260 y = getHeight() - getEmptyBottomMargin();
261 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100262 }
263 }
264
265 private void initView(Context context) {
266 mScroller = new OverScroller(getContext());
267 setFocusable(true);
268 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200269 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100270 final ViewConfiguration configuration = ViewConfiguration.get(context);
271 mTouchSlop = configuration.getScaledTouchSlop();
272 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
273 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100274 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100275
276 mSidePaddings = context.getResources()
277 .getDimensionPixelSize(R.dimen.notification_side_padding);
Selim Cinek67b22602014-03-10 15:40:16 +0100278 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200279 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100280 mBottomStackPeekSize = context.getResources()
281 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinek67b22602014-03-10 15:40:16 +0100282 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200283 mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed());
Selim Cineka5eaa602014-05-12 21:27:47 +0200284 mPaddingBetweenElementsDimmed = context.getResources()
285 .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
286 mPaddingBetweenElementsNormal = context.getResources()
287 .getDimensionPixelSize(R.dimen.notification_padding);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200288 updatePadding(mAmbientState.isDimmed());
Selim Cinek1408eb52014-06-02 14:45:38 +0200289 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
290 R.dimen.min_top_overscroll_to_qs);
291 mNotificationTopPadding = getResources().getDimensionPixelSize(
292 R.dimen.notifications_top_padding);
Selim Cinekd83771e2014-07-04 16:45:31 +0200293 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
294 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200295 }
296
297 private void updatePadding(boolean dimmed) {
Jorim Jaggid7c1fae2014-08-13 18:27:47 +0200298 mPaddingBetweenElements = dimmed && mStackScrollAlgorithm.shouldScaleDimmed()
Selim Cineka5eaa602014-05-12 21:27:47 +0200299 ? mPaddingBetweenElementsDimmed
300 : mPaddingBetweenElementsNormal;
301 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
302 updateContentHeight();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200303 notifyHeightChangeListener(null);
304 }
305
306 private void notifyHeightChangeListener(ExpandableView view) {
307 if (mOnHeightChangedListener != null) {
308 mOnHeightChangedListener.onHeightChanged(view);
309 }
Selim Cinek67b22602014-03-10 15:40:16 +0100310 }
311
312 @Override
313 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
314 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
315 int mode = MeasureSpec.getMode(widthMeasureSpec);
316 int size = MeasureSpec.getSize(widthMeasureSpec);
317 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
318 measureChildren(childMeasureSpec, heightMeasureSpec);
319 }
320
321 @Override
322 protected void onLayout(boolean changed, int l, int t, int r, int b) {
323
324 // we layout all our children centered on the top
325 float centerX = getWidth() / 2.0f;
326 for (int i = 0; i < getChildCount(); i++) {
327 View child = getChildAt(i);
328 float width = child.getMeasuredWidth();
329 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100330 child.layout((int) (centerX - width / 2.0f),
331 0,
332 (int) (centerX + width / 2.0f),
333 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100334 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200335 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100336 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200337 clampScrollPosition();
Selim Cineka5e211b2014-08-11 17:35:48 +0200338 requestAnimationOnViewResize();
Selim Cinek319bdc42014-05-01 23:01:58 +0200339 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100340 }
341
Selim Cineka5e211b2014-08-11 17:35:48 +0200342 private void requestAnimationOnViewResize() {
343 if (mRequestViewResizeAnimationOnLayout && mIsExpanded && mAnimationsEnabled) {
344 mNeedViewResizeAnimation = true;
345 mNeedsAnimation = true;
346 }
347 mRequestViewResizeAnimationOnLayout = false;
348 }
349
Selim Cinekc27437b2014-05-14 10:23:33 +0200350 public void updateSpeedBumpIndex(int newIndex) {
351 int currentIndex = indexOfChild(mSpeedBumpView);
352
353 // If we are currently layouted before the new speed bump index, we have to decrease it.
354 boolean validIndex = newIndex > 0;
355 if (newIndex > getChildCount() - 1) {
356 validIndex = false;
357 newIndex = -1;
358 }
359 if (validIndex && currentIndex != newIndex) {
360 changeViewPosition(mSpeedBumpView, newIndex);
361 }
362 updateSpeedBump(validIndex);
363 mAmbientState.setSpeedBumpIndex(newIndex);
364 }
365
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200366 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
367 mListener = listener;
368 }
369
370 /**
371 * Returns the location the given child is currently rendered at.
372 *
373 * @param child the child to get the location for
374 * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants
375 */
376 public int getChildLocation(View child) {
377 ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
378 if (childViewState == null) {
379 return ViewState.LOCATION_UNKNOWN;
380 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100381 if (childViewState.gone) {
382 return ViewState.LOCATION_GONE;
383 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200384 return childViewState.location;
385 }
386
Selim Cinek67b22602014-03-10 15:40:16 +0100387 private void setMaxLayoutHeight(int maxLayoutHeight) {
388 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200389 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100390 }
391
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200392 private void updateAlgorithmHeightAndPadding() {
Selim Cinek67b22602014-03-10 15:40:16 +0100393 mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200394 mStackScrollAlgorithm.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100395 }
396
397 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200398 * @return whether the height of the layout needs to be adapted, in order to ensure that the
399 * last child is not in the bottom stack.
400 */
401 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200402 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200403 }
404
Selim Cinek4a1ac842014-05-01 15:51:58 +0200405 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100406 * Updates the children views according to the stack scroll algorithm. Call this whenever
407 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
408 */
409 private void updateChildren() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200410 mAmbientState.setScrollY(mOwnScrollY);
411 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200412 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200413 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100414 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200415 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100416 }
417 }
418
Selim Cinek319bdc42014-05-01 23:01:58 +0200419 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200420 if (!mChildrenUpdateRequested) {
421 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
422 mChildrenUpdateRequested = true;
423 invalidate();
424 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200425 }
426
Selim Cinek67b22602014-03-10 15:40:16 +0100427 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200428 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100429 }
430
Selim Cinekf7a14c02014-07-07 14:01:46 +0200431 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100432 int scrollRange = getScrollRange();
433 if (scrollRange < mOwnScrollY) {
434 mOwnScrollY = scrollRange;
435 }
436 }
437
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200438 public int getTopPadding() {
439 return mTopPadding;
440 }
441
Selim Cinek1408eb52014-06-02 14:45:38 +0200442 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200443 if (mTopPadding != topPadding) {
444 mTopPadding = topPadding;
445 updateAlgorithmHeightAndPadding();
446 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200447 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200448 mTopPaddingNeedsAnimation = true;
449 mNeedsAnimation = true;
450 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200451 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200452 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200453 }
454 }
455
456 /**
457 * Update the height of the stack to a new height.
458 *
459 * @param height the new height of the stack
460 */
461 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100462 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200463 setIsExpanded(height > 0.0f);
464 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200465 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200466 int stackHeight;
Jorim Jaggi58bef332014-11-21 18:10:32 +0100467 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= minStackHeight
468 || getNotGoneChildCount() == 0) {
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200469 setTranslationY(mTopPaddingOverflow);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200470 stackHeight = newStackHeight;
471 } else {
472
473 // We did not reach the position yet where we actually start growing,
474 // so we translate the stack upwards.
475 int translationY = (newStackHeight - minStackHeight);
476 // A slight parallax effect is introduced in order for the stack to catch up with
477 // the top card.
Jorim Jaggi58bef332014-11-21 18:10:32 +0100478 float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
479 / minStackHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200480 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekd83771e2014-07-04 16:45:31 +0200481 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
482 mCollapseSecondCardPadding);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200483 setTranslationY(translationY - mTopPadding);
484 stackHeight = (int) (height - (translationY - mTopPadding));
485 }
486 if (stackHeight != mCurrentStackHeight) {
487 mCurrentStackHeight = stackHeight;
488 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200489 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200490 }
Selim Cinek67b22602014-03-10 15:40:16 +0100491 }
492
493 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100494 * 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 +0100495 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
496 *
497 * @return either the layout height or the externally defined height, whichever is smaller
498 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200499 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100500 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
501 }
502
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100503 public int getItemHeight() {
504 return mCollapsedSize;
505 }
506
507 public int getBottomStackPeekSize() {
508 return mBottomStackPeekSize;
509 }
510
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200511 public int getCollapseSecondCardPadding() {
512 return mCollapseSecondCardPadding;
513 }
514
Dan Sandler4247a5c2014-07-23 15:58:08 -0400515 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100516 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400517 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100518 }
519
Jorim Jaggi56306252014-07-03 00:40:09 +0200520 public void setScrollView(ViewGroup scrollView) {
521 mScrollView = scrollView;
522 }
523
524 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
525 mInterceptDelegateEnabled = interceptDelegateEnabled;
526 }
527
Selim Cinek67b22602014-03-10 15:40:16 +0100528 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400529 if (mDismissAllInProgress) {
530 return;
531 }
Selim Cinek67b22602014-03-10 15:40:16 +0100532 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
533 final View veto = v.findViewById(R.id.veto);
534 if (veto != null && veto.getVisibility() != View.GONE) {
535 veto.performClick();
536 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100537 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200538 if (mDragAnimPendingChildren.contains(v)) {
539 // We start the swipe and finish it in the same frame, we don't want any animation
540 // for the drag
541 mDragAnimPendingChildren.remove(v);
542 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200543 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200544 mAmbientState.onDragFinished(v);
Selim Cinekeb973562014-05-02 17:07:49 +0200545 }
546
547 @Override
548 public void onChildSnappedBack(View animView) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200549 mAmbientState.onDragFinished(animView);
Selim Cinekeb973562014-05-02 17:07:49 +0200550 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200551 if (mAnimationsEnabled) {
552 mSnappedBackChildren.add(animView);
553 mNeedsAnimation = true;
554 }
Selim Cinekeb973562014-05-02 17:07:49 +0200555 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200556 } else {
557 // We start the swipe and snap back in the same frame, we don't want any animation
558 mDragAnimPendingChildren.remove(animView);
559 }
Selim Cinek67b22602014-03-10 15:40:16 +0100560 }
561
Adrian Roos5d9cc662014-05-28 17:08:13 +0200562 @Override
563 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
564 return false;
565 }
566
Selim Cinek34cf5c42014-09-26 15:39:00 +0200567 @Override
568 public float getFalsingThresholdFactor() {
569 return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
570 }
571
Selim Cinek67b22602014-03-10 15:40:16 +0100572 public void onBeginDrag(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100573 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200574 mAmbientState.onBeginDrag(v);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200575 if (mAnimationsEnabled) {
576 mDragAnimPendingChildren.add(v);
577 mNeedsAnimation = true;
578 }
Selim Cinekeb973562014-05-02 17:07:49 +0200579 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100580 }
581
582 public void onDragCancelled(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100583 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100584 }
585
586 public View getChildAtPosition(MotionEvent ev) {
587 return getChildAtPosition(ev.getX(), ev.getY());
588 }
589
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200590 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100591 int[] location = new int[2];
592 getLocationOnScreen(location);
Selim Cinek319bdc42014-05-01 23:01:58 +0200593 return getChildAtPosition(touchX - location[0], touchY - location[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100594 }
595
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200596 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100597 // find the view under the pointer, accounting for GONE views
598 final int count = getChildCount();
599 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200600 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100601 if (slidingChild.getVisibility() == GONE
602 || slidingChild instanceof StackScrollerDecorView
603 || slidingChild == mSpeedBumpView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100604 continue;
605 }
Selim Cinek89faff12014-06-19 16:29:04 -0700606 float childTop = slidingChild.getTranslationY();
607 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200608 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200609
610 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
611 // camera affordance).
612 int left = 0;
613 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100614
615 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
616 return slidingChild;
617 }
618 }
619 return null;
620 }
621
622 public boolean canChildBeExpanded(View v) {
623 return v instanceof ExpandableNotificationRow
624 && ((ExpandableNotificationRow) v).isExpandable();
625 }
626
627 public void setUserExpandedChild(View v, boolean userExpanded) {
628 if (v instanceof ExpandableNotificationRow) {
629 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
630 }
631 }
632
633 public void setUserLockedChild(View v, boolean userLocked) {
634 if (v instanceof ExpandableNotificationRow) {
635 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
636 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200637 removeLongPressCallback();
638 requestDisallowInterceptTouchEvent(true);
639 }
640
641 @Override
642 public void expansionStateChanged(boolean isExpanding) {
643 mExpandingNotification = isExpanding;
644 if (!mExpandedInThisMotion) {
645 mMaxScrollAfterExpand = mOwnScrollY;
646 mExpandedInThisMotion = true;
647 }
648 }
649
650 public void setScrollingEnabled(boolean enable) {
651 mScrollingEnabled = enable;
652 }
653
654 public void setExpandingEnabled(boolean enable) {
655 mExpandHelper.setEnabled(enable);
656 }
657
658 private boolean isScrollingEnabled() {
659 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100660 }
661
662 public View getChildContentView(View v) {
663 return v;
664 }
665
666 public boolean canChildBeDismissed(View v) {
667 final View veto = v.findViewById(R.id.veto);
668 return (veto != null && veto.getVisibility() != View.GONE);
669 }
670
Selim Cinek19c8c702014-08-25 22:09:19 +0200671 @Override
672 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200673 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200674 }
675
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100676 private void setSwipingInProgress(boolean isSwiped) {
677 mSwipingInProgress = isSwiped;
678 if(isSwiped) {
679 requestDisallowInterceptTouchEvent(true);
680 }
Selim Cinek67b22602014-03-10 15:40:16 +0100681 }
682
683 @Override
684 protected void onConfigurationChanged(Configuration newConfig) {
685 super.onConfigurationChanged(newConfig);
686 float densityScale = getResources().getDisplayMetrics().density;
687 mSwipeHelper.setDensityScale(densityScale);
688 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
689 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
690 initView(getContext());
691 }
692
Dan Sandlereceda3d2014-07-21 15:35:01 -0400693 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
694 child.setClipBounds(null);
695 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100696 }
697
698 @Override
699 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200700 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
701 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Jorim Jaggi56306252014-07-03 00:40:09 +0200702 if (mDelegateToScrollView) {
703 if (isCancelOrUp) {
704 mDelegateToScrollView = false;
705 }
706 transformTouchEvent(ev, this, mScrollView);
707 return mScrollView.onTouchEvent(ev);
708 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100709 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200710 boolean expandWantsIt = false;
Jorim Jaggi341d1822014-07-03 23:34:13 +0200711 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200712 if (isCancelOrUp) {
713 mExpandHelper.onlyObserveMovements(false);
714 }
715 boolean wasExpandingBefore = mExpandingNotification;
716 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200717 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
718 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200719 dispatchDownEventToScroller(ev);
720 }
721 }
Selim Cinek67b22602014-03-10 15:40:16 +0100722 boolean scrollerWantsIt = false;
Selim Cinekf7a14c02014-07-07 14:01:46 +0200723 if (!mSwipingInProgress && !mExpandingNotification && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100724 scrollerWantsIt = onScrollTouch(ev);
725 }
726 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200727 if (!mIsBeingDragged
728 && !mExpandingNotification
729 && !mExpandedInThisMotion
730 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100731 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
732 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200733 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
734 }
735
736 private void dispatchDownEventToScroller(MotionEvent ev) {
737 MotionEvent downEvent = MotionEvent.obtain(ev);
738 downEvent.setAction(MotionEvent.ACTION_DOWN);
739 onScrollTouch(downEvent);
740 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100741 }
742
743 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200744 if (!isScrollingEnabled()) {
745 return false;
746 }
Selim Cinek67b22602014-03-10 15:40:16 +0100747 initVelocityTrackerIfNotExists();
748 mVelocityTracker.addMovement(ev);
749
750 final int action = ev.getAction();
751
752 switch (action & MotionEvent.ACTION_MASK) {
753 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +0200754 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +0100755 return false;
756 }
757 boolean isBeingDragged = !mScroller.isFinished();
758 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +0100759
760 /*
761 * If being flinged and user touches, stop the fling. isFinished
762 * will be false if being flinged.
763 */
764 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200765 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100766 }
767
768 // Remember where the motion event started
769 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +0200770 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +0100771 mActivePointerId = ev.getPointerId(0);
772 break;
773 }
774 case MotionEvent.ACTION_MOVE:
775 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
776 if (activePointerIndex == -1) {
777 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
778 break;
779 }
780
781 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +0200782 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +0100783 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200784 final int xDiff = Math.abs(x - mDownX);
785 final int yDiff = Math.abs(deltaY);
786 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +0100787 setIsBeingDragged(true);
788 if (deltaY > 0) {
789 deltaY -= mTouchSlop;
790 } else {
791 deltaY += mTouchSlop;
792 }
793 }
794 if (mIsBeingDragged) {
795 // Scroll to follow the motion event
796 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200797 int range = getScrollRange();
798 if (mExpandedInThisMotion) {
799 range = Math.min(range, mMaxScrollAfterExpand);
800 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200801
802 float scrollAmount;
803 if (deltaY < 0) {
804 scrollAmount = overScrollDown(deltaY);
805 } else {
806 scrollAmount = overScrollUp(deltaY, range);
807 }
Selim Cinek67b22602014-03-10 15:40:16 +0100808
809 // Calling overScrollBy will call onOverScrolled, which
810 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200811 if (scrollAmount != 0.0f) {
812 // The scrolling motion could not be compensated with the
813 // existing overScroll, we have to scroll the view
814 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
815 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +0100816 }
Selim Cinek67b22602014-03-10 15:40:16 +0100817 }
818 break;
819 case MotionEvent.ACTION_UP:
820 if (mIsBeingDragged) {
821 final VelocityTracker velocityTracker = mVelocityTracker;
822 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
823 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
824
Selim Cinek1408eb52014-06-02 14:45:38 +0200825 if (shouldOverScrollFling(initialVelocity)) {
826 onOverScrollFling(true, initialVelocity);
827 } else {
828 if (getChildCount() > 0) {
829 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
830 float currentOverScrollTop = getCurrentOverScrollAmount(true);
831 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
832 fling(-initialVelocity);
833 } else {
834 onOverScrollFling(false, initialVelocity);
835 }
836 } else {
837 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
838 getScrollRange())) {
839 postInvalidateOnAnimation();
840 }
Selim Cinek67b22602014-03-10 15:40:16 +0100841 }
842 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200843 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700844
845 mActivePointerId = INVALID_POINTER;
846 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +0100847 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700848
Selim Cinek67b22602014-03-10 15:40:16 +0100849 break;
850 case MotionEvent.ACTION_CANCEL:
851 if (mIsBeingDragged && getChildCount() > 0) {
852 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
853 postInvalidateOnAnimation();
854 }
855 mActivePointerId = INVALID_POINTER;
856 endDrag();
857 }
858 break;
859 case MotionEvent.ACTION_POINTER_DOWN: {
860 final int index = ev.getActionIndex();
861 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +0200862 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +0100863 mActivePointerId = ev.getPointerId(index);
864 break;
865 }
866 case MotionEvent.ACTION_POINTER_UP:
867 onSecondaryPointerUp(ev);
868 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +0200869 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +0100870 break;
871 }
872 return true;
873 }
874
Selim Cinek1408eb52014-06-02 14:45:38 +0200875 private void onOverScrollFling(boolean open, int initialVelocity) {
876 if (mOverscrollTopChangedListener != null) {
877 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
878 }
879 mDontReportNextOverScroll = true;
880 setOverScrollAmount(0.0f, true, false);
881 }
882
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200883 /**
884 * Perform a scroll upwards and adapt the overscroll amounts accordingly
885 *
886 * @param deltaY The amount to scroll upwards, has to be positive.
887 * @return The amount of scrolling to be performed by the scroller,
888 * not handled by the overScroll amount.
889 */
890 private float overScrollUp(int deltaY, int range) {
891 deltaY = Math.max(deltaY, 0);
892 float currentTopAmount = getCurrentOverScrollAmount(true);
893 float newTopAmount = currentTopAmount - deltaY;
894 if (currentTopAmount > 0) {
895 setOverScrollAmount(newTopAmount, true /* onTop */,
896 false /* animate */);
897 }
898 // Top overScroll might not grab all scrolling motion,
899 // we have to scroll as well.
900 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
901 float newScrollY = mOwnScrollY + scrollAmount;
902 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200903 if (!mExpandedInThisMotion) {
904 float currentBottomPixels = getCurrentOverScrolledPixels(false);
905 // We overScroll on the top
906 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
907 false /* onTop */,
908 false /* animate */);
909 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200910 mOwnScrollY = range;
911 scrollAmount = 0.0f;
912 }
913 return scrollAmount;
914 }
915
916 /**
917 * Perform a scroll downward and adapt the overscroll amounts accordingly
918 *
919 * @param deltaY The amount to scroll downwards, has to be negative.
920 * @return The amount of scrolling to be performed by the scroller,
921 * not handled by the overScroll amount.
922 */
923 private float overScrollDown(int deltaY) {
924 deltaY = Math.min(deltaY, 0);
925 float currentBottomAmount = getCurrentOverScrollAmount(false);
926 float newBottomAmount = currentBottomAmount + deltaY;
927 if (currentBottomAmount > 0) {
928 setOverScrollAmount(newBottomAmount, false /* onTop */,
929 false /* animate */);
930 }
931 // Bottom overScroll might not grab all scrolling motion,
932 // we have to scroll as well.
933 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
934 float newScrollY = mOwnScrollY + scrollAmount;
935 if (newScrollY < 0) {
936 float currentTopPixels = getCurrentOverScrolledPixels(true);
937 // We overScroll on the top
938 setOverScrolledPixels(currentTopPixels - newScrollY,
939 true /* onTop */,
940 false /* animate */);
941 mOwnScrollY = 0;
942 scrollAmount = 0.0f;
943 }
944 return scrollAmount;
945 }
946
Selim Cinek67b22602014-03-10 15:40:16 +0100947 private void onSecondaryPointerUp(MotionEvent ev) {
948 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
949 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
950 final int pointerId = ev.getPointerId(pointerIndex);
951 if (pointerId == mActivePointerId) {
952 // This was our active pointer going up. Choose a new
953 // active pointer and adjust accordingly.
954 // TODO: Make this decision more intelligent.
955 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
956 mLastMotionY = (int) ev.getY(newPointerIndex);
957 mActivePointerId = ev.getPointerId(newPointerIndex);
958 if (mVelocityTracker != null) {
959 mVelocityTracker.clear();
960 }
961 }
962 }
963
964 private void initVelocityTrackerIfNotExists() {
965 if (mVelocityTracker == null) {
966 mVelocityTracker = VelocityTracker.obtain();
967 }
968 }
969
970 private void recycleVelocityTracker() {
971 if (mVelocityTracker != null) {
972 mVelocityTracker.recycle();
973 mVelocityTracker = null;
974 }
975 }
976
977 private void initOrResetVelocityTracker() {
978 if (mVelocityTracker == null) {
979 mVelocityTracker = VelocityTracker.obtain();
980 } else {
981 mVelocityTracker.clear();
982 }
983 }
984
985 @Override
986 public void computeScroll() {
987 if (mScroller.computeScrollOffset()) {
988 // This is called at drawing time by ViewGroup.
989 int oldX = mScrollX;
990 int oldY = mOwnScrollY;
991 int x = mScroller.getCurrX();
992 int y = mScroller.getCurrY();
993
994 if (oldX != x || oldY != y) {
995 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200996 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
997 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +0200998 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200999 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1000 }
1001 }
Selim Cinek67b22602014-03-10 15:40:16 +01001002
1003 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001004 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001005 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001006 }
1007
1008 // Keep on drawing until the animation has finished.
1009 postInvalidateOnAnimation();
1010 }
1011 }
1012
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001013 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001014 protected boolean overScrollBy(int deltaX, int deltaY,
1015 int scrollX, int scrollY,
1016 int scrollRangeX, int scrollRangeY,
1017 int maxOverScrollX, int maxOverScrollY,
1018 boolean isTouchEvent) {
1019
1020 int newScrollY = scrollY + deltaY;
1021
1022 final int top = -maxOverScrollY;
1023 final int bottom = maxOverScrollY + scrollRangeY;
1024
1025 boolean clampedY = false;
1026 if (newScrollY > bottom) {
1027 newScrollY = bottom;
1028 clampedY = true;
1029 } else if (newScrollY < top) {
1030 newScrollY = top;
1031 clampedY = true;
1032 }
1033
1034 onOverScrolled(0, newScrollY, false, clampedY);
1035
1036 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001037 }
1038
1039 /**
1040 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1041 * overscroll effect based on numPixels. By default this will also cancel animations on the
1042 * same overScroll edge.
1043 *
1044 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1045 * the rubber-banding logic.
1046 * @param onTop Should the effect be applied on top of the scroller.
1047 * @param animate Should an animation be performed.
1048 */
1049 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001050 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001051 }
1052
1053 /**
1054 * Set the effective overScroll amount which will be directly reflected in the layout.
1055 * By default this will also cancel animations on the same overScroll edge.
1056 *
1057 * @param amount The amount to overScroll by.
1058 * @param onTop Should the effect be applied on top of the scroller.
1059 * @param animate Should an animation be performed.
1060 */
1061 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1062 setOverScrollAmount(amount, onTop, animate, true);
1063 }
1064
1065 /**
1066 * Set the effective overScroll amount which will be directly reflected in the layout.
1067 *
1068 * @param amount The amount to overScroll by.
1069 * @param onTop Should the effect be applied on top of the scroller.
1070 * @param animate Should an animation be performed.
1071 * @param cancelAnimators Should running animations be cancelled.
1072 */
1073 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1074 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001075 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1076 }
1077
1078 /**
1079 * Set the effective overScroll amount which will be directly reflected in the layout.
1080 *
1081 * @param amount The amount to overScroll by.
1082 * @param onTop Should the effect be applied on top of the scroller.
1083 * @param animate Should an animation be performed.
1084 * @param cancelAnimators Should running animations be cancelled.
1085 * @param isRubberbanded The value which will be passed to
1086 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1087 */
1088 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1089 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001090 if (cancelAnimators) {
1091 mStateAnimator.cancelOverScrollAnimators(onTop);
1092 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001093 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001094 }
1095
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001096 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1097 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001098 amount = Math.max(0, amount);
1099 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001100 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001101 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001102 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001103 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001104 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001105 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001106 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001107 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001108 }
1109 }
1110
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001111 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001112 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1113 if (mDontReportNextOverScroll) {
1114 mDontReportNextOverScroll = false;
1115 return;
1116 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001117 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001118 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001119 }
1120 }
1121
1122 public void setOverscrollTopChangedListener(
1123 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1124 mOverscrollTopChangedListener = overscrollTopChangedListener;
1125 }
1126
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001127 public float getCurrentOverScrollAmount(boolean top) {
1128 return mAmbientState.getOverScrollAmount(top);
1129 }
1130
1131 public float getCurrentOverScrolledPixels(boolean top) {
1132 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1133 }
1134
1135 private void setOverScrolledPixels(float amount, boolean onTop) {
1136 if (onTop) {
1137 mOverScrolledTopPixels = amount;
1138 } else {
1139 mOverScrolledBottomPixels = amount;
1140 }
1141 }
1142
Selim Cinek319bdc42014-05-01 23:01:58 +02001143 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001144 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001145 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001146 }
1147
1148 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001149 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001150 // Treat animating scrolls differently; see #computeScroll() for why.
1151 if (!mScroller.isFinished()) {
1152 final int oldX = mScrollX;
1153 final int oldY = mOwnScrollY;
1154 mScrollX = scrollX;
1155 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001156 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001157 springBack();
1158 } else {
1159 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1160 invalidateParentIfNeeded();
1161 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001162 float overScrollTop = getCurrentOverScrollAmount(true);
1163 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001164 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001165 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001166 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001167 }
Selim Cinek67b22602014-03-10 15:40:16 +01001168 }
Selim Cinek67b22602014-03-10 15:40:16 +01001169 } else {
1170 customScrollTo(scrollY);
1171 scrollTo(scrollX, mScrollY);
1172 }
1173 }
1174
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001175 private void springBack() {
1176 int scrollRange = getScrollRange();
1177 boolean overScrolledTop = mOwnScrollY <= 0;
1178 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1179 if (overScrolledTop || overScrolledBottom) {
1180 boolean onTop;
1181 float newAmount;
1182 if (overScrolledTop) {
1183 onTop = true;
1184 newAmount = -mOwnScrollY;
1185 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001186 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001187 } else {
1188 onTop = false;
1189 newAmount = mOwnScrollY - scrollRange;
1190 mOwnScrollY = scrollRange;
1191 }
1192 setOverScrollAmount(newAmount, onTop, false);
1193 setOverScrollAmount(0.0f, onTop, true);
1194 mScroller.forceFinished(true);
1195 }
1196 }
1197
Selim Cinek67b22602014-03-10 15:40:16 +01001198 private int getScrollRange() {
1199 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001200 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001201 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001202 int contentHeight = getContentHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001203 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cineka5eaa602014-05-12 21:27:47 +02001204 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1205 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001206 if (scrollRange > 0) {
1207 View lastChild = getLastChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001208 // We want to at least be able collapse the first item and not ending in a weird
1209 // end state.
1210 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1211 }
Selim Cinek67b22602014-03-10 15:40:16 +01001212 }
1213 return scrollRange;
1214 }
1215
Selim Cinek343e6e22014-04-11 21:23:30 +02001216 /**
1217 * @return the first child which has visibility unequal to GONE
1218 */
1219 private View getFirstChildNotGone() {
1220 int childCount = getChildCount();
1221 for (int i = 0; i < childCount; i++) {
1222 View child = getChildAt(i);
1223 if (child.getVisibility() != View.GONE) {
1224 return child;
1225 }
1226 }
1227 return null;
1228 }
1229
Selim Cinek4a1ac842014-05-01 15:51:58 +02001230 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001231 * @return The first child which has visibility unequal to GONE which is currently below the
1232 * given translationY or equal to it.
1233 */
1234 private View getFirstChildBelowTranlsationY(float translationY) {
1235 int childCount = getChildCount();
1236 for (int i = 0; i < childCount; i++) {
1237 View child = getChildAt(i);
1238 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1239 return child;
1240 }
1241 }
1242 return null;
1243 }
1244
1245 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001246 * @return the last child which has visibility unequal to GONE
1247 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001248 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001249 int childCount = getChildCount();
1250 for (int i = childCount - 1; i >= 0; i--) {
1251 View child = getChildAt(i);
1252 if (child.getVisibility() != View.GONE) {
1253 return child;
1254 }
1255 }
1256 return null;
1257 }
1258
Jorim Jaggi069cd032014-05-15 03:09:01 +02001259 /**
1260 * @return the number of children which have visibility unequal to GONE
1261 */
1262 public int getNotGoneChildCount() {
1263 int childCount = getChildCount();
1264 int count = 0;
1265 for (int i = 0; i < childCount; i++) {
1266 View child = getChildAt(i);
1267 if (child.getVisibility() != View.GONE) {
1268 count++;
1269 }
1270 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001271 if (mDismissView.willBeGone()) {
1272 count--;
1273 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02001274 if (mEmptyShadeView.willBeGone()) {
1275 count--;
1276 }
Jorim Jaggi069cd032014-05-15 03:09:01 +02001277 return count;
1278 }
1279
Selim Cinek343e6e22014-04-11 21:23:30 +02001280 private int getMaxExpandHeight(View view) {
1281 if (view instanceof ExpandableNotificationRow) {
1282 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001283 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001284 }
1285 return view.getHeight();
1286 }
1287
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001288 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001289 return mContentHeight;
1290 }
1291
1292 private void updateContentHeight() {
1293 int height = 0;
1294 for (int i = 0; i < getChildCount(); i++) {
1295 View child = getChildAt(i);
Jorim Jaggid4a57442014-04-10 02:45:55 +02001296 if (child.getVisibility() != View.GONE) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001297 if (height != 0) {
1298 // add the padding before this element
Jorim Jaggid4a57442014-04-10 02:45:55 +02001299 height += mPaddingBetweenElements;
1300 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001301 if (child instanceof ExpandableNotificationRow) {
1302 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001303 height += row.getIntrinsicHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001304 } else if (child instanceof ExpandableView) {
1305 ExpandableView expandableView = (ExpandableView) child;
1306 height += expandableView.getActualHeight();
1307 }
Selim Cinek67b22602014-03-10 15:40:16 +01001308 }
1309 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001310 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001311 }
1312
1313 /**
1314 * Fling the scroll view
1315 *
1316 * @param velocityY The initial velocity in the Y direction. Positive
1317 * numbers mean that the finger/cursor is moving down the screen,
1318 * which means we want to scroll towards the top.
1319 */
1320 private void fling(int velocityY) {
1321 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001322 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001323
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001324 float topAmount = getCurrentOverScrollAmount(true);
1325 float bottomAmount = getCurrentOverScrollAmount(false);
1326 if (velocityY < 0 && topAmount > 0) {
1327 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001328 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001329 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001330 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001331 * mOverflingDistance + topAmount;
1332 } else if (velocityY > 0 && bottomAmount > 0) {
1333 mOwnScrollY += bottomAmount;
1334 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001335 mMaxOverScroll = Math.abs(velocityY) / 1000f
1336 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1337 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001338 } else {
1339 // it will be set once we reach the boundary
1340 mMaxOverScroll = 0.0f;
1341 }
1342 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek4195dd02014-05-19 18:16:14 +02001343 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001344
1345 postInvalidateOnAnimation();
1346 }
1347 }
1348
Selim Cinek1408eb52014-06-02 14:45:38 +02001349 /**
1350 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1351 * overScroll view (i.e QS).
1352 */
1353 private boolean shouldOverScrollFling(int initialVelocity) {
1354 float topOverScroll = getCurrentOverScrollAmount(true);
1355 return mScrolledToTopOnFirstDown
1356 && !mExpandedInThisMotion
1357 && topOverScroll > mMinTopOverScrollToEscape
1358 && initialVelocity > 0;
1359 }
1360
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001361 /**
1362 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1363 * account.
1364 *
1365 * @param qsHeight the top padding imposed by the quick settings panel
1366 * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
1367 * container
1368 * @param animate whether to animate the change
1369 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1370 * {@code qsHeight} is the final top padding
1371 */
1372 public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
1373 boolean ignoreIntrinsicPadding) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001374 float start = qsHeight - scrollY + mNotificationTopPadding;
1375 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001376 int minStackHeight = getMinStackHeight();
1377 if (stackHeight <= minStackHeight) {
1378 float overflow = minStackHeight - stackHeight;
1379 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001380 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001381 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001382 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001383 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001384 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001385 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1386 animate);
1387 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001388 }
1389
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001390 public int getNotificationTopPadding() {
1391 return mNotificationTopPadding;
1392 }
1393
1394 public int getMinStackHeight() {
Selim Cinekd83771e2014-07-04 16:45:31 +02001395 return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001396 }
1397
1398 public float getTopPaddingOverflow() {
1399 return mTopPaddingOverflow;
1400 }
1401
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001402 public int getPeekHeight() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001403 return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
1404 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001405 }
1406
Selim Cinek1408eb52014-06-02 14:45:38 +02001407 private int clampPadding(int desiredPadding) {
1408 return Math.max(desiredPadding, mIntrinsicPadding);
1409 }
1410
Selim Cinekfed1ab62014-06-17 14:10:33 -07001411 private float getRubberBandFactor(boolean onTop) {
1412 if (!onTop) {
1413 return RUBBER_BAND_FACTOR_NORMAL;
1414 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001415 if (mExpandedInThisMotion) {
1416 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
1417 } else if (mIsExpansionChanging) {
1418 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1419 } else if (mScrolledToTopOnFirstDown) {
1420 return 1.0f;
1421 }
1422 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001423 }
1424
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001425 /**
1426 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1427 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1428 * overscroll view (e.g. expand QS).
1429 */
1430 private boolean isRubberbanded(boolean onTop) {
1431 return !onTop || mExpandedInThisMotion || mIsExpansionChanging
1432 || !mScrolledToTopOnFirstDown;
1433 }
1434
Selim Cinek67b22602014-03-10 15:40:16 +01001435 private void endDrag() {
1436 setIsBeingDragged(false);
1437
1438 recycleVelocityTracker();
1439
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001440 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1441 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1442 }
1443 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1444 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1445 }
Selim Cinek67b22602014-03-10 15:40:16 +01001446 }
1447
Jorim Jaggi56306252014-07-03 00:40:09 +02001448 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1449 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1450 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1451 }
1452
Selim Cinek67b22602014-03-10 15:40:16 +01001453 @Override
1454 public boolean onInterceptTouchEvent(MotionEvent ev) {
Jorim Jaggi56306252014-07-03 00:40:09 +02001455 if (mInterceptDelegateEnabled) {
1456 transformTouchEvent(ev, this, mScrollView);
1457 if (mScrollView.onInterceptTouchEvent(ev)) {
1458 mDelegateToScrollView = true;
1459 removeLongPressCallback();
1460 return true;
1461 }
1462 transformTouchEvent(ev, mScrollView, this);
1463 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001464 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001465 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001466 boolean expandWantsIt = false;
Jorim Jaggi341d1822014-07-03 23:34:13 +02001467 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001468 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1469 }
Selim Cinek67b22602014-03-10 15:40:16 +01001470 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001471 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001472 scrollWantsIt = onInterceptTouchEventScroll(ev);
1473 }
1474 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001475 if (!mIsBeingDragged
1476 && !mExpandingNotification
1477 && !mExpandedInThisMotion
1478 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001479 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1480 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001481 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1482 }
1483
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001484 private void handleEmptySpaceClick(MotionEvent ev) {
1485 switch (ev.getActionMasked()) {
1486 case MotionEvent.ACTION_MOVE:
1487 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1488 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1489 mTouchIsClick = false;
1490 }
1491 break;
1492 case MotionEvent.ACTION_UP:
1493 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1494 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1495 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1496 }
1497 break;
1498 }
1499 }
1500
Selim Cinek1408eb52014-06-02 14:45:38 +02001501 private void initDownStates(MotionEvent ev) {
1502 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1503 mExpandedInThisMotion = false;
1504 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001505 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001506 mTouchIsClick = true;
1507 mInitialTouchX = ev.getX();
1508 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001509 }
Selim Cinek67b22602014-03-10 15:40:16 +01001510 }
1511
Christoph Studer068f5922014-04-08 17:43:07 -04001512 @Override
1513 protected void onViewRemoved(View child) {
1514 super.onViewRemoved(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001515 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001516 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001517 // This is only a position change, don't do anything special
1518 return;
1519 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001520 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04001521 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001522 updateScrollStateForRemovedChild(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001523 boolean animationGenerated = generateRemoveAnimation(child);
1524 if (animationGenerated && !mSwipedOutViews.contains(child)) {
1525 // Add this view to an overlay in order to ensure that it will still be temporary
1526 // drawn when removed
1527 getOverlay().add(child);
1528 }
Selim Cinekcab4a602014-09-03 14:47:57 +02001529 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02001530
1531 // Make sure the clipRect we might have set is removed
1532 child.setClipBounds(null);
Selim Cinekc27437b2014-05-14 10:23:33 +02001533 }
1534
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001535 /**
1536 * Generate a remove animation for a child view.
1537 *
1538 * @param child The view to generate the remove animation for.
1539 * @return Whether an animation was generated.
1540 */
1541 private boolean generateRemoveAnimation(View child) {
Jorim Jaggi75c95042014-05-16 19:09:59 +02001542 if (mIsExpanded && mAnimationsEnabled) {
Selim Cinekf4c19962014-05-01 21:55:31 +02001543 if (!mChildrenToAddAnimated.contains(child)) {
1544 // Generate Animations
1545 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001546 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001547 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02001548 } else {
1549 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001550 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001551 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02001552 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001553 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001554 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001555 }
1556
1557 /**
1558 * Updates the scroll position when a child was removed
1559 *
1560 * @param removedChild the removed child
1561 */
1562 private void updateScrollStateForRemovedChild(View removedChild) {
1563 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinekd7c4e002014-07-04 18:36:42 +02001564 int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
Selim Cinek572bbd42014-04-25 16:43:27 +02001565 int endPosition = startingPosition + childHeight;
1566 if (endPosition <= mOwnScrollY) {
1567 // This child is fully scrolled of the top, so we have to deduct its height from the
1568 // scrollPosition
1569 mOwnScrollY -= childHeight;
1570 } else if (startingPosition < mOwnScrollY) {
1571 // This child is currently being scrolled into, set the scroll position to the start of
1572 // this child
1573 mOwnScrollY = startingPosition;
1574 }
1575 }
1576
Selim Cinekd7c4e002014-07-04 18:36:42 +02001577 private int getIntrinsicHeight(View view) {
1578 if (view instanceof ExpandableView) {
1579 ExpandableView expandableView = (ExpandableView) view;
1580 return expandableView.getIntrinsicHeight();
1581 }
1582 return view.getHeight();
1583 }
1584
Selim Cinek572bbd42014-04-25 16:43:27 +02001585 private int getPositionInLinearLayout(View requestedChild) {
1586 int position = 0;
1587 for (int i = 0; i < getChildCount(); i++) {
1588 View child = getChildAt(i);
1589 if (child == requestedChild) {
1590 return position;
1591 }
1592 if (child.getVisibility() != View.GONE) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02001593 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001594 if (i < getChildCount()-1) {
1595 position += mPaddingBetweenElements;
1596 }
1597 }
1598 }
1599 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02001600 }
1601
1602 @Override
1603 protected void onViewAdded(View child) {
1604 super.onViewAdded(child);
1605 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02001606 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00001607 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02001608 updateAnimationState(child);
Selim Cinek7d5f3742014-11-07 18:07:49 +01001609 if (canChildBeDismissed(child)) {
1610 // Make sure the dismissButton is visible and not in the animated state.
1611 // We need to do this to avoid a race where a clearable notification is added after the
1612 // dismiss animation is finished
1613 mDismissView.showClearButton();
1614 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001615 }
1616
Jorim Jaggi75c95042014-05-16 19:09:59 +02001617 public void setAnimationsEnabled(boolean animationsEnabled) {
1618 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02001619 updateNotificationAnimationStates();
1620 }
1621
1622 private void updateNotificationAnimationStates() {
1623 boolean running = mIsExpanded && mAnimationsEnabled;
1624 int childCount = getChildCount();
1625 for (int i = 0; i < childCount; i++) {
1626 View child = getChildAt(i);
1627 updateAnimationState(running, child);
1628 }
1629 }
1630
Selim Cinek51ae05d2014-09-09 15:51:38 +02001631 private void updateAnimationState(View child) {
1632 updateAnimationState(mAnimationsEnabled && mIsExpanded, child);
1633 }
1634
1635
Selim Cinekcab4a602014-09-03 14:47:57 +02001636 private void updateAnimationState(boolean running, View child) {
1637 if (child instanceof ExpandableNotificationRow) {
1638 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1639 row.setIconAnimationRunning(running);
1640 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02001641 }
1642
1643 public boolean isAddOrRemoveAnimationPending() {
1644 return mNeedsAnimation
1645 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1646 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001647 /**
1648 * Generate an animation for an added child view.
1649 *
1650 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00001651 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001652 */
Jorim Jaggif6411742014-08-05 17:10:43 +00001653 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001654 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001655 // Generate Animations
1656 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00001657 if (fromMoreCard) {
1658 mFromMoreCardAdditions.add(child);
1659 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001660 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02001661 }
1662 }
1663
1664 /**
1665 * Change the position of child to a new location
1666 *
1667 * @param child the view to change the position for
1668 * @param newIndex the new index
1669 */
1670 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04001671 int currentIndex = indexOfChild(child);
1672 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001673 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02001674 removeView(child);
1675 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001676 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04001677 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001678 mChildrenChangingPositions.add(child);
1679 mNeedsAnimation = true;
1680 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001681 }
1682 }
1683
Selim Cinekf4c19962014-05-01 21:55:31 +02001684 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001685 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001686 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001687 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001688 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001689 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001690 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1691 mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001692 mAnimationEvents.clear();
Selim Cinekf4c19962014-05-01 21:55:31 +02001693 } else {
1694 applyCurrentState();
1695 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001696 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02001697 }
1698
1699 private void generateChildHierarchyEvents() {
Selim Cinek572bbd42014-04-25 16:43:27 +02001700 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001701 generateChildAdditionEvents();
1702 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02001703 generateSnapBackEvents();
1704 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001705 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001706 generateActivateEvent();
1707 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02001708 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04001709 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001710 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02001711 generateViewResizeEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02001712 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001713 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001714 }
1715
Selim Cineka5e211b2014-08-11 17:35:48 +02001716 private void generateViewResizeEvent() {
1717 if (mNeedViewResizeAnimation) {
1718 mAnimationEvents.add(
1719 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
1720 }
1721 mNeedViewResizeAnimation = false;
1722 }
1723
Selim Cinekeb973562014-05-02 17:07:49 +02001724 private void generateSnapBackEvents() {
1725 for (View child : mSnappedBackChildren) {
1726 mAnimationEvents.add(new AnimationEvent(child,
1727 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1728 }
1729 mSnappedBackChildren.clear();
1730 }
1731
1732 private void generateDragEvents() {
1733 for (View child : mDragAnimPendingChildren) {
1734 mAnimationEvents.add(new AnimationEvent(child,
1735 AnimationEvent.ANIMATION_TYPE_START_DRAG));
1736 }
1737 mDragAnimPendingChildren.clear();
1738 }
1739
Selim Cinek572bbd42014-04-25 16:43:27 +02001740 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001741 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001742 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1743 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001744 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1745 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001746 AnimationEvent event = new AnimationEvent(child, animationType);
1747
1748 // we need to know the view after this one
1749 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1750 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02001751 }
1752 mSwipedOutViews.clear();
1753 mChildrenToRemoveAnimated.clear();
1754 }
1755
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001756 private void generatePositionChangeEvents() {
1757 for (View child : mChildrenChangingPositions) {
1758 mAnimationEvents.add(new AnimationEvent(child,
1759 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1760 }
1761 mChildrenChangingPositions.clear();
1762 }
1763
Selim Cinek572bbd42014-04-25 16:43:27 +02001764 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001765 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001766 if (mFromMoreCardAdditions.contains(child)) {
1767 mAnimationEvents.add(new AnimationEvent(child,
1768 AnimationEvent.ANIMATION_TYPE_ADD,
1769 StackStateAnimator.ANIMATION_DURATION_STANDARD));
1770 } else {
1771 mAnimationEvents.add(new AnimationEvent(child,
1772 AnimationEvent.ANIMATION_TYPE_ADD));
1773 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001774 }
1775 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001776 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04001777 }
1778
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001779 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02001780 if (mTopPaddingNeedsAnimation) {
1781 mAnimationEvents.add(
1782 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
1783 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001784 mTopPaddingNeedsAnimation = false;
1785 }
1786
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001787 private void generateActivateEvent() {
1788 if (mActivateNeedsAnimation) {
1789 mAnimationEvents.add(
1790 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
1791 }
1792 mActivateNeedsAnimation = false;
1793 }
1794
Selim Cinekd9acca52014-09-01 22:33:25 +02001795 private void generateAnimateEverythingEvent() {
1796 if (mEverythingNeedsAnimation) {
1797 mAnimationEvents.add(
1798 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
1799 }
1800 mEverythingNeedsAnimation = false;
1801 }
1802
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001803 private void generateDimmedEvent() {
1804 if (mDimmedNeedsAnimation) {
1805 mAnimationEvents.add(
1806 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
1807 }
1808 mDimmedNeedsAnimation = false;
1809 }
1810
Jorim Jaggiae441282014-08-01 02:45:18 +02001811 private void generateHideSensitiveEvent() {
1812 if (mHideSensitiveNeedsAnimation) {
1813 mAnimationEvents.add(
1814 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
1815 }
1816 mHideSensitiveNeedsAnimation = false;
1817 }
1818
John Spurlockbf370992014-06-17 13:58:31 -04001819 private void generateDarkEvent() {
1820 if (mDarkNeedsAnimation) {
1821 mAnimationEvents.add(
1822 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
1823 }
1824 mDarkNeedsAnimation = false;
1825 }
1826
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001827 private void generateGoToFullShadeEvent() {
1828 if (mGoToFullShadeNeedsAnimation) {
1829 mAnimationEvents.add(
1830 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
1831 }
1832 mGoToFullShadeNeedsAnimation = false;
1833 }
1834
Selim Cinek67b22602014-03-10 15:40:16 +01001835 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001836 if (!isScrollingEnabled()) {
1837 return false;
1838 }
Selim Cinek67b22602014-03-10 15:40:16 +01001839 /*
1840 * This method JUST determines whether we want to intercept the motion.
1841 * If we return true, onMotionEvent will be called and we do the actual
1842 * scrolling there.
1843 */
1844
1845 /*
1846 * Shortcut the most recurring case: the user is in the dragging
1847 * state and he is moving his finger. We want to intercept this
1848 * motion.
1849 */
1850 final int action = ev.getAction();
1851 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
1852 return true;
1853 }
1854
Selim Cinek67b22602014-03-10 15:40:16 +01001855 switch (action & MotionEvent.ACTION_MASK) {
1856 case MotionEvent.ACTION_MOVE: {
1857 /*
1858 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1859 * whether the user has moved far enough from his original down touch.
1860 */
1861
1862 /*
1863 * Locally do absolute value. mLastMotionY is set to the y value
1864 * of the down event.
1865 */
1866 final int activePointerId = mActivePointerId;
1867 if (activePointerId == INVALID_POINTER) {
1868 // If we don't have a valid id, the touch down wasn't on content.
1869 break;
1870 }
1871
1872 final int pointerIndex = ev.findPointerIndex(activePointerId);
1873 if (pointerIndex == -1) {
1874 Log.e(TAG, "Invalid pointerId=" + activePointerId
1875 + " in onInterceptTouchEvent");
1876 break;
1877 }
1878
1879 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001880 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001881 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02001882 final int xDiff = Math.abs(x - mDownX);
1883 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001884 setIsBeingDragged(true);
1885 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001886 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01001887 initVelocityTrackerIfNotExists();
1888 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01001889 }
1890 break;
1891 }
1892
1893 case MotionEvent.ACTION_DOWN: {
1894 final int y = (int) ev.getY();
1895 if (getChildAtPosition(ev.getX(), y) == null) {
1896 setIsBeingDragged(false);
1897 recycleVelocityTracker();
1898 break;
1899 }
1900
1901 /*
1902 * Remember location of down touch.
1903 * ACTION_DOWN always refers to pointer index 0.
1904 */
1905 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001906 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001907 mActivePointerId = ev.getPointerId(0);
Selim Cinek1408eb52014-06-02 14:45:38 +02001908 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01001909
1910 initOrResetVelocityTracker();
1911 mVelocityTracker.addMovement(ev);
1912 /*
1913 * If being flinged and user touches the screen, initiate drag;
1914 * otherwise don't. mScroller.isFinished should be false when
1915 * being flinged.
1916 */
1917 boolean isBeingDragged = !mScroller.isFinished();
1918 setIsBeingDragged(isBeingDragged);
1919 break;
1920 }
1921
1922 case MotionEvent.ACTION_CANCEL:
1923 case MotionEvent.ACTION_UP:
1924 /* Release the drag */
1925 setIsBeingDragged(false);
1926 mActivePointerId = INVALID_POINTER;
1927 recycleVelocityTracker();
1928 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1929 postInvalidateOnAnimation();
1930 }
1931 break;
1932 case MotionEvent.ACTION_POINTER_UP:
1933 onSecondaryPointerUp(ev);
1934 break;
1935 }
1936
1937 /*
1938 * The only time we want to intercept motion events is if we are in the
1939 * drag mode.
1940 */
1941 return mIsBeingDragged;
1942 }
1943
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001944 /**
1945 * @return Whether the specified motion event is actually happening over the content.
1946 */
1947 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01001948 return isInContentBounds(event.getY());
1949 }
1950
1951 /**
1952 * @return Whether a y coordinate is inside the content.
1953 */
1954 public boolean isInContentBounds(float y) {
1955 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001956 }
1957
Selim Cinek67b22602014-03-10 15:40:16 +01001958 private void setIsBeingDragged(boolean isDragged) {
1959 mIsBeingDragged = isDragged;
1960 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001961 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02001962 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01001963 }
1964 }
1965
1966 @Override
1967 public void onWindowFocusChanged(boolean hasWindowFocus) {
1968 super.onWindowFocusChanged(hasWindowFocus);
1969 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001970 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01001971 }
1972 }
Selim Cinekfab078b2014-03-27 22:45:58 +01001973
Selim Cinek1408eb52014-06-02 14:45:38 +02001974 public void removeLongPressCallback() {
1975 mSwipeHelper.removeLongPressCallback();
1976 }
1977
Selim Cinekfab078b2014-03-27 22:45:58 +01001978 @Override
1979 public boolean isScrolledToTop() {
1980 return mOwnScrollY == 0;
1981 }
1982
1983 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001984 public boolean isScrolledToBottom() {
1985 return mOwnScrollY >= getScrollRange();
1986 }
1987
1988 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01001989 public View getHostView() {
1990 return this;
1991 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001992
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001993 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001994 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02001995 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001996 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02001997 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001998 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02001999 }
2000 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002001 }
2002
Selim Cinek1685e632014-04-08 02:27:49 +02002003 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002004 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002005 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2006 }
2007
2008 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002009 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02002010 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002011 if (!mIsExpanded) {
2012 mOwnScrollY = 0;
Selim Cinekf336f4c2014-11-12 16:58:16 +01002013
2014 // lets make sure nothing is in the overlay anymore
2015 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002016 }
Selim Cinek1685e632014-04-08 02:27:49 +02002017 }
2018
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002019 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002020 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002021 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002022 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002023 if (changed) {
2024 updateNotificationAnimationStates();
2025 }
Selim Cinek1685e632014-04-08 02:27:49 +02002026 }
2027
Jorim Jaggibe565df2014-04-28 17:51:23 +02002028 @Override
2029 public void onHeightChanged(ExpandableView view) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002030 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002031 updateScrollPositionOnExpandInBottom(view);
2032 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002033 notifyHeightChangeListener(view);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002034 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002035 }
2036
Selim Cineka5e211b2014-08-11 17:35:48 +02002037 @Override
2038 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002039 if (mIsExpanded && mAnimationsEnabled) {
2040 mRequestViewResizeAnimationOnLayout = true;
2041 }
Selim Cinek31094df2014-08-14 19:28:15 +02002042 mStackScrollAlgorithm.onReset(view);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002043 updateAnimationState(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002044 }
2045
Selim Cinekf7a14c02014-07-07 14:01:46 +02002046 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2047 if (view instanceof ExpandableNotificationRow) {
2048 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2049 if (row.isUserLocked()) {
2050 // We are actually expanding this view
2051 float endPosition = row.getTranslationY() + row.getActualHeight();
2052 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
2053 mBottomStackSlowDownHeight;
2054 if (endPosition > stackEnd) {
2055 mOwnScrollY += endPosition - stackEnd;
2056 mDisallowScrollingInThisMotion = true;
2057 }
2058 }
2059 }
2060 }
2061
Jorim Jaggibe565df2014-04-28 17:51:23 +02002062 public void setOnHeightChangedListener(
2063 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2064 this.mOnHeightChangedListener = mOnHeightChangedListener;
2065 }
2066
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002067 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2068 mOnEmptySpaceClickListener = listener;
2069 }
2070
Selim Cinek572bbd42014-04-25 16:43:27 +02002071 public void onChildAnimationFinished() {
Selim Cinek319bdc42014-05-01 23:01:58 +02002072 requestChildrenUpdate();
Selim Cinek572bbd42014-04-25 16:43:27 +02002073 }
2074
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002075 /**
2076 * See {@link AmbientState#setDimmed}.
2077 */
2078 public void setDimmed(boolean dimmed, boolean animate) {
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002079 mStackScrollAlgorithm.setDimmed(dimmed);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002080 mAmbientState.setDimmed(dimmed);
Selim Cineka5eaa602014-05-12 21:27:47 +02002081 updatePadding(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002082 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002083 mDimmedNeedsAnimation = true;
2084 mNeedsAnimation = true;
2085 }
2086 requestChildrenUpdate();
2087 }
2088
Jorim Jaggiae441282014-08-01 02:45:18 +02002089 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2090 if (hideSensitive != mAmbientState.isHideSensitive()) {
2091 int childCount = getChildCount();
2092 for (int i = 0; i < childCount; i++) {
2093 ExpandableView v = (ExpandableView) getChildAt(i);
2094 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2095 }
2096 mAmbientState.setHideSensitive(hideSensitive);
2097 if (animate && mAnimationsEnabled) {
2098 mHideSensitiveNeedsAnimation = true;
2099 mNeedsAnimation = true;
2100 }
2101 requestChildrenUpdate();
2102 }
2103 }
2104
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002105 /**
2106 * See {@link AmbientState#setActivatedChild}.
2107 */
Selim Cineka32ab602014-06-11 15:06:01 +02002108 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002109 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002110 if (mAnimationsEnabled) {
2111 mActivateNeedsAnimation = true;
2112 mNeedsAnimation = true;
2113 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002114 requestChildrenUpdate();
2115 }
2116
Selim Cineka32ab602014-06-11 15:06:01 +02002117 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002118 return mAmbientState.getActivatedChild();
2119 }
2120
Selim Cinek572bbd42014-04-25 16:43:27 +02002121 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002122 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002123 if (mListener != null) {
2124 mListener.onChildLocationsChanged(this);
2125 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002126 }
2127
Selim Cinekc27437b2014-05-14 10:23:33 +02002128 public void setSpeedBumpView(SpeedBumpView speedBumpView) {
2129 mSpeedBumpView = speedBumpView;
2130 addView(speedBumpView);
2131 }
2132
2133 private void updateSpeedBump(boolean visible) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002134 boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
2135 if (visible != notGoneBefore) {
2136 int newVisibility = visible ? VISIBLE : GONE;
Selim Cinekc27437b2014-05-14 10:23:33 +02002137 mSpeedBumpView.setVisibility(newVisibility);
2138 if (visible) {
Selim Cinekc27437b2014-05-14 10:23:33 +02002139 // Make invisible to ensure that the appear animation is played.
2140 mSpeedBumpView.setInvisible();
Selim Cinekc27437b2014-05-14 10:23:33 +02002141 } else {
Jorim Jaggi8b730062014-07-31 22:19:52 +02002142 // TODO: This doesn't really work, because the view is already set to GONE above.
Selim Cinekc27437b2014-05-14 10:23:33 +02002143 generateRemoveAnimation(mSpeedBumpView);
2144 }
2145 }
2146 }
2147
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002148 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002149 updateSpeedBump(true /* visibility */);
2150 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002151 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002152 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002153 mGoToFullShadeDelay = delay;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002154 mNeedsAnimation = true;
2155 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002156 }
2157
Selim Cinek1408eb52014-06-02 14:45:38 +02002158 public void cancelExpandHelper() {
2159 mExpandHelper.cancel();
2160 }
2161
2162 public void setIntrinsicPadding(int intrinsicPadding) {
2163 mIntrinsicPadding = intrinsicPadding;
2164 }
2165
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002166 public int getIntrinsicPadding() {
2167 return mIntrinsicPadding;
2168 }
2169
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002170 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002171 * @return the y position of the first notification
2172 */
2173 public float getNotificationsTopY() {
2174 return mTopPadding + getTranslationY();
2175 }
2176
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002177 @Override
2178 public boolean shouldDelayChildPressedState() {
2179 return true;
2180 }
2181
Jorim Jaggi457cc352014-06-02 22:47:42 +02002182 /**
John Spurlockbf370992014-06-17 13:58:31 -04002183 * See {@link AmbientState#setDark}.
2184 */
2185 public void setDark(boolean dark, boolean animate) {
2186 mAmbientState.setDark(dark);
2187 if (animate && mAnimationsEnabled) {
2188 mDarkNeedsAnimation = true;
2189 mNeedsAnimation = true;
2190 }
2191 requestChildrenUpdate();
2192 }
2193
Dan Sandlereceda3d2014-07-21 15:35:01 -04002194 public void setDismissView(DismissView dismissView) {
2195 mDismissView = dismissView;
2196 addView(mDismissView);
2197 }
2198
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002199 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2200 mEmptyShadeView = emptyShadeView;
2201 addView(mEmptyShadeView);
2202 }
2203
2204 public void updateEmptyShadeView(boolean visible) {
2205 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2206 int newVisibility = visible ? VISIBLE : GONE;
2207 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002208 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002209 if (mEmptyShadeView.willBeGone()) {
2210 mEmptyShadeView.cancelAnimation();
2211 } else {
2212 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002213 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002214 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002215 mEmptyShadeView.setWillBeGone(false);
2216 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002217 notifyHeightChangeListener(mDismissView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002218 } else {
2219 mEmptyShadeView.setWillBeGone(true);
2220 mEmptyShadeView.performVisibilityAnimation(false, new Runnable() {
2221 @Override
2222 public void run() {
2223 mEmptyShadeView.setVisibility(GONE);
2224 mEmptyShadeView.setWillBeGone(false);
2225 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002226 notifyHeightChangeListener(mDismissView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002227 }
2228 });
2229 }
2230 }
2231 }
2232
Dan Sandlereceda3d2014-07-21 15:35:01 -04002233 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002234 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002235 int newVisibility = visible ? VISIBLE : GONE;
2236 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002237 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002238 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002239 mDismissView.cancelAnimation();
2240 } else {
2241 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002242 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002243 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002244 mDismissView.setWillBeGone(false);
2245 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002246 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002247 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01002248 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002249 @Override
2250 public void run() {
2251 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002252 mDismissView.setWillBeGone(false);
2253 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002254 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002255 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01002256 };
2257 if (mDismissView.isButtonVisible() && mIsExpanded) {
2258 mDismissView.setWillBeGone(true);
2259 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
2260 } else {
2261 dimissHideFinishRunnable.run();
2262 mDismissView.showClearButton();
2263 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002264 }
2265 }
2266 }
2267
2268 public void setDismissAllInProgress(boolean dismissAllInProgress) {
2269 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek7d5f3742014-11-07 18:07:49 +01002270 mDismissView.setDismissAllInProgress(dismissAllInProgress);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002271 }
2272
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002273 public boolean isDismissViewNotGone() {
2274 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2275 }
2276
2277 public boolean isDismissViewVisible() {
2278 return mDismissView.isVisible();
2279 }
2280
2281 public int getDismissViewHeight() {
Jorim Jaggi1d49ec92014-08-25 18:44:01 +02002282 int height = mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2283
2284 // Hack: Accommodate for additional distance when we only have one notification and the
2285 // dismiss all button.
2286 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
2287 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
2288 height += mCollapseSecondCardPadding;
2289 }
2290 return height;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002291 }
2292
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01002293 public int getEmptyShadeViewHeight() {
2294 return mEmptyShadeView.getHeight();
2295 }
2296
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002297 public float getBottomMostNotificationBottom() {
2298 final int count = getChildCount();
2299 float max = 0;
2300 for (int childIdx = 0; childIdx < count; childIdx++) {
2301 ExpandableView child = (ExpandableView) getChildAt(childIdx);
2302 if (child.getVisibility() == GONE) {
2303 continue;
2304 }
2305 float bottom = child.getTranslationY() + child.getActualHeight();
2306 if (bottom > max) {
2307 max = bottom;
2308 }
2309 }
2310 return max + getTranslationY();
2311 }
2312
John Spurlockbf370992014-06-17 13:58:31 -04002313 /**
Selim Cinek3afd00e2014-08-11 22:32:57 +02002314 * @param qsMinHeight The minimum height of the quick settings including padding
2315 * See {@link StackScrollAlgorithm#updateIsSmallScreen}.
2316 */
2317 public void updateIsSmallScreen(int qsMinHeight) {
2318 mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight);
2319 }
2320
Selim Cinek19c8c702014-08-25 22:09:19 +02002321 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
2322 this.mPhoneStatusBar = phoneStatusBar;
2323 }
2324
Selim Cinekd9acca52014-09-01 22:33:25 +02002325 public void onGoToKeyguard() {
2326 if (mIsExpanded && mAnimationsEnabled) {
2327 mEverythingNeedsAnimation = true;
2328 requestChildrenUpdate();
2329 }
2330 }
2331
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002332 private boolean isBelowLastNotification(float touchX, float touchY) {
2333 ExpandableView lastChildNotGone = (ExpandableView) getLastChildNotGone();
2334 if (lastChildNotGone == null) {
2335 return touchY > mIntrinsicPadding;
2336 }
2337 if (lastChildNotGone != mDismissView && lastChildNotGone != mEmptyShadeView) {
2338 return touchY > lastChildNotGone.getY() + lastChildNotGone.getActualHeight();
2339 } else if (lastChildNotGone == mEmptyShadeView) {
2340 return touchY > mEmptyShadeView.getY();
2341 } else {
2342 float dismissY = mDismissView.getY();
2343 boolean belowDismissView = touchY > dismissY + mDismissView.getActualHeight();
2344 return belowDismissView || (touchY > dismissY
2345 && mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
2346 touchY - dismissY));
2347 }
2348 }
2349
Selim Cinek3afd00e2014-08-11 22:32:57 +02002350 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002351 * A listener that is notified when some child locations might have changed.
2352 */
2353 public interface OnChildLocationsChangedListener {
2354 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2355 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002356
Jorim Jaggi290600a2014-05-30 17:02:20 +02002357 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002358 * A listener that is notified when the empty space below the notifications is clicked on
2359 */
2360 public interface OnEmptySpaceClickListener {
2361 public void onEmptySpaceClicked(float x, float y);
2362 }
2363
2364 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02002365 * A listener that gets notified when the overscroll at the top has changed.
2366 */
2367 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002368
2369 /**
2370 * Notifies a listener that the overscroll has changed.
2371 *
2372 * @param amount the amount of overscroll, in pixels
2373 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2374 * unrubberbanded motion to directly expand overscroll view (e.g expand
2375 * QS)
2376 */
2377 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02002378
2379 /**
2380 * Notify a listener that the scroller wants to escape from the scrolling motion and
2381 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2382 *
2383 * @param velocity The velocity that the Scroller had when over flinging
2384 * @param open Should the fling open or close the overscroll view.
2385 */
2386 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002387 }
2388
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002389 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02002390
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002391 static AnimationFilter[] FILTERS = new AnimationFilter[] {
2392
2393 // ANIMATION_TYPE_ADD
2394 new AnimationFilter()
2395 .animateAlpha()
2396 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002397 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002398 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002399 .animateZ()
2400 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002401
2402 // ANIMATION_TYPE_REMOVE
2403 new AnimationFilter()
2404 .animateAlpha()
2405 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002406 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002407 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002408 .animateZ()
2409 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002410
2411 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2412 new AnimationFilter()
2413 .animateAlpha()
2414 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002415 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002416 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002417 .animateZ()
2418 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002419
2420 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2421 new AnimationFilter()
2422 .animateAlpha()
2423 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002424 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002425 .animateY()
2426 .animateDimmed()
2427 .animateScale()
2428 .animateZ(),
2429
2430 // ANIMATION_TYPE_START_DRAG
2431 new AnimationFilter()
2432 .animateAlpha(),
2433
2434 // ANIMATION_TYPE_SNAP_BACK
2435 new AnimationFilter()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002436 .animateAlpha()
2437 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002438
2439 // ANIMATION_TYPE_ACTIVATED_CHILD
2440 new AnimationFilter()
2441 .animateScale()
2442 .animateAlpha(),
2443
2444 // ANIMATION_TYPE_DIMMED
2445 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002446 .animateY()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002447 .animateScale()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002448 .animateDimmed(),
2449
2450 // ANIMATION_TYPE_CHANGE_POSITION
2451 new AnimationFilter()
2452 .animateAlpha()
2453 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002454 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002455 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04002456 .animateZ(),
2457
2458 // ANIMATION_TYPE_DARK
2459 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01002460 .animateDark()
2461 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002462
2463 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2464 new AnimationFilter()
2465 .animateAlpha()
2466 .animateHeight()
2467 .animateTopInset()
2468 .animateY()
2469 .animateDimmed()
2470 .animateScale()
Jorim Jaggiae441282014-08-01 02:45:18 +02002471 .animateZ()
2472 .hasDelays(),
2473
2474 // ANIMATION_TYPE_HIDE_SENSITIVE
2475 new AnimationFilter()
2476 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02002477
2478 // ANIMATION_TYPE_VIEW_RESIZE
2479 new AnimationFilter()
2480 .animateAlpha()
2481 .animateHeight()
2482 .animateTopInset()
2483 .animateY()
2484 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02002485
2486 // ANIMATION_TYPE_EVERYTHING
2487 new AnimationFilter()
2488 .animateAlpha()
2489 .animateDark()
2490 .animateScale()
2491 .animateDimmed()
2492 .animateHideSensitive()
2493 .animateHeight()
2494 .animateTopInset()
2495 .animateY()
2496 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002497 };
2498
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002499 static int[] LENGTHS = new int[] {
2500
2501 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002502 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002503
2504 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002505 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002506
2507 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2508 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2509
2510 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2511 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2512
2513 // ANIMATION_TYPE_START_DRAG
2514 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2515
2516 // ANIMATION_TYPE_SNAP_BACK
2517 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2518
2519 // ANIMATION_TYPE_ACTIVATED_CHILD
2520 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2521
2522 // ANIMATION_TYPE_DIMMED
2523 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002524
2525 // ANIMATION_TYPE_CHANGE_POSITION
2526 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04002527
2528 // ANIMATION_TYPE_DARK
2529 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002530
2531 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2532 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02002533
2534 // ANIMATION_TYPE_HIDE_SENSITIVE
2535 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02002536
2537 // ANIMATION_TYPE_VIEW_RESIZE
2538 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02002539
2540 // ANIMATION_TYPE_EVERYTHING
2541 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002542 };
2543
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002544 static final int ANIMATION_TYPE_ADD = 0;
2545 static final int ANIMATION_TYPE_REMOVE = 1;
2546 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
2547 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
2548 static final int ANIMATION_TYPE_START_DRAG = 4;
2549 static final int ANIMATION_TYPE_SNAP_BACK = 5;
2550 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
2551 static final int ANIMATION_TYPE_DIMMED = 7;
2552 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04002553 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002554 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02002555 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02002556 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekd9acca52014-09-01 22:33:25 +02002557 static final int ANIMATION_TYPE_EVERYTHING = 13;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002558
Selim Cinek572bbd42014-04-25 16:43:27 +02002559 final long eventStartTime;
2560 final View changingView;
2561 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002562 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002563 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002564 View viewAfterChangingView;
Selim Cinek572bbd42014-04-25 16:43:27 +02002565
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002566 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002567 this(view, type, LENGTHS[type]);
2568 }
2569
2570 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002571 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
2572 changingView = view;
2573 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002574 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002575 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002576 }
2577
2578 /**
2579 * Combines the length of several animation events into a single value.
2580 *
2581 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002582 * @return The combined length. Depending on the event types, this might be the maximum of
2583 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002584 */
2585 static long combineLength(ArrayList<AnimationEvent> events) {
2586 long length = 0;
2587 int size = events.size();
2588 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002589 AnimationEvent event = events.get(i);
2590 length = Math.max(length, event.length);
2591 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
2592 return event.length;
2593 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02002594 }
2595 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02002596 }
2597 }
2598
Selim Cinek67b22602014-03-10 15:40:16 +01002599}