blob: 23d9b9fe94b84c40f0432fc42ef986395400aa47 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.systemui.statusbar.stack;
18
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010019import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010020import android.content.Context;
21import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010022import android.graphics.Canvas;
Selim Cinek67b22602014-03-10 15:40:16 +010023import android.graphics.Paint;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010024import android.graphics.PointF;
Selim Cinek67b22602014-03-10 15:40:16 +010025import android.util.AttributeSet;
26import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070027import android.util.Pair;
Selim Cinek67b22602014-03-10 15:40:16 +010028import android.view.MotionEvent;
29import android.view.VelocityTracker;
30import android.view.View;
31import android.view.ViewConfiguration;
32import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020033import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020034import android.view.animation.AnimationUtils;
Selim Cinek67b22602014-03-10 15:40:16 +010035import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020036
Selim Cinek67b22602014-03-10 15:40:16 +010037import com.android.systemui.ExpandHelper;
38import com.android.systemui.R;
39import com.android.systemui.SwipeHelper;
Selim Cineka32ab602014-06-11 15:06:01 +020040import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040041import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020042import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010043import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020044import com.android.systemui.statusbar.ExpandableView;
Selim Cinekb5605e52015-02-20 18:21:41 +010045import com.android.systemui.statusbar.NotificationData;
Selim Cinek2cd45df2015-06-09 18:00:07 -070046import com.android.systemui.statusbar.NotificationOverflowContainer;
Selim Cinekc27437b2014-05-14 10:23:33 +020047import com.android.systemui.statusbar.SpeedBumpView;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010048import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020049import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb5605e52015-02-20 18:21:41 +010050import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020051import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070052import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070053import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010054import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010055
Selim Cinek572bbd42014-04-25 16:43:27 +020056import java.util.ArrayList;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020057import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020058
Selim Cinek67b22602014-03-10 15:40:16 +010059/**
60 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
61 */
62public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020063 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Selim Cinekb5605e52015-02-20 18:21:41 +010064 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
Selim Cinek67b22602014-03-10 15:40:16 +010065
66 private static final String TAG = "NotificationStackScrollLayout";
67 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020068 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
69 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020070 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010071
72 /**
73 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
74 */
75 private static final int INVALID_POINTER = -1;
76
Selim Cinek1408eb52014-06-02 14:45:38 +020077 private ExpandHelper mExpandHelper;
Selim Cinek67b22602014-03-10 15:40:16 +010078 private SwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +020079 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +010080 private int mCurrentStackHeight = Integer.MAX_VALUE;
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +010081
82 /**
83 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
84 * externally from {@link #setStackHeight}
85 */
86 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +010087 private int mOwnScrollY;
88 private int mMaxLayoutHeight;
89
90 private VelocityTracker mVelocityTracker;
91 private OverScroller mScroller;
92 private int mTouchSlop;
93 private int mMinimumVelocity;
94 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +010095 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020096 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +010097 private boolean mIsBeingDragged;
98 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +020099 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +0100100 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100101 private boolean mTouchIsClick;
102 private float mInitialTouchX;
103 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100104
105 private int mSidePaddings;
106 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100107 private int mContentHeight;
108 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200109 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100110 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100111 private int mPaddingBetweenElements;
Selim Cineka5eaa602014-05-12 21:27:47 +0200112 private int mPaddingBetweenElementsDimmed;
113 private int mPaddingBetweenElementsNormal;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200114 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200115 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100116
117 /**
118 * The algorithm which calculates the properties for our children
119 */
120 private StackScrollAlgorithm mStackScrollAlgorithm;
121
122 /**
123 * The current State this Layout is in
124 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200125 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200126 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100127 private NotificationGroupManager mGroupManager;
Selim Cineka59ecc32015-04-07 10:51:49 -0700128 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
129 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
130 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
131 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
132 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
133 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200134 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700135 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
136 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200137 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200138 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200139 private boolean mChangePositionInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200140
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200141 /**
142 * The raw amount of the overScroll on the top, which is not rubber-banded.
143 */
144 private float mOverScrolledTopPixels;
145
146 /**
147 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
148 */
149 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200150 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200151 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200152 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100153 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200154 private boolean mNeedsAnimation;
155 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200156 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200157 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400158 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100159 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200160 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200161 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200162 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200163 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200164 private SpeedBumpView mSpeedBumpView;
165 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700166 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200167 private boolean mExpandingNotification;
168 private boolean mExpandedInThisMotion;
169 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400170 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200171 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400172 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200173
174 /**
175 * Was the scroller scrolled to the top when the down motion was observed?
176 */
177 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200178 /**
179 * The minimal amount of over scroll which is needed in order to switch to the quick settings
180 * when over scrolling on a expanded card.
181 */
182 private float mMinTopOverScrollToEscape;
183 private int mIntrinsicPadding;
184 private int mNotificationTopPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700185 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200186 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200187 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200188 private boolean mRequestViewResizeAnimationOnLayout;
189 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100190 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700191 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700192
Selim Cinek1408eb52014-06-02 14:45:38 +0200193 /**
194 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
195 * This is needed to avoid scrolling too far after the notification was collapsed in the same
196 * motion.
197 */
198 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400199 private SwipeHelper.LongPressListener mLongPressListener;
Selim Cinek1408eb52014-06-02 14:45:38 +0200200
201 /**
202 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
203 * animating.
204 */
205 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200206 private ViewGroup mScrollView;
207 private boolean mInterceptDelegateEnabled;
208 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700209 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700210 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200211 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200212 = new ViewTreeObserver.OnPreDrawListener() {
213 @Override
214 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200215 updateChildren();
216 mChildrenUpdateRequested = false;
217 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200218 return true;
219 }
220 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200221 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100222 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100223 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700224 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700225 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700226 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
227 = new HashSet<>();
228 private HeadsUpManager mHeadsUpManager;
229 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700230 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200231 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700232 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700233 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Selim Cinek67b22602014-03-10 15:40:16 +0100234
235 public NotificationStackScrollLayout(Context context) {
236 this(context, null);
237 }
238
239 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
240 this(context, attrs, 0);
241 }
242
243 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
244 this(context, attrs, defStyleAttr, 0);
245 }
246
247 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
248 int defStyleRes) {
249 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200250 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
251 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
252 mExpandHelper = new ExpandHelper(getContext(), this,
253 minHeight, maxHeight);
254 mExpandHelper.setEventSource(this);
255 mExpandHelper.setScrollAdapter(this);
256
257 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
258 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinek67b22602014-03-10 15:40:16 +0100259 initView(context);
260 if (DEBUG) {
261 setWillNotDraw(false);
262 mDebugPaint = new Paint();
263 mDebugPaint.setColor(0xffff0000);
264 mDebugPaint.setStrokeWidth(2);
265 mDebugPaint.setStyle(Paint.Style.STROKE);
266 }
267 }
268
269 @Override
270 protected void onDraw(Canvas canvas) {
271 if (DEBUG) {
272 int y = mCollapsedSize;
273 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200274 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200275 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200276 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
277 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100278 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
279 y = (int) getLayoutHeight();
280 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200281 y = getHeight() - getEmptyBottomMargin();
282 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100283 }
284 }
285
286 private void initView(Context context) {
287 mScroller = new OverScroller(getContext());
288 setFocusable(true);
289 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200290 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100291 final ViewConfiguration configuration = ViewConfiguration.get(context);
292 mTouchSlop = configuration.getScaledTouchSlop();
293 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
294 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100295 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100296
297 mSidePaddings = context.getResources()
298 .getDimensionPixelSize(R.dimen.notification_side_padding);
Selim Cinek67b22602014-03-10 15:40:16 +0100299 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200300 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100301 mBottomStackPeekSize = context.getResources()
302 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinek67b22602014-03-10 15:40:16 +0100303 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200304 mStackScrollAlgorithm.setDimmed(mAmbientState.isDimmed());
Selim Cineka5eaa602014-05-12 21:27:47 +0200305 mPaddingBetweenElementsDimmed = context.getResources()
306 .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
307 mPaddingBetweenElementsNormal = context.getResources()
308 .getDimensionPixelSize(R.dimen.notification_padding);
Jorim Jaggi220bf5c2014-06-14 22:43:12 +0200309 updatePadding(mAmbientState.isDimmed());
Selim Cinek1408eb52014-06-02 14:45:38 +0200310 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
311 R.dimen.min_top_overscroll_to_qs);
312 mNotificationTopPadding = getResources().getDimensionPixelSize(
313 R.dimen.notifications_top_padding);
Selim Cinekd83771e2014-07-04 16:45:31 +0200314 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
315 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200316 }
317
318 private void updatePadding(boolean dimmed) {
Jorim Jaggid7c1fae2014-08-13 18:27:47 +0200319 mPaddingBetweenElements = dimmed && mStackScrollAlgorithm.shouldScaleDimmed()
Selim Cineka5eaa602014-05-12 21:27:47 +0200320 ? mPaddingBetweenElementsDimmed
321 : mPaddingBetweenElementsNormal;
322 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
323 updateContentHeight();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200324 notifyHeightChangeListener(null);
325 }
326
327 private void notifyHeightChangeListener(ExpandableView view) {
328 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100329 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200330 }
Selim Cinek67b22602014-03-10 15:40:16 +0100331 }
332
333 @Override
334 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
335 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
336 int mode = MeasureSpec.getMode(widthMeasureSpec);
337 int size = MeasureSpec.getSize(widthMeasureSpec);
338 int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
339 measureChildren(childMeasureSpec, heightMeasureSpec);
340 }
341
342 @Override
343 protected void onLayout(boolean changed, int l, int t, int r, int b) {
344
345 // we layout all our children centered on the top
346 float centerX = getWidth() / 2.0f;
347 for (int i = 0; i < getChildCount(); i++) {
348 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100349 if (child.getVisibility() == GONE) {
350 continue;
351 }
Selim Cinek67b22602014-03-10 15:40:16 +0100352 float width = child.getMeasuredWidth();
353 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100354 child.layout((int) (centerX - width / 2.0f),
355 0,
356 (int) (centerX + width / 2.0f),
357 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100358 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200359 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100360 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200361 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100362 if (mRequestViewResizeAnimationOnLayout) {
363 requestAnimationOnViewResize();
364 mRequestViewResizeAnimationOnLayout = false;
365 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200366 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100367 }
368
Selim Cineka5e211b2014-08-11 17:35:48 +0200369 private void requestAnimationOnViewResize() {
Selim Cinekb5605e52015-02-20 18:21:41 +0100370 if (mIsExpanded && mAnimationsEnabled) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200371 mNeedViewResizeAnimation = true;
372 mNeedsAnimation = true;
373 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200374 }
375
Selim Cinekc27437b2014-05-14 10:23:33 +0200376 public void updateSpeedBumpIndex(int newIndex) {
377 int currentIndex = indexOfChild(mSpeedBumpView);
378
379 // If we are currently layouted before the new speed bump index, we have to decrease it.
380 boolean validIndex = newIndex > 0;
381 if (newIndex > getChildCount() - 1) {
382 validIndex = false;
383 newIndex = -1;
384 }
385 if (validIndex && currentIndex != newIndex) {
386 changeViewPosition(mSpeedBumpView, newIndex);
387 }
388 updateSpeedBump(validIndex);
389 mAmbientState.setSpeedBumpIndex(newIndex);
390 }
391
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200392 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
393 mListener = listener;
394 }
395
396 /**
397 * Returns the location the given child is currently rendered at.
398 *
399 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100400 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200401 */
402 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100403 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200404 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100405 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200406 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100407 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100408 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100409 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200410 return childViewState.location;
411 }
412
Selim Cinek67b22602014-03-10 15:40:16 +0100413 private void setMaxLayoutHeight(int maxLayoutHeight) {
414 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200415 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100416 }
417
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200418 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700419 mAmbientState.setLayoutHeight(getLayoutHeight());
420 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100421 }
422
423 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200424 * @return whether the height of the layout needs to be adapted, in order to ensure that the
425 * last child is not in the bottom stack.
426 */
427 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200428 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200429 }
430
Selim Cinek4a1ac842014-05-01 15:51:58 +0200431 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100432 * Updates the children views according to the stack scroll algorithm. Call this whenever
433 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
434 */
435 private void updateChildren() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200436 mAmbientState.setScrollY(mOwnScrollY);
437 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200438 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200439 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100440 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200441 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100442 }
443 }
444
Selim Cinek319bdc42014-05-01 23:01:58 +0200445 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200446 if (!mChildrenUpdateRequested) {
447 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
448 mChildrenUpdateRequested = true;
449 invalidate();
450 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200451 }
452
Selim Cinek67b22602014-03-10 15:40:16 +0100453 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200454 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100455 }
456
Selim Cinekf7a14c02014-07-07 14:01:46 +0200457 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100458 int scrollRange = getScrollRange();
459 if (scrollRange < mOwnScrollY) {
460 mOwnScrollY = scrollRange;
461 }
462 }
463
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200464 public int getTopPadding() {
465 return mTopPadding;
466 }
467
Selim Cinek1408eb52014-06-02 14:45:38 +0200468 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200469 if (mTopPadding != topPadding) {
470 mTopPadding = topPadding;
471 updateAlgorithmHeightAndPadding();
472 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200473 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200474 mTopPaddingNeedsAnimation = true;
475 mNeedsAnimation = true;
476 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200477 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200478 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200479 }
480 }
481
482 /**
483 * Update the height of the stack to a new height.
484 *
485 * @param height the new height of the stack
486 */
487 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100488 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200489 setIsExpanded(height > 0.0f);
490 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200491 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200492 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700493 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700494 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek684a4422015-04-15 16:18:39 -0700495 int normalUnfoldPositionStart = trackingHeadsUp ? mHeadsUpManager.getTopHeadsUpHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700496 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700497 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100498 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700499 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200500 stackHeight = newStackHeight;
501 } else {
502
503 // We did not reach the position yet where we actually start growing,
504 // so we translate the stack upwards.
505 int translationY = (newStackHeight - minStackHeight);
506 // A slight parallax effect is introduced in order for the stack to catch up with
507 // the top card.
Jorim Jaggi58bef332014-11-21 18:10:32 +0100508 float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
509 / minStackHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200510 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700511 if (!trackingHeadsUp) {
512 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
513 mCollapseSecondCardPadding);
514 } else {
515 translationY = (int) (height - mHeadsUpManager.getTopHeadsUpHeight());
516 }
517 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200518 stackHeight = (int) (height - (translationY - mTopPadding));
519 }
520 if (stackHeight != mCurrentStackHeight) {
521 mCurrentStackHeight = stackHeight;
522 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200523 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200524 }
Selim Cinekd2281152015-04-10 14:37:46 -0700525 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700526 }
527
Selim Cinekd2281152015-04-10 14:37:46 -0700528 public float getStackTranslation() {
529 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700530 }
531
Selim Cinekd2281152015-04-10 14:37:46 -0700532 private void setStackTranslation(float stackTranslation) {
533 if (stackTranslation != mStackTranslation) {
534 mStackTranslation = stackTranslation;
535 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700536 requestChildrenUpdate();
537 }
Selim Cinek67b22602014-03-10 15:40:16 +0100538 }
539
540 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100541 * 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 +0100542 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
543 *
544 * @return either the layout height or the externally defined height, whichever is smaller
545 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200546 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100547 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
548 }
549
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100550 public int getItemHeight() {
551 return mCollapsedSize;
552 }
553
554 public int getBottomStackPeekSize() {
555 return mBottomStackPeekSize;
556 }
557
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200558 public int getCollapseSecondCardPadding() {
559 return mCollapseSecondCardPadding;
560 }
561
Dan Sandler4247a5c2014-07-23 15:58:08 -0400562 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100563 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400564 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100565 }
566
Jorim Jaggi56306252014-07-03 00:40:09 +0200567 public void setScrollView(ViewGroup scrollView) {
568 mScrollView = scrollView;
569 }
570
571 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
572 mInterceptDelegateEnabled = interceptDelegateEnabled;
573 }
574
Selim Cinek67b22602014-03-10 15:40:16 +0100575 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400576 if (mDismissAllInProgress) {
577 return;
578 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100579 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200580 if (mDragAnimPendingChildren.contains(v)) {
581 // We start the swipe and finish it in the same frame, we don't want any animation
582 // for the drag
583 mDragAnimPendingChildren.remove(v);
584 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200585 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200586 mAmbientState.onDragFinished(v);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700587 if (v instanceof ExpandableNotificationRow) {
588 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
589 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700590 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700591 }
592 }
593 final View veto = v.findViewById(R.id.veto);
594 if (veto != null && veto.getVisibility() != View.GONE) {
595 veto.performClick();
596 }
597 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
Selim Cinekeb973562014-05-02 17:07:49 +0200598 }
599
600 @Override
601 public void onChildSnappedBack(View animView) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200602 mAmbientState.onDragFinished(animView);
Selim Cinekeb973562014-05-02 17:07:49 +0200603 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200604 if (mAnimationsEnabled) {
605 mSnappedBackChildren.add(animView);
606 mNeedsAnimation = true;
607 }
Selim Cinekeb973562014-05-02 17:07:49 +0200608 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200609 } else {
610 // We start the swipe and snap back in the same frame, we don't want any animation
611 mDragAnimPendingChildren.remove(animView);
612 }
Selim Cinek67b22602014-03-10 15:40:16 +0100613 }
614
Adrian Roos5d9cc662014-05-28 17:08:13 +0200615 @Override
616 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700617 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700618 mScrimController.setTopHeadsUpDragAmount(animView,
619 Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
620 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200621 return false;
622 }
623
Selim Cinek67b22602014-03-10 15:40:16 +0100624 public void onBeginDrag(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100625 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200626 mAmbientState.onBeginDrag(v);
Selim Cinek131c1e22015-05-11 19:04:49 -0700627 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200628 mDragAnimPendingChildren.add(v);
629 mNeedsAnimation = true;
630 }
Selim Cinekeb973562014-05-02 17:07:49 +0200631 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100632 }
633
Selim Cinek684a4422015-04-15 16:18:39 -0700634 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700635 if (v instanceof ExpandableNotificationRow) {
636 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700637 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700638 }
639 return false;
640 }
641
642 private boolean isHeadsUp(View v) {
643 if (v instanceof ExpandableNotificationRow) {
644 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
645 return row.isHeadsUp();
646 }
647 return false;
648 }
649
Selim Cinek67b22602014-03-10 15:40:16 +0100650 public void onDragCancelled(View v) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100651 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100652 }
653
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700654 @Override
655 public float getFalsingThresholdFactor() {
656 return mPhoneStatusBar.isScreenOnComingFromTouch() ? 1.5f : 1.0f;
657 }
658
Selim Cinek67b22602014-03-10 15:40:16 +0100659 public View getChildAtPosition(MotionEvent ev) {
660 return getChildAtPosition(ev.getX(), ev.getY());
661 }
662
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100663 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
664 getLocationOnScreen(mTempInt2);
665 float localTouchY = touchY - mTempInt2[1];
666
667 ExpandableView closestChild = null;
668 float minDist = Float.MAX_VALUE;
669
670 // find the view closest to the location, accounting for GONE views
671 final int count = getChildCount();
672 for (int childIdx = 0; childIdx < count; childIdx++) {
673 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
674 if (slidingChild.getVisibility() == GONE
675 || slidingChild instanceof StackScrollerDecorView
676 || slidingChild == mSpeedBumpView) {
677 continue;
678 }
679 float childTop = slidingChild.getTranslationY();
680 float top = childTop + slidingChild.getClipTopAmount();
681 float bottom = childTop + slidingChild.getActualHeight();
682
683 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
684 if (dist < minDist) {
685 closestChild = slidingChild;
686 minDist = dist;
687 }
688 }
689 return closestChild;
690 }
691
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200692 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100693 getLocationOnScreen(mTempInt2);
694 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100695 }
696
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200697 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100698 // find the view under the pointer, accounting for GONE views
699 final int count = getChildCount();
700 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200701 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100702 if (slidingChild.getVisibility() == GONE
703 || slidingChild instanceof StackScrollerDecorView
704 || slidingChild == mSpeedBumpView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100705 continue;
706 }
Selim Cinek89faff12014-06-19 16:29:04 -0700707 float childTop = slidingChild.getTranslationY();
708 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200709 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200710
711 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
712 // camera affordance).
713 int left = 0;
714 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100715
716 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100717 if (slidingChild instanceof ExpandableNotificationRow) {
718 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700719 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cineka59ecc32015-04-07 10:51:49 -0700720 && mHeadsUpManager.getTopEntry().entry.row != row) {
721 continue;
722 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100723 return row.getViewAtPosition(touchY - childTop);
724 }
Selim Cinek67b22602014-03-10 15:40:16 +0100725 return slidingChild;
726 }
727 }
728 return null;
729 }
730
731 public boolean canChildBeExpanded(View v) {
732 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700733 && ((ExpandableNotificationRow) v).isExpandable()
734 && !((ExpandableNotificationRow) v).isHeadsUp();
Selim Cinek67b22602014-03-10 15:40:16 +0100735 }
736
737 public void setUserExpandedChild(View v, boolean userExpanded) {
738 if (v instanceof ExpandableNotificationRow) {
739 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
740 }
741 }
742
743 public void setUserLockedChild(View v, boolean userLocked) {
744 if (v instanceof ExpandableNotificationRow) {
745 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
746 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200747 removeLongPressCallback();
748 requestDisallowInterceptTouchEvent(true);
749 }
750
751 @Override
752 public void expansionStateChanged(boolean isExpanding) {
753 mExpandingNotification = isExpanding;
754 if (!mExpandedInThisMotion) {
755 mMaxScrollAfterExpand = mOwnScrollY;
756 mExpandedInThisMotion = true;
757 }
758 }
759
760 public void setScrollingEnabled(boolean enable) {
761 mScrollingEnabled = enable;
762 }
763
764 public void setExpandingEnabled(boolean enable) {
765 mExpandHelper.setEnabled(enable);
766 }
767
768 private boolean isScrollingEnabled() {
769 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100770 }
771
772 public View getChildContentView(View v) {
773 return v;
774 }
775
776 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -0700777 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +0100778 }
779
Selim Cinek19c8c702014-08-25 22:09:19 +0200780 @Override
781 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200782 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200783 }
784
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100785 private void setSwipingInProgress(boolean isSwiped) {
786 mSwipingInProgress = isSwiped;
787 if(isSwiped) {
788 requestDisallowInterceptTouchEvent(true);
789 }
Selim Cinek67b22602014-03-10 15:40:16 +0100790 }
791
792 @Override
793 protected void onConfigurationChanged(Configuration newConfig) {
794 super.onConfigurationChanged(newConfig);
795 float densityScale = getResources().getDisplayMetrics().density;
796 mSwipeHelper.setDensityScale(densityScale);
797 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
798 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
799 initView(getContext());
800 }
801
Dan Sandlereceda3d2014-07-21 15:35:01 -0400802 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400803 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100804 }
805
806 @Override
807 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200808 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
809 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Jorim Jaggi56306252014-07-03 00:40:09 +0200810 if (mDelegateToScrollView) {
811 if (isCancelOrUp) {
812 mDelegateToScrollView = false;
813 }
814 transformTouchEvent(ev, this, mScrollView);
815 return mScrollView.onTouchEvent(ev);
816 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100817 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200818 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +0200819 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200820 if (isCancelOrUp) {
821 mExpandHelper.onlyObserveMovements(false);
822 }
823 boolean wasExpandingBefore = mExpandingNotification;
824 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200825 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
826 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200827 dispatchDownEventToScroller(ev);
828 }
829 }
Selim Cinek67b22602014-03-10 15:40:16 +0100830 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700831 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
832 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100833 scrollerWantsIt = onScrollTouch(ev);
834 }
835 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200836 if (!mIsBeingDragged
837 && !mExpandingNotification
838 && !mExpandedInThisMotion
839 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100840 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
841 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200842 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
843 }
844
845 private void dispatchDownEventToScroller(MotionEvent ev) {
846 MotionEvent downEvent = MotionEvent.obtain(ev);
847 downEvent.setAction(MotionEvent.ACTION_DOWN);
848 onScrollTouch(downEvent);
849 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100850 }
851
852 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200853 if (!isScrollingEnabled()) {
854 return false;
855 }
Selim Cinek67b22602014-03-10 15:40:16 +0100856 initVelocityTrackerIfNotExists();
857 mVelocityTracker.addMovement(ev);
858
859 final int action = ev.getAction();
860
861 switch (action & MotionEvent.ACTION_MASK) {
862 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +0200863 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +0100864 return false;
865 }
866 boolean isBeingDragged = !mScroller.isFinished();
867 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +0100868
869 /*
870 * If being flinged and user touches, stop the fling. isFinished
871 * will be false if being flinged.
872 */
873 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200874 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100875 }
876
877 // Remember where the motion event started
878 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +0200879 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +0100880 mActivePointerId = ev.getPointerId(0);
881 break;
882 }
883 case MotionEvent.ACTION_MOVE:
884 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
885 if (activePointerIndex == -1) {
886 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
887 break;
888 }
889
890 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +0200891 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +0100892 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200893 final int xDiff = Math.abs(x - mDownX);
894 final int yDiff = Math.abs(deltaY);
895 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +0100896 setIsBeingDragged(true);
897 if (deltaY > 0) {
898 deltaY -= mTouchSlop;
899 } else {
900 deltaY += mTouchSlop;
901 }
902 }
903 if (mIsBeingDragged) {
904 // Scroll to follow the motion event
905 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200906 int range = getScrollRange();
907 if (mExpandedInThisMotion) {
908 range = Math.min(range, mMaxScrollAfterExpand);
909 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200910
911 float scrollAmount;
912 if (deltaY < 0) {
913 scrollAmount = overScrollDown(deltaY);
914 } else {
915 scrollAmount = overScrollUp(deltaY, range);
916 }
Selim Cinek67b22602014-03-10 15:40:16 +0100917
918 // Calling overScrollBy will call onOverScrolled, which
919 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200920 if (scrollAmount != 0.0f) {
921 // The scrolling motion could not be compensated with the
922 // existing overScroll, we have to scroll the view
923 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
924 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +0100925 }
Selim Cinek67b22602014-03-10 15:40:16 +0100926 }
927 break;
928 case MotionEvent.ACTION_UP:
929 if (mIsBeingDragged) {
930 final VelocityTracker velocityTracker = mVelocityTracker;
931 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
932 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
933
Selim Cinek1408eb52014-06-02 14:45:38 +0200934 if (shouldOverScrollFling(initialVelocity)) {
935 onOverScrollFling(true, initialVelocity);
936 } else {
937 if (getChildCount() > 0) {
938 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
939 float currentOverScrollTop = getCurrentOverScrollAmount(true);
940 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
941 fling(-initialVelocity);
942 } else {
943 onOverScrollFling(false, initialVelocity);
944 }
945 } else {
946 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
947 getScrollRange())) {
948 postInvalidateOnAnimation();
949 }
Selim Cinek67b22602014-03-10 15:40:16 +0100950 }
951 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200952 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700953
954 mActivePointerId = INVALID_POINTER;
955 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +0100956 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700957
Selim Cinek67b22602014-03-10 15:40:16 +0100958 break;
959 case MotionEvent.ACTION_CANCEL:
960 if (mIsBeingDragged && getChildCount() > 0) {
961 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
962 postInvalidateOnAnimation();
963 }
964 mActivePointerId = INVALID_POINTER;
965 endDrag();
966 }
967 break;
968 case MotionEvent.ACTION_POINTER_DOWN: {
969 final int index = ev.getActionIndex();
970 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +0200971 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +0100972 mActivePointerId = ev.getPointerId(index);
973 break;
974 }
975 case MotionEvent.ACTION_POINTER_UP:
976 onSecondaryPointerUp(ev);
977 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +0200978 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +0100979 break;
980 }
981 return true;
982 }
983
Selim Cinek1408eb52014-06-02 14:45:38 +0200984 private void onOverScrollFling(boolean open, int initialVelocity) {
985 if (mOverscrollTopChangedListener != null) {
986 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
987 }
988 mDontReportNextOverScroll = true;
989 setOverScrollAmount(0.0f, true, false);
990 }
991
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200992 /**
993 * Perform a scroll upwards and adapt the overscroll amounts accordingly
994 *
995 * @param deltaY The amount to scroll upwards, has to be positive.
996 * @return The amount of scrolling to be performed by the scroller,
997 * not handled by the overScroll amount.
998 */
999 private float overScrollUp(int deltaY, int range) {
1000 deltaY = Math.max(deltaY, 0);
1001 float currentTopAmount = getCurrentOverScrollAmount(true);
1002 float newTopAmount = currentTopAmount - deltaY;
1003 if (currentTopAmount > 0) {
1004 setOverScrollAmount(newTopAmount, true /* onTop */,
1005 false /* animate */);
1006 }
1007 // Top overScroll might not grab all scrolling motion,
1008 // we have to scroll as well.
1009 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1010 float newScrollY = mOwnScrollY + scrollAmount;
1011 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001012 if (!mExpandedInThisMotion) {
1013 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1014 // We overScroll on the top
1015 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1016 false /* onTop */,
1017 false /* animate */);
1018 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001019 mOwnScrollY = range;
1020 scrollAmount = 0.0f;
1021 }
1022 return scrollAmount;
1023 }
1024
1025 /**
1026 * Perform a scroll downward and adapt the overscroll amounts accordingly
1027 *
1028 * @param deltaY The amount to scroll downwards, has to be negative.
1029 * @return The amount of scrolling to be performed by the scroller,
1030 * not handled by the overScroll amount.
1031 */
1032 private float overScrollDown(int deltaY) {
1033 deltaY = Math.min(deltaY, 0);
1034 float currentBottomAmount = getCurrentOverScrollAmount(false);
1035 float newBottomAmount = currentBottomAmount + deltaY;
1036 if (currentBottomAmount > 0) {
1037 setOverScrollAmount(newBottomAmount, false /* onTop */,
1038 false /* animate */);
1039 }
1040 // Bottom overScroll might not grab all scrolling motion,
1041 // we have to scroll as well.
1042 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1043 float newScrollY = mOwnScrollY + scrollAmount;
1044 if (newScrollY < 0) {
1045 float currentTopPixels = getCurrentOverScrolledPixels(true);
1046 // We overScroll on the top
1047 setOverScrolledPixels(currentTopPixels - newScrollY,
1048 true /* onTop */,
1049 false /* animate */);
1050 mOwnScrollY = 0;
1051 scrollAmount = 0.0f;
1052 }
1053 return scrollAmount;
1054 }
1055
Selim Cinek67b22602014-03-10 15:40:16 +01001056 private void onSecondaryPointerUp(MotionEvent ev) {
1057 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1058 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1059 final int pointerId = ev.getPointerId(pointerIndex);
1060 if (pointerId == mActivePointerId) {
1061 // This was our active pointer going up. Choose a new
1062 // active pointer and adjust accordingly.
1063 // TODO: Make this decision more intelligent.
1064 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1065 mLastMotionY = (int) ev.getY(newPointerIndex);
1066 mActivePointerId = ev.getPointerId(newPointerIndex);
1067 if (mVelocityTracker != null) {
1068 mVelocityTracker.clear();
1069 }
1070 }
1071 }
1072
1073 private void initVelocityTrackerIfNotExists() {
1074 if (mVelocityTracker == null) {
1075 mVelocityTracker = VelocityTracker.obtain();
1076 }
1077 }
1078
1079 private void recycleVelocityTracker() {
1080 if (mVelocityTracker != null) {
1081 mVelocityTracker.recycle();
1082 mVelocityTracker = null;
1083 }
1084 }
1085
1086 private void initOrResetVelocityTracker() {
1087 if (mVelocityTracker == null) {
1088 mVelocityTracker = VelocityTracker.obtain();
1089 } else {
1090 mVelocityTracker.clear();
1091 }
1092 }
1093
1094 @Override
1095 public void computeScroll() {
1096 if (mScroller.computeScrollOffset()) {
1097 // This is called at drawing time by ViewGroup.
1098 int oldX = mScrollX;
1099 int oldY = mOwnScrollY;
1100 int x = mScroller.getCurrX();
1101 int y = mScroller.getCurrY();
1102
1103 if (oldX != x || oldY != y) {
1104 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001105 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1106 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001107 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001108 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1109 }
1110 }
Selim Cinek67b22602014-03-10 15:40:16 +01001111
1112 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001113 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001114 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001115 }
1116
1117 // Keep on drawing until the animation has finished.
1118 postInvalidateOnAnimation();
1119 }
1120 }
1121
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001122 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001123 protected boolean overScrollBy(int deltaX, int deltaY,
1124 int scrollX, int scrollY,
1125 int scrollRangeX, int scrollRangeY,
1126 int maxOverScrollX, int maxOverScrollY,
1127 boolean isTouchEvent) {
1128
1129 int newScrollY = scrollY + deltaY;
1130
1131 final int top = -maxOverScrollY;
1132 final int bottom = maxOverScrollY + scrollRangeY;
1133
1134 boolean clampedY = false;
1135 if (newScrollY > bottom) {
1136 newScrollY = bottom;
1137 clampedY = true;
1138 } else if (newScrollY < top) {
1139 newScrollY = top;
1140 clampedY = true;
1141 }
1142
1143 onOverScrolled(0, newScrollY, false, clampedY);
1144
1145 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001146 }
1147
1148 /**
1149 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1150 * overscroll effect based on numPixels. By default this will also cancel animations on the
1151 * same overScroll edge.
1152 *
1153 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1154 * the rubber-banding logic.
1155 * @param onTop Should the effect be applied on top of the scroller.
1156 * @param animate Should an animation be performed.
1157 */
1158 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001159 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001160 }
1161
1162 /**
1163 * Set the effective overScroll amount which will be directly reflected in the layout.
1164 * By default this will also cancel animations on the same overScroll edge.
1165 *
1166 * @param amount The amount to overScroll by.
1167 * @param onTop Should the effect be applied on top of the scroller.
1168 * @param animate Should an animation be performed.
1169 */
1170 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1171 setOverScrollAmount(amount, onTop, animate, true);
1172 }
1173
1174 /**
1175 * Set the effective overScroll amount which will be directly reflected in the layout.
1176 *
1177 * @param amount The amount to overScroll by.
1178 * @param onTop Should the effect be applied on top of the scroller.
1179 * @param animate Should an animation be performed.
1180 * @param cancelAnimators Should running animations be cancelled.
1181 */
1182 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1183 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001184 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1185 }
1186
1187 /**
1188 * Set the effective overScroll amount which will be directly reflected in the layout.
1189 *
1190 * @param amount The amount to overScroll by.
1191 * @param onTop Should the effect be applied on top of the scroller.
1192 * @param animate Should an animation be performed.
1193 * @param cancelAnimators Should running animations be cancelled.
1194 * @param isRubberbanded The value which will be passed to
1195 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1196 */
1197 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1198 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001199 if (cancelAnimators) {
1200 mStateAnimator.cancelOverScrollAnimators(onTop);
1201 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001202 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001203 }
1204
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001205 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1206 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001207 amount = Math.max(0, amount);
1208 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001209 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001210 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001211 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001212 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001213 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001214 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001215 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001216 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001217 }
1218 }
1219
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001220 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001221 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1222 if (mDontReportNextOverScroll) {
1223 mDontReportNextOverScroll = false;
1224 return;
1225 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001226 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001227 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001228 }
1229 }
1230
1231 public void setOverscrollTopChangedListener(
1232 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1233 mOverscrollTopChangedListener = overscrollTopChangedListener;
1234 }
1235
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001236 public float getCurrentOverScrollAmount(boolean top) {
1237 return mAmbientState.getOverScrollAmount(top);
1238 }
1239
1240 public float getCurrentOverScrolledPixels(boolean top) {
1241 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1242 }
1243
1244 private void setOverScrolledPixels(float amount, boolean onTop) {
1245 if (onTop) {
1246 mOverScrolledTopPixels = amount;
1247 } else {
1248 mOverScrolledBottomPixels = amount;
1249 }
1250 }
1251
Selim Cinek319bdc42014-05-01 23:01:58 +02001252 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001253 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001254 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001255 }
1256
1257 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001258 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001259 // Treat animating scrolls differently; see #computeScroll() for why.
1260 if (!mScroller.isFinished()) {
1261 final int oldX = mScrollX;
1262 final int oldY = mOwnScrollY;
1263 mScrollX = scrollX;
1264 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001265 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001266 springBack();
1267 } else {
1268 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1269 invalidateParentIfNeeded();
1270 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001271 float overScrollTop = getCurrentOverScrollAmount(true);
1272 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001273 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001274 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001275 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001276 }
Selim Cinek67b22602014-03-10 15:40:16 +01001277 }
Selim Cinek67b22602014-03-10 15:40:16 +01001278 } else {
1279 customScrollTo(scrollY);
1280 scrollTo(scrollX, mScrollY);
1281 }
1282 }
1283
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001284 private void springBack() {
1285 int scrollRange = getScrollRange();
1286 boolean overScrolledTop = mOwnScrollY <= 0;
1287 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1288 if (overScrolledTop || overScrolledBottom) {
1289 boolean onTop;
1290 float newAmount;
1291 if (overScrolledTop) {
1292 onTop = true;
1293 newAmount = -mOwnScrollY;
1294 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001295 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001296 } else {
1297 onTop = false;
1298 newAmount = mOwnScrollY - scrollRange;
1299 mOwnScrollY = scrollRange;
1300 }
1301 setOverScrollAmount(newAmount, onTop, false);
1302 setOverScrollAmount(0.0f, onTop, true);
1303 mScroller.forceFinished(true);
1304 }
1305 }
1306
Selim Cinek67b22602014-03-10 15:40:16 +01001307 private int getScrollRange() {
1308 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001309 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001310 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001311 int contentHeight = getContentHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001312 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cineka5eaa602014-05-12 21:27:47 +02001313 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1314 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001315 if (scrollRange > 0) {
1316 View lastChild = getLastChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001317 // We want to at least be able collapse the first item and not ending in a weird
1318 // end state.
1319 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1320 }
Selim Cinek67b22602014-03-10 15:40:16 +01001321 }
1322 return scrollRange;
1323 }
1324
Selim Cinek343e6e22014-04-11 21:23:30 +02001325 /**
1326 * @return the first child which has visibility unequal to GONE
1327 */
1328 private View getFirstChildNotGone() {
1329 int childCount = getChildCount();
1330 for (int i = 0; i < childCount; i++) {
1331 View child = getChildAt(i);
1332 if (child.getVisibility() != View.GONE) {
1333 return child;
1334 }
1335 }
1336 return null;
1337 }
1338
Selim Cinek4a1ac842014-05-01 15:51:58 +02001339 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001340 * @return The first child which has visibility unequal to GONE which is currently below the
1341 * given translationY or equal to it.
1342 */
1343 private View getFirstChildBelowTranlsationY(float translationY) {
1344 int childCount = getChildCount();
1345 for (int i = 0; i < childCount; i++) {
1346 View child = getChildAt(i);
1347 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1348 return child;
1349 }
1350 }
1351 return null;
1352 }
1353
1354 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001355 * @return the last child which has visibility unequal to GONE
1356 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001357 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001358 int childCount = getChildCount();
1359 for (int i = childCount - 1; i >= 0; i--) {
1360 View child = getChildAt(i);
1361 if (child.getVisibility() != View.GONE) {
1362 return child;
1363 }
1364 }
1365 return null;
1366 }
1367
Jorim Jaggi069cd032014-05-15 03:09:01 +02001368 /**
1369 * @return the number of children which have visibility unequal to GONE
1370 */
1371 public int getNotGoneChildCount() {
1372 int childCount = getChildCount();
1373 int count = 0;
1374 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001375 ExpandableView child = (ExpandableView) getChildAt(i);
1376 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001377 count++;
1378 }
1379 }
1380 return count;
1381 }
1382
Selim Cinek343e6e22014-04-11 21:23:30 +02001383 private int getMaxExpandHeight(View view) {
1384 if (view instanceof ExpandableNotificationRow) {
1385 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001386 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001387 }
1388 return view.getHeight();
1389 }
1390
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001391 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001392 return mContentHeight;
1393 }
1394
1395 private void updateContentHeight() {
1396 int height = 0;
1397 for (int i = 0; i < getChildCount(); i++) {
1398 View child = getChildAt(i);
Jorim Jaggid4a57442014-04-10 02:45:55 +02001399 if (child.getVisibility() != View.GONE) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001400 if (height != 0) {
1401 // add the padding before this element
Jorim Jaggid4a57442014-04-10 02:45:55 +02001402 height += mPaddingBetweenElements;
1403 }
Selim Cineke53e6bb2015-04-13 16:14:26 -07001404 if (child instanceof ExpandableView) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001405 ExpandableView expandableView = (ExpandableView) child;
Selim Cineke53e6bb2015-04-13 16:14:26 -07001406 height += expandableView.getIntrinsicHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001407 }
Selim Cinek67b22602014-03-10 15:40:16 +01001408 }
1409 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001410 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001411 }
1412
1413 /**
1414 * Fling the scroll view
1415 *
1416 * @param velocityY The initial velocity in the Y direction. Positive
1417 * numbers mean that the finger/cursor is moving down the screen,
1418 * which means we want to scroll towards the top.
1419 */
1420 private void fling(int velocityY) {
1421 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001422 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001423
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001424 float topAmount = getCurrentOverScrollAmount(true);
1425 float bottomAmount = getCurrentOverScrollAmount(false);
1426 if (velocityY < 0 && topAmount > 0) {
1427 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001428 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001429 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001430 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001431 * mOverflingDistance + topAmount;
1432 } else if (velocityY > 0 && bottomAmount > 0) {
1433 mOwnScrollY += bottomAmount;
1434 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001435 mMaxOverScroll = Math.abs(velocityY) / 1000f
1436 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1437 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001438 } else {
1439 // it will be set once we reach the boundary
1440 mMaxOverScroll = 0.0f;
1441 }
1442 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek4195dd02014-05-19 18:16:14 +02001443 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001444
1445 postInvalidateOnAnimation();
1446 }
1447 }
1448
Selim Cinek1408eb52014-06-02 14:45:38 +02001449 /**
1450 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1451 * overScroll view (i.e QS).
1452 */
1453 private boolean shouldOverScrollFling(int initialVelocity) {
1454 float topOverScroll = getCurrentOverScrollAmount(true);
1455 return mScrolledToTopOnFirstDown
1456 && !mExpandedInThisMotion
1457 && topOverScroll > mMinTopOverScrollToEscape
1458 && initialVelocity > 0;
1459 }
1460
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001461 /**
1462 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1463 * account.
1464 *
1465 * @param qsHeight the top padding imposed by the quick settings panel
1466 * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
1467 * container
1468 * @param animate whether to animate the change
1469 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1470 * {@code qsHeight} is the final top padding
1471 */
1472 public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
1473 boolean ignoreIntrinsicPadding) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001474 float start = qsHeight - scrollY + mNotificationTopPadding;
1475 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001476 int minStackHeight = getMinStackHeight();
1477 if (stackHeight <= minStackHeight) {
1478 float overflow = minStackHeight - stackHeight;
1479 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001480 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001481 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001482 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001483 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001484 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001485 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1486 animate);
1487 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001488 }
1489
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001490 public int getNotificationTopPadding() {
1491 return mNotificationTopPadding;
1492 }
1493
1494 public int getMinStackHeight() {
Selim Cinekd83771e2014-07-04 16:45:31 +02001495 return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001496 }
1497
1498 public float getTopPaddingOverflow() {
1499 return mTopPaddingOverflow;
1500 }
1501
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001502 public int getPeekHeight() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001503 return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
1504 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001505 }
1506
Selim Cinek1408eb52014-06-02 14:45:38 +02001507 private int clampPadding(int desiredPadding) {
1508 return Math.max(desiredPadding, mIntrinsicPadding);
1509 }
1510
Selim Cinekfed1ab62014-06-17 14:10:33 -07001511 private float getRubberBandFactor(boolean onTop) {
1512 if (!onTop) {
1513 return RUBBER_BAND_FACTOR_NORMAL;
1514 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001515 if (mExpandedInThisMotion) {
1516 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001517 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001518 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1519 } else if (mScrolledToTopOnFirstDown) {
1520 return 1.0f;
1521 }
1522 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001523 }
1524
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001525 /**
1526 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1527 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1528 * overscroll view (e.g. expand QS).
1529 */
1530 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001531 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001532 || !mScrolledToTopOnFirstDown;
1533 }
1534
Selim Cinek67b22602014-03-10 15:40:16 +01001535 private void endDrag() {
1536 setIsBeingDragged(false);
1537
1538 recycleVelocityTracker();
1539
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001540 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1541 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1542 }
1543 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1544 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1545 }
Selim Cinek67b22602014-03-10 15:40:16 +01001546 }
1547
Jorim Jaggi56306252014-07-03 00:40:09 +02001548 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1549 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1550 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1551 }
1552
Selim Cinek67b22602014-03-10 15:40:16 +01001553 @Override
1554 public boolean onInterceptTouchEvent(MotionEvent ev) {
Jorim Jaggi56306252014-07-03 00:40:09 +02001555 if (mInterceptDelegateEnabled) {
1556 transformTouchEvent(ev, this, mScrollView);
1557 if (mScrollView.onInterceptTouchEvent(ev)) {
1558 mDelegateToScrollView = true;
1559 removeLongPressCallback();
1560 return true;
1561 }
1562 transformTouchEvent(ev, mScrollView, this);
1563 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001564 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001565 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001566 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001567 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001568 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1569 }
Selim Cinek67b22602014-03-10 15:40:16 +01001570 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001571 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001572 scrollWantsIt = onInterceptTouchEventScroll(ev);
1573 }
1574 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001575 if (!mIsBeingDragged
1576 && !mExpandingNotification
1577 && !mExpandedInThisMotion
1578 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001579 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1580 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001581 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1582 }
1583
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001584 private void handleEmptySpaceClick(MotionEvent ev) {
1585 switch (ev.getActionMasked()) {
1586 case MotionEvent.ACTION_MOVE:
1587 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1588 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1589 mTouchIsClick = false;
1590 }
1591 break;
1592 case MotionEvent.ACTION_UP:
1593 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1594 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1595 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1596 }
1597 break;
1598 }
1599 }
1600
Selim Cinek1408eb52014-06-02 14:45:38 +02001601 private void initDownStates(MotionEvent ev) {
1602 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1603 mExpandedInThisMotion = false;
1604 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001605 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001606 mTouchIsClick = true;
1607 mInitialTouchX = ev.getX();
1608 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001609 }
Selim Cinek67b22602014-03-10 15:40:16 +01001610 }
1611
Christoph Studer068f5922014-04-08 17:43:07 -04001612 @Override
Adam Powell6690d012015-06-17 16:41:56 -07001613 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04001614 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001615 // we only call our internal methods if this is actually a removal and not just a
1616 // notification which becomes a child notification
1617 if (!isChildInGroup(child)) {
1618 onViewRemovedInternal(child);
1619 }
1620 }
1621
1622 private void onViewRemovedInternal(View child) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001623 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001624 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001625 // This is only a position change, don't do anything special
1626 return;
1627 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001628 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04001629 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001630 updateScrollStateForRemovedChild(child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07001631 boolean animationGenerated = generateRemoveAnimation(child);
1632 if (animationGenerated && !mSwipedOutViews.contains(child)) {
1633 // Add this view to an overlay in order to ensure that it will still be temporary
1634 // drawn when removed
1635 getOverlay().add(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001636 }
Selim Cinekcab4a602014-09-03 14:47:57 +02001637 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02001638
1639 // Make sure the clipRect we might have set is removed
Selim Cineka272dfe2015-02-20 18:12:28 +01001640 ((ExpandableView) child).setClipTopOptimization(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02001641 }
1642
Selim Cinekb5605e52015-02-20 18:21:41 +01001643 private boolean isChildInGroup(View child) {
1644 return child instanceof ExpandableNotificationRow
1645 && mGroupManager.isChildInGroupWithSummary(
1646 ((ExpandableNotificationRow) child).getStatusBarNotification());
1647 }
1648
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001649 /**
1650 * Generate a remove animation for a child view.
1651 *
1652 * @param child The view to generate the remove animation for.
1653 * @return Whether an animation was generated.
1654 */
1655 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07001656 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07001657 mAddedHeadsUpChildren.remove(child);
1658 return false;
1659 }
Selim Cinek0fccc722015-07-29 17:04:36 -07001660 if (isClickedHeadsUp(child)) {
1661 // An animation is already running, add it to the Overlay
1662 mClearOverlayViewsWhenFinished.add(child);
1663 return true;
1664 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001665 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07001666 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02001667 // Generate Animations
1668 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001669 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001670 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02001671 } else {
1672 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001673 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001674 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02001675 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001676 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001677 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001678 }
1679
Selim Cinek0fccc722015-07-29 17:04:36 -07001680 private boolean isClickedHeadsUp(View child) {
1681 return HeadsUpManager.isClickedHeadsUpNotification(child);
1682 }
1683
Selim Cineke0890e52015-06-17 11:17:08 -07001684 /**
1685 * Remove a removed child view from the heads up animations if it was just added there
1686 *
1687 * @return whether any child was removed from the list to animate
1688 */
1689 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
1690 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001691 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
1692 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07001693 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001694 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07001695 mTmpList.add(eventPair);
1696 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001697 }
1698 }
Selim Cineke0890e52015-06-17 11:17:08 -07001699 if (hasAddEvent) {
1700 // This child was just added lets remove all events.
1701 mHeadsUpChangeAnimations.removeAll(mTmpList);
1702 }
1703 mTmpList.clear();
1704 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001705 }
1706
Selim Cinek572bbd42014-04-25 16:43:27 +02001707 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01001708 * @param child the child to query
1709 * @return whether a view is not a top level child but a child notification and that group is
1710 * not expanded
1711 */
1712 private boolean isChildInInvisibleGroup(View child) {
1713 if (child instanceof ExpandableNotificationRow) {
1714 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1715 ExpandableNotificationRow groupSummary =
1716 mGroupManager.getGroupSummary(row.getStatusBarNotification());
1717 if (groupSummary != null && groupSummary != row) {
1718 return !groupSummary.areChildrenExpanded();
1719 }
1720 }
1721 return false;
1722 }
1723
1724 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02001725 * Updates the scroll position when a child was removed
1726 *
1727 * @param removedChild the removed child
1728 */
1729 private void updateScrollStateForRemovedChild(View removedChild) {
1730 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinekd7c4e002014-07-04 18:36:42 +02001731 int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
Selim Cinek572bbd42014-04-25 16:43:27 +02001732 int endPosition = startingPosition + childHeight;
1733 if (endPosition <= mOwnScrollY) {
1734 // This child is fully scrolled of the top, so we have to deduct its height from the
1735 // scrollPosition
1736 mOwnScrollY -= childHeight;
1737 } else if (startingPosition < mOwnScrollY) {
1738 // This child is currently being scrolled into, set the scroll position to the start of
1739 // this child
1740 mOwnScrollY = startingPosition;
1741 }
1742 }
1743
Selim Cinekd7c4e002014-07-04 18:36:42 +02001744 private int getIntrinsicHeight(View view) {
1745 if (view instanceof ExpandableView) {
1746 ExpandableView expandableView = (ExpandableView) view;
1747 return expandableView.getIntrinsicHeight();
1748 }
1749 return view.getHeight();
1750 }
1751
Selim Cinek572bbd42014-04-25 16:43:27 +02001752 private int getPositionInLinearLayout(View requestedChild) {
1753 int position = 0;
1754 for (int i = 0; i < getChildCount(); i++) {
1755 View child = getChildAt(i);
1756 if (child == requestedChild) {
1757 return position;
1758 }
1759 if (child.getVisibility() != View.GONE) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02001760 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001761 if (i < getChildCount()-1) {
1762 position += mPaddingBetweenElements;
1763 }
1764 }
1765 }
1766 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02001767 }
1768
1769 @Override
Adam Powell6690d012015-06-17 16:41:56 -07001770 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02001771 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001772 onViewAddedInternal(child);
1773 }
1774
1775 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07001776 updateHideSensitiveForChild(child);
Selim Cinek1685e632014-04-08 02:27:49 +02001777 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02001778 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00001779 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02001780 updateAnimationState(child);
Selim Cinek7d5f3742014-11-07 18:07:49 +01001781 if (canChildBeDismissed(child)) {
1782 // Make sure the dismissButton is visible and not in the animated state.
1783 // We need to do this to avoid a race where a clearable notification is added after the
1784 // dismiss animation is finished
1785 mDismissView.showClearButton();
1786 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001787 }
1788
Selim Cinekd06c41c2015-07-06 14:51:36 -07001789 private void updateHideSensitiveForChild(View child) {
1790 if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) {
1791 ExpandableView expandableView = (ExpandableView) child;
1792 expandableView.setHideSensitiveForIntrinsicHeight(true);
1793 }
1794 }
1795
Selim Cinekb5605e52015-02-20 18:21:41 +01001796 public void notifyGroupChildRemoved(View row) {
1797 onViewRemovedInternal(row);
1798 }
1799
1800 public void notifyGroupChildAdded(View row) {
1801 onViewAddedInternal(row);
1802 }
1803
Jorim Jaggi75c95042014-05-16 19:09:59 +02001804 public void setAnimationsEnabled(boolean animationsEnabled) {
1805 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02001806 updateNotificationAnimationStates();
1807 }
1808
1809 private void updateNotificationAnimationStates() {
Selim Cinek8d490d42015-04-10 00:05:50 -07001810 boolean running = mAnimationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02001811 int childCount = getChildCount();
1812 for (int i = 0; i < childCount; i++) {
1813 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07001814 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02001815 updateAnimationState(running, child);
1816 }
1817 }
1818
Selim Cinek51ae05d2014-09-09 15:51:38 +02001819 private void updateAnimationState(View child) {
Selim Cinek8d490d42015-04-10 00:05:50 -07001820 updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02001821 }
1822
1823
Selim Cinekcab4a602014-09-03 14:47:57 +02001824 private void updateAnimationState(boolean running, View child) {
1825 if (child instanceof ExpandableNotificationRow) {
1826 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1827 row.setIconAnimationRunning(running);
1828 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02001829 }
1830
1831 public boolean isAddOrRemoveAnimationPending() {
1832 return mNeedsAnimation
1833 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1834 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001835 /**
1836 * Generate an animation for an added child view.
1837 *
1838 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00001839 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001840 */
Jorim Jaggif6411742014-08-05 17:10:43 +00001841 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001842 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001843 // Generate Animations
1844 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00001845 if (fromMoreCard) {
1846 mFromMoreCardAdditions.add(child);
1847 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001848 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02001849 }
Selim Cineka59ecc32015-04-07 10:51:49 -07001850 if (isHeadsUp(child)) {
1851 mAddedHeadsUpChildren.add(child);
1852 mChildrenToAddAnimated.remove(child);
1853 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001854 }
1855
1856 /**
1857 * Change the position of child to a new location
1858 *
1859 * @param child the view to change the position for
1860 * @param newIndex the new index
1861 */
1862 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04001863 int currentIndex = indexOfChild(child);
1864 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001865 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02001866 removeView(child);
1867 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001868 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04001869 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001870 mChildrenChangingPositions.add(child);
1871 mNeedsAnimation = true;
1872 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001873 }
1874 }
1875
Selim Cinekf4c19962014-05-01 21:55:31 +02001876 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001877 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001878 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001879 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001880 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001881 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001882 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1883 mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001884 mAnimationEvents.clear();
Selim Cinekf4c19962014-05-01 21:55:31 +02001885 } else {
1886 applyCurrentState();
1887 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001888 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02001889 }
1890
1891 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07001892 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02001893 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001894 generateChildAdditionEvents();
1895 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02001896 generateSnapBackEvents();
1897 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001898 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001899 generateActivateEvent();
1900 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02001901 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04001902 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001903 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02001904 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01001905 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02001906 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001907 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001908 }
1909
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001910 private void generateHeadsUpAnimationEvents() {
1911 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001912 ExpandableNotificationRow row = eventPair.first;
1913 boolean isHeadsUp = eventPair.second;
1914 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
1915 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07001916 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07001917 if (!mIsExpanded && !isHeadsUp) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001918 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinekeaee9c02015-06-25 11:04:20 -04001919 } else {
1920 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
1921 if (viewState == null) {
1922 // A view state was never generated for this view, so we don't need to animate
1923 // this. This may happen with notification children.
1924 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07001925 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04001926 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
1927 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
1928 // Our custom add animation
1929 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
1930 } else {
1931 // Normal add animation
1932 type = AnimationEvent.ANIMATION_TYPE_ADD;
1933 }
1934 onBottom = !pinnedAndClosed;
1935 }
Selim Cineka59ecc32015-04-07 10:51:49 -07001936 }
1937 AnimationEvent event = new AnimationEvent(row, type);
1938 event.headsUpFromBottom = onBottom;
1939 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001940 }
1941 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07001942 mAddedHeadsUpChildren.clear();
1943 }
1944
Selim Cinekeaee9c02015-06-25 11:04:20 -04001945 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001946 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
1947 return false;
1948 }
1949 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001950 }
1951
Selim Cinekb5605e52015-02-20 18:21:41 +01001952 private void generateGroupExpansionEvent() {
1953 // Generate a group expansion/collapsing event if there is such a group at all
1954 if (mExpandedGroupView != null) {
1955 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
1956 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
1957 mExpandedGroupView = null;
1958 }
1959 }
1960
Selim Cineka5e211b2014-08-11 17:35:48 +02001961 private void generateViewResizeEvent() {
1962 if (mNeedViewResizeAnimation) {
1963 mAnimationEvents.add(
1964 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
1965 }
1966 mNeedViewResizeAnimation = false;
1967 }
1968
Selim Cinekeb973562014-05-02 17:07:49 +02001969 private void generateSnapBackEvents() {
1970 for (View child : mSnappedBackChildren) {
1971 mAnimationEvents.add(new AnimationEvent(child,
1972 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1973 }
1974 mSnappedBackChildren.clear();
1975 }
1976
1977 private void generateDragEvents() {
1978 for (View child : mDragAnimPendingChildren) {
1979 mAnimationEvents.add(new AnimationEvent(child,
1980 AnimationEvent.ANIMATION_TYPE_START_DRAG));
1981 }
1982 mDragAnimPendingChildren.clear();
1983 }
1984
Selim Cinek572bbd42014-04-25 16:43:27 +02001985 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001986 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001987 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1988 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001989 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1990 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001991 AnimationEvent event = new AnimationEvent(child, animationType);
1992
1993 // we need to know the view after this one
1994 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1995 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02001996 }
1997 mSwipedOutViews.clear();
1998 mChildrenToRemoveAnimated.clear();
1999 }
2000
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002001 private void generatePositionChangeEvents() {
2002 for (View child : mChildrenChangingPositions) {
2003 mAnimationEvents.add(new AnimationEvent(child,
2004 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2005 }
2006 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002007 if (mGenerateChildOrderChangedEvent) {
2008 mAnimationEvents.add(new AnimationEvent(null,
2009 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2010 mGenerateChildOrderChangedEvent = false;
2011 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002012 }
2013
Selim Cinek572bbd42014-04-25 16:43:27 +02002014 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002015 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002016 if (mFromMoreCardAdditions.contains(child)) {
2017 mAnimationEvents.add(new AnimationEvent(child,
2018 AnimationEvent.ANIMATION_TYPE_ADD,
2019 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2020 } else {
2021 mAnimationEvents.add(new AnimationEvent(child,
2022 AnimationEvent.ANIMATION_TYPE_ADD));
2023 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002024 }
2025 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002026 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002027 }
2028
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002029 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002030 if (mTopPaddingNeedsAnimation) {
2031 mAnimationEvents.add(
2032 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2033 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002034 mTopPaddingNeedsAnimation = false;
2035 }
2036
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002037 private void generateActivateEvent() {
2038 if (mActivateNeedsAnimation) {
2039 mAnimationEvents.add(
2040 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2041 }
2042 mActivateNeedsAnimation = false;
2043 }
2044
Selim Cinekd9acca52014-09-01 22:33:25 +02002045 private void generateAnimateEverythingEvent() {
2046 if (mEverythingNeedsAnimation) {
2047 mAnimationEvents.add(
2048 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2049 }
2050 mEverythingNeedsAnimation = false;
2051 }
2052
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002053 private void generateDimmedEvent() {
2054 if (mDimmedNeedsAnimation) {
2055 mAnimationEvents.add(
2056 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2057 }
2058 mDimmedNeedsAnimation = false;
2059 }
2060
Jorim Jaggiae441282014-08-01 02:45:18 +02002061 private void generateHideSensitiveEvent() {
2062 if (mHideSensitiveNeedsAnimation) {
2063 mAnimationEvents.add(
2064 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2065 }
2066 mHideSensitiveNeedsAnimation = false;
2067 }
2068
John Spurlockbf370992014-06-17 13:58:31 -04002069 private void generateDarkEvent() {
2070 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002071 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2072 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2073 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04002074 }
2075 mDarkNeedsAnimation = false;
2076 }
2077
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002078 private void generateGoToFullShadeEvent() {
2079 if (mGoToFullShadeNeedsAnimation) {
2080 mAnimationEvents.add(
2081 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2082 }
2083 mGoToFullShadeNeedsAnimation = false;
2084 }
2085
Selim Cinek67b22602014-03-10 15:40:16 +01002086 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002087 if (!isScrollingEnabled()) {
2088 return false;
2089 }
Selim Cinek67b22602014-03-10 15:40:16 +01002090 /*
2091 * This method JUST determines whether we want to intercept the motion.
2092 * If we return true, onMotionEvent will be called and we do the actual
2093 * scrolling there.
2094 */
2095
2096 /*
2097 * Shortcut the most recurring case: the user is in the dragging
2098 * state and he is moving his finger. We want to intercept this
2099 * motion.
2100 */
2101 final int action = ev.getAction();
2102 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2103 return true;
2104 }
2105
Selim Cinek67b22602014-03-10 15:40:16 +01002106 switch (action & MotionEvent.ACTION_MASK) {
2107 case MotionEvent.ACTION_MOVE: {
2108 /*
2109 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
2110 * whether the user has moved far enough from his original down touch.
2111 */
2112
2113 /*
2114 * Locally do absolute value. mLastMotionY is set to the y value
2115 * of the down event.
2116 */
2117 final int activePointerId = mActivePointerId;
2118 if (activePointerId == INVALID_POINTER) {
2119 // If we don't have a valid id, the touch down wasn't on content.
2120 break;
2121 }
2122
2123 final int pointerIndex = ev.findPointerIndex(activePointerId);
2124 if (pointerIndex == -1) {
2125 Log.e(TAG, "Invalid pointerId=" + activePointerId
2126 + " in onInterceptTouchEvent");
2127 break;
2128 }
2129
2130 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002131 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002132 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002133 final int xDiff = Math.abs(x - mDownX);
2134 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002135 setIsBeingDragged(true);
2136 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002137 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002138 initVelocityTrackerIfNotExists();
2139 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002140 }
2141 break;
2142 }
2143
2144 case MotionEvent.ACTION_DOWN: {
2145 final int y = (int) ev.getY();
2146 if (getChildAtPosition(ev.getX(), y) == null) {
2147 setIsBeingDragged(false);
2148 recycleVelocityTracker();
2149 break;
2150 }
2151
2152 /*
2153 * Remember location of down touch.
2154 * ACTION_DOWN always refers to pointer index 0.
2155 */
2156 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002157 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002158 mActivePointerId = ev.getPointerId(0);
Selim Cinek1408eb52014-06-02 14:45:38 +02002159 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002160
2161 initOrResetVelocityTracker();
2162 mVelocityTracker.addMovement(ev);
2163 /*
2164 * If being flinged and user touches the screen, initiate drag;
2165 * otherwise don't. mScroller.isFinished should be false when
2166 * being flinged.
2167 */
2168 boolean isBeingDragged = !mScroller.isFinished();
2169 setIsBeingDragged(isBeingDragged);
2170 break;
2171 }
2172
2173 case MotionEvent.ACTION_CANCEL:
2174 case MotionEvent.ACTION_UP:
2175 /* Release the drag */
2176 setIsBeingDragged(false);
2177 mActivePointerId = INVALID_POINTER;
2178 recycleVelocityTracker();
2179 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2180 postInvalidateOnAnimation();
2181 }
2182 break;
2183 case MotionEvent.ACTION_POINTER_UP:
2184 onSecondaryPointerUp(ev);
2185 break;
2186 }
2187
2188 /*
2189 * The only time we want to intercept motion events is if we are in the
2190 * drag mode.
2191 */
2192 return mIsBeingDragged;
2193 }
2194
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002195 /**
2196 * @return Whether the specified motion event is actually happening over the content.
2197 */
2198 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002199 return isInContentBounds(event.getY());
2200 }
2201
2202 /**
2203 * @return Whether a y coordinate is inside the content.
2204 */
2205 public boolean isInContentBounds(float y) {
2206 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002207 }
2208
Selim Cinek67b22602014-03-10 15:40:16 +01002209 private void setIsBeingDragged(boolean isDragged) {
2210 mIsBeingDragged = isDragged;
2211 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002212 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002213 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002214 }
2215 }
2216
2217 @Override
2218 public void onWindowFocusChanged(boolean hasWindowFocus) {
2219 super.onWindowFocusChanged(hasWindowFocus);
2220 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002221 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002222 }
2223 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002224
Selim Cinek1408eb52014-06-02 14:45:38 +02002225 public void removeLongPressCallback() {
2226 mSwipeHelper.removeLongPressCallback();
2227 }
2228
Selim Cinekfab078b2014-03-27 22:45:58 +01002229 @Override
2230 public boolean isScrolledToTop() {
2231 return mOwnScrollY == 0;
2232 }
2233
2234 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002235 public boolean isScrolledToBottom() {
2236 return mOwnScrollY >= getScrollRange();
2237 }
2238
2239 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002240 public View getHostView() {
2241 return this;
2242 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002243
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002244 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002245 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002246 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002247 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02002248 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002249 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002250 }
2251 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002252 }
2253
Selim Cinek1685e632014-04-08 02:27:49 +02002254 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002255 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002256 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2257 }
2258
2259 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002260 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02002261 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002262 if (!mIsExpanded) {
2263 mOwnScrollY = 0;
Selim Cinekf336f4c2014-11-12 16:58:16 +01002264
2265 // lets make sure nothing is in the overlay anymore
2266 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002267 }
Selim Cinek1685e632014-04-08 02:27:49 +02002268 }
2269
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002270 public void onPanelTrackingStarted() {
2271 mPanelTracking = true;
2272 }
2273 public void onPanelTrackingStopped() {
2274 mPanelTracking = false;
2275 }
2276
Selim Cinekb24e0a92015-06-09 20:17:30 -07002277 public void resetScrollPosition() {
2278 mScroller.abortAnimation();
2279 mOwnScrollY = 0;
2280 }
2281
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002282 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002283 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002284 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002285 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002286 if (changed) {
2287 updateNotificationAnimationStates();
2288 }
Selim Cinek1685e632014-04-08 02:27:49 +02002289 }
2290
Jorim Jaggibe565df2014-04-28 17:51:23 +02002291 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002292 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002293 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002294 updateScrollPositionOnExpandInBottom(view);
2295 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002296 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002297 if (needsAnimation) {
2298 requestAnimationOnViewResize();
2299 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002300 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002301 }
2302
Selim Cineka5e211b2014-08-11 17:35:48 +02002303 @Override
2304 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002305 if (mIsExpanded && mAnimationsEnabled) {
2306 mRequestViewResizeAnimationOnLayout = true;
2307 }
Selim Cinek31094df2014-08-14 19:28:15 +02002308 mStackScrollAlgorithm.onReset(view);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002309 updateAnimationState(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002310 }
2311
Selim Cinekf7a14c02014-07-07 14:01:46 +02002312 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2313 if (view instanceof ExpandableNotificationRow) {
2314 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002315 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02002316 // We are actually expanding this view
2317 float endPosition = row.getTranslationY() + row.getActualHeight();
2318 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
Selim Cinekcb9400a2015-06-03 16:56:13 +02002319 mBottomStackSlowDownHeight + (int) mStackTranslation;
Selim Cinekf7a14c02014-07-07 14:01:46 +02002320 if (endPosition > stackEnd) {
2321 mOwnScrollY += endPosition - stackEnd;
2322 mDisallowScrollingInThisMotion = true;
2323 }
2324 }
2325 }
2326 }
2327
Jorim Jaggibe565df2014-04-28 17:51:23 +02002328 public void setOnHeightChangedListener(
2329 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2330 this.mOnHeightChangedListener = mOnHeightChangedListener;
2331 }
2332
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002333 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2334 mOnEmptySpaceClickListener = listener;
2335 }
2336
Selim Cinek572bbd42014-04-25 16:43:27 +02002337 public void onChildAnimationFinished() {
Selim Cinek319bdc42014-05-01 23:01:58 +02002338 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07002339 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07002340 clearViewOverlays();
2341 }
2342
2343 private void clearViewOverlays() {
2344 for (View view : mClearOverlayViewsWhenFinished) {
2345 getOverlay().remove(view);
2346 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002347 }
2348
2349 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002350 for (Runnable runnable : mAnimationFinishedRunnables) {
2351 runnable.run();
2352 }
2353 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002354 }
2355
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002356 /**
2357 * See {@link AmbientState#setDimmed}.
2358 */
2359 public void setDimmed(boolean dimmed, boolean animate) {
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002360 mStackScrollAlgorithm.setDimmed(dimmed);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002361 mAmbientState.setDimmed(dimmed);
Selim Cineka5eaa602014-05-12 21:27:47 +02002362 updatePadding(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002363 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002364 mDimmedNeedsAnimation = true;
2365 mNeedsAnimation = true;
2366 }
2367 requestChildrenUpdate();
2368 }
2369
Jorim Jaggiae441282014-08-01 02:45:18 +02002370 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2371 if (hideSensitive != mAmbientState.isHideSensitive()) {
2372 int childCount = getChildCount();
2373 for (int i = 0; i < childCount; i++) {
2374 ExpandableView v = (ExpandableView) getChildAt(i);
2375 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2376 }
2377 mAmbientState.setHideSensitive(hideSensitive);
2378 if (animate && mAnimationsEnabled) {
2379 mHideSensitiveNeedsAnimation = true;
2380 mNeedsAnimation = true;
2381 }
2382 requestChildrenUpdate();
2383 }
2384 }
2385
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002386 /**
2387 * See {@link AmbientState#setActivatedChild}.
2388 */
Selim Cineka32ab602014-06-11 15:06:01 +02002389 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002390 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002391 if (mAnimationsEnabled) {
2392 mActivateNeedsAnimation = true;
2393 mNeedsAnimation = true;
2394 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002395 requestChildrenUpdate();
2396 }
2397
Selim Cineka32ab602014-06-11 15:06:01 +02002398 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002399 return mAmbientState.getActivatedChild();
2400 }
2401
Selim Cinek572bbd42014-04-25 16:43:27 +02002402 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002403 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002404 if (mListener != null) {
2405 mListener.onChildLocationsChanged(this);
2406 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002407 runAnimationFinishedRunnables();
Selim Cinek572bbd42014-04-25 16:43:27 +02002408 }
2409
Selim Cinekc27437b2014-05-14 10:23:33 +02002410 public void setSpeedBumpView(SpeedBumpView speedBumpView) {
2411 mSpeedBumpView = speedBumpView;
2412 addView(speedBumpView);
2413 }
2414
2415 private void updateSpeedBump(boolean visible) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002416 boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
2417 if (visible != notGoneBefore) {
2418 int newVisibility = visible ? VISIBLE : GONE;
Selim Cinekc27437b2014-05-14 10:23:33 +02002419 mSpeedBumpView.setVisibility(newVisibility);
2420 if (visible) {
Selim Cinekc27437b2014-05-14 10:23:33 +02002421 // Make invisible to ensure that the appear animation is played.
2422 mSpeedBumpView.setInvisible();
Selim Cinekc27437b2014-05-14 10:23:33 +02002423 } else {
Jorim Jaggi8b730062014-07-31 22:19:52 +02002424 // TODO: This doesn't really work, because the view is already set to GONE above.
Selim Cinekc27437b2014-05-14 10:23:33 +02002425 generateRemoveAnimation(mSpeedBumpView);
2426 }
2427 }
2428 }
2429
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002430 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002431 updateSpeedBump(true /* visibility */);
2432 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002433 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002434 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002435 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002436 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002437 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002438 }
2439
Selim Cinek1408eb52014-06-02 14:45:38 +02002440 public void cancelExpandHelper() {
2441 mExpandHelper.cancel();
2442 }
2443
2444 public void setIntrinsicPadding(int intrinsicPadding) {
2445 mIntrinsicPadding = intrinsicPadding;
2446 }
2447
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002448 public int getIntrinsicPadding() {
2449 return mIntrinsicPadding;
2450 }
2451
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002452 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002453 * @return the y position of the first notification
2454 */
2455 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07002456 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02002457 }
2458
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002459 @Override
2460 public boolean shouldDelayChildPressedState() {
2461 return true;
2462 }
2463
Jorim Jaggi457cc352014-06-02 22:47:42 +02002464 /**
John Spurlockbf370992014-06-17 13:58:31 -04002465 * See {@link AmbientState#setDark}.
2466 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002467 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04002468 mAmbientState.setDark(dark);
2469 if (animate && mAnimationsEnabled) {
2470 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002471 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04002472 mNeedsAnimation = true;
2473 }
2474 requestChildrenUpdate();
2475 }
2476
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002477 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2478 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2479 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2480 }
2481 if (screenLocation.y > getBottomMostNotificationBottom()) {
2482 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2483 }
2484 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2485 if (child != null) {
2486 return getNotGoneIndex(child);
2487 } else {
2488 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2489 }
2490 }
2491
2492 private int getNotGoneIndex(View child) {
2493 int count = getChildCount();
2494 int notGoneIndex = 0;
2495 for (int i = 0; i < count; i++) {
2496 View v = getChildAt(i);
2497 if (child == v) {
2498 return notGoneIndex;
2499 }
2500 if (v.getVisibility() != View.GONE) {
2501 notGoneIndex++;
2502 }
2503 }
2504 return -1;
2505 }
2506
Dan Sandlereceda3d2014-07-21 15:35:01 -04002507 public void setDismissView(DismissView dismissView) {
2508 mDismissView = dismissView;
2509 addView(mDismissView);
2510 }
2511
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002512 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2513 mEmptyShadeView = emptyShadeView;
2514 addView(mEmptyShadeView);
2515 }
2516
2517 public void updateEmptyShadeView(boolean visible) {
2518 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2519 int newVisibility = visible ? VISIBLE : GONE;
2520 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002521 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002522 if (mEmptyShadeView.willBeGone()) {
2523 mEmptyShadeView.cancelAnimation();
2524 } else {
2525 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002526 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002527 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002528 mEmptyShadeView.setWillBeGone(false);
2529 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07002530 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002531 } else {
Selim Cinek20867102014-12-10 17:09:17 +01002532 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002533 @Override
2534 public void run() {
2535 mEmptyShadeView.setVisibility(GONE);
2536 mEmptyShadeView.setWillBeGone(false);
2537 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07002538 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002539 }
Selim Cinek20867102014-12-10 17:09:17 +01002540 };
2541 if (mAnimationsEnabled) {
2542 mEmptyShadeView.setWillBeGone(true);
2543 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
2544 } else {
2545 mEmptyShadeView.setInvisible();
2546 onFinishedRunnable.run();
2547 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002548 }
2549 }
2550 }
2551
Selim Cinek2cd45df2015-06-09 18:00:07 -07002552 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
2553 mOverflowContainer = overFlowContainer;
2554 addView(mOverflowContainer);
2555 }
2556
2557 public void updateOverflowContainerVisibility(boolean visible) {
2558 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
2559 : mOverflowContainer.getVisibility();
2560 final int newVisibility = visible ? VISIBLE : GONE;
2561 if (oldVisibility != newVisibility) {
2562 Runnable onFinishedRunnable = new Runnable() {
2563 @Override
2564 public void run() {
2565 mOverflowContainer.setVisibility(newVisibility);
2566 mOverflowContainer.setWillBeGone(false);
2567 updateContentHeight();
2568 notifyHeightChangeListener(mOverflowContainer);
2569 }
2570 };
2571 if (!mAnimationsEnabled || !mIsExpanded) {
2572 mOverflowContainer.cancelAppearDrawing();
2573 onFinishedRunnable.run();
2574 } else if (newVisibility != GONE) {
2575 mOverflowContainer.performAddAnimation(0,
2576 StackStateAnimator.ANIMATION_DURATION_STANDARD);
2577 mOverflowContainer.setVisibility(newVisibility);
2578 mOverflowContainer.setWillBeGone(false);
2579 updateContentHeight();
2580 notifyHeightChangeListener(mOverflowContainer);
2581 } else {
2582 mOverflowContainer.performRemoveAnimation(
2583 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2584 0.0f,
2585 onFinishedRunnable);
2586 mOverflowContainer.setWillBeGone(true);
2587 }
2588 }
2589 }
2590
Dan Sandlereceda3d2014-07-21 15:35:01 -04002591 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002592 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002593 int newVisibility = visible ? VISIBLE : GONE;
2594 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002595 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002596 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002597 mDismissView.cancelAnimation();
2598 } else {
2599 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002600 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002601 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002602 mDismissView.setWillBeGone(false);
2603 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002604 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002605 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01002606 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002607 @Override
2608 public void run() {
2609 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002610 mDismissView.setWillBeGone(false);
2611 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002612 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002613 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01002614 };
Selim Cinek20867102014-12-10 17:09:17 +01002615 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01002616 mDismissView.setWillBeGone(true);
2617 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
2618 } else {
2619 dimissHideFinishRunnable.run();
2620 mDismissView.showClearButton();
2621 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002622 }
2623 }
2624 }
2625
2626 public void setDismissAllInProgress(boolean dismissAllInProgress) {
2627 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek7d5f3742014-11-07 18:07:49 +01002628 mDismissView.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07002629 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cineka272dfe2015-02-20 18:12:28 +01002630 if (dismissAllInProgress) {
2631 disableClipOptimization();
2632 }
Selim Cinek9c17b772015-07-07 20:37:09 -07002633 handleDismissAllClipping();
2634 }
2635
2636 private void handleDismissAllClipping() {
2637 final int count = getChildCount();
2638 boolean previousChildWillBeDismissed = false;
2639 for (int i = 0; i < count; i++) {
2640 ExpandableView child = (ExpandableView) getChildAt(i);
2641 if (child.getVisibility() == GONE) {
2642 continue;
2643 }
2644 if (mDismissAllInProgress && previousChildWillBeDismissed) {
2645 child.setMinClipTopAmount(child.getClipTopAmount());
2646 } else {
2647 child.setMinClipTopAmount(0);
2648 }
2649 previousChildWillBeDismissed = canChildBeDismissed(child);
2650 }
Selim Cineka272dfe2015-02-20 18:12:28 +01002651 }
2652
2653 private void disableClipOptimization() {
2654 final int count = getChildCount();
2655 for (int i = 0; i < count; i++) {
2656 ExpandableView child = (ExpandableView) getChildAt(i);
2657 if (child.getVisibility() == GONE) {
2658 continue;
2659 }
2660 child.setClipTopOptimization(0);
2661 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002662 }
2663
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002664 public boolean isDismissViewNotGone() {
2665 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2666 }
2667
2668 public boolean isDismissViewVisible() {
2669 return mDismissView.isVisible();
2670 }
2671
2672 public int getDismissViewHeight() {
Jorim Jaggi1d49ec92014-08-25 18:44:01 +02002673 int height = mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2674
2675 // Hack: Accommodate for additional distance when we only have one notification and the
2676 // dismiss all button.
2677 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
2678 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
2679 height += mCollapseSecondCardPadding;
2680 }
2681 return height;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002682 }
2683
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01002684 public int getEmptyShadeViewHeight() {
2685 return mEmptyShadeView.getHeight();
2686 }
2687
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002688 public float getBottomMostNotificationBottom() {
2689 final int count = getChildCount();
2690 float max = 0;
2691 for (int childIdx = 0; childIdx < count; childIdx++) {
2692 ExpandableView child = (ExpandableView) getChildAt(childIdx);
2693 if (child.getVisibility() == GONE) {
2694 continue;
2695 }
2696 float bottom = child.getTranslationY() + child.getActualHeight();
2697 if (bottom > max) {
2698 max = bottom;
2699 }
2700 }
Selim Cinekd2281152015-04-10 14:37:46 -07002701 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002702 }
2703
John Spurlockbf370992014-06-17 13:58:31 -04002704 /**
Selim Cinek3afd00e2014-08-11 22:32:57 +02002705 * @param qsMinHeight The minimum height of the quick settings including padding
2706 * See {@link StackScrollAlgorithm#updateIsSmallScreen}.
2707 */
2708 public void updateIsSmallScreen(int qsMinHeight) {
2709 mStackScrollAlgorithm.updateIsSmallScreen(mMaxLayoutHeight - qsMinHeight);
2710 }
2711
Selim Cinek19c8c702014-08-25 22:09:19 +02002712 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
2713 this.mPhoneStatusBar = phoneStatusBar;
2714 }
2715
Selim Cinekb5605e52015-02-20 18:21:41 +01002716 public void setGroupManager(NotificationGroupManager groupManager) {
2717 this.mGroupManager = groupManager;
2718 }
2719
Selim Cinekd9acca52014-09-01 22:33:25 +02002720 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01002721 requestAnimateEverything();
2722 }
2723
2724 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02002725 if (mIsExpanded && mAnimationsEnabled) {
2726 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01002727 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02002728 requestChildrenUpdate();
2729 }
2730 }
2731
Selim Cinek04fb2582015-06-02 19:58:09 +02002732 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01002733 int childCount = getChildCount();
2734 for (int i = childCount - 1; i >= 0; i--) {
2735 ExpandableView child = (ExpandableView) getChildAt(i);
2736 if (child.getVisibility() != View.GONE) {
2737 float childTop = child.getY();
2738 if (childTop > touchY) {
2739 // we are above a notification entirely let's abort
2740 return false;
2741 }
2742 boolean belowChild = touchY > childTop + child.getActualHeight();
2743 if (child == mDismissView) {
2744 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
2745 touchY - childTop)) {
2746 // We clicked on the dismiss button
2747 return false;
2748 }
2749 } else if (child == mEmptyShadeView) {
2750 // We arrived at the empty shade view, for which we accept all clicks
2751 return true;
2752 } else if (!belowChild){
2753 // We are on a child
2754 return false;
2755 }
2756 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002757 }
Selim Cinek04fb2582015-06-02 19:58:09 +02002758 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002759 }
2760
Selim Cinekb5605e52015-02-20 18:21:41 +01002761 private void updateExpandButtons() {
2762 for (int i = 0; i < getChildCount(); i++) {
2763 View child = getChildAt(i);
2764 if (child instanceof ExpandableNotificationRow) {
2765 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2766 row.updateExpandButton();
2767 }
2768 }
2769 }
2770
2771 @Override
2772 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
2773 boolean animated = mAnimationsEnabled && mIsExpanded;
2774 if (animated) {
2775 mExpandedGroupView = changedRow;
2776 mNeedsAnimation = true;
2777 }
2778 changedRow.setChildrenExpanded(expanded, animated);
2779 onHeightChanged(changedRow, false /* needsAnimation */);
2780 }
2781
2782 @Override
2783 public void onGroupsProhibitedChanged() {
2784 updateExpandButtons();
2785 }
2786
2787 @Override
2788 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
2789 for (NotificationData.Entry entry : group.children) {
2790 ExpandableNotificationRow row = entry.row;
2791 if (indexOfChild(row) != -1) {
2792 removeView(row);
2793 group.summary.row.addChildNotification(row);
2794 }
2795 }
2796 }
2797
2798 public void generateChildOrderChangedEvent() {
2799 if (mIsExpanded && mAnimationsEnabled) {
2800 mGenerateChildOrderChangedEvent = true;
2801 mNeedsAnimation = true;
2802 requestChildrenUpdate();
2803 }
2804 }
2805
Selim Cinek684a4422015-04-15 16:18:39 -07002806 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002807 mAnimationFinishedRunnables.add(runnable);
2808 }
2809
2810 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
2811 mHeadsUpManager = headsUpManager;
2812 mAmbientState.setHeadsUpManager(headsUpManager);
Selim Cinekd2281152015-04-10 14:37:46 -07002813 mStackScrollAlgorithm.setHeadsUpManager(headsUpManager);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002814 }
2815
2816 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
2817 if (mAnimationsEnabled) {
2818 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
2819 mNeedsAnimation = true;
2820 requestChildrenUpdate();
2821 }
2822 }
2823
2824 public void setShadeExpanded(boolean shadeExpanded) {
2825 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07002826 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002827 }
2828
Selim Cineka59ecc32015-04-07 10:51:49 -07002829 /**
2830 * Set the boundary for the bottom heads up position. The heads up will always be above this
2831 * position.
2832 *
2833 * @param height the height of the screen
2834 * @param bottomBarHeight the height of the bar on the bottom
2835 */
2836 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
2837 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
2838 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002839 requestChildrenUpdate();
2840 }
2841
2842 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
2843 mTrackingHeadsUp = trackingHeadsUp;
2844 }
2845
Selim Cinekaac93252015-04-14 20:04:12 -07002846 public void setScrimController(ScrimController scrimController) {
2847 mScrimController = scrimController;
2848 }
2849
Selim Cinekbbc580b2015-06-03 14:11:03 +02002850 public void forceNoOverlappingRendering(boolean force) {
2851 mForceNoOverlappingRendering = force;
2852 }
2853
2854 @Override
2855 public boolean hasOverlappingRendering() {
2856 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
2857 }
2858
Selim Cinek3afd00e2014-08-11 22:32:57 +02002859 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002860 * A listener that is notified when some child locations might have changed.
2861 */
2862 public interface OnChildLocationsChangedListener {
2863 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2864 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002865
Jorim Jaggi290600a2014-05-30 17:02:20 +02002866 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002867 * A listener that is notified when the empty space below the notifications is clicked on
2868 */
2869 public interface OnEmptySpaceClickListener {
2870 public void onEmptySpaceClicked(float x, float y);
2871 }
2872
2873 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02002874 * A listener that gets notified when the overscroll at the top has changed.
2875 */
2876 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002877
2878 /**
2879 * Notifies a listener that the overscroll has changed.
2880 *
2881 * @param amount the amount of overscroll, in pixels
2882 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2883 * unrubberbanded motion to directly expand overscroll view (e.g expand
2884 * QS)
2885 */
2886 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02002887
2888 /**
2889 * Notify a listener that the scroller wants to escape from the scrolling motion and
2890 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2891 *
2892 * @param velocity The velocity that the Scroller had when over flinging
2893 * @param open Should the fling open or close the overscroll view.
2894 */
2895 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002896 }
2897
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002898 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02002899
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002900 static AnimationFilter[] FILTERS = new AnimationFilter[] {
2901
2902 // ANIMATION_TYPE_ADD
2903 new AnimationFilter()
2904 .animateAlpha()
2905 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002906 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002907 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002908 .animateZ()
2909 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002910
2911 // ANIMATION_TYPE_REMOVE
2912 new AnimationFilter()
2913 .animateAlpha()
2914 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002915 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002916 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002917 .animateZ()
2918 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002919
2920 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2921 new AnimationFilter()
2922 .animateAlpha()
2923 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002924 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002925 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002926 .animateZ()
2927 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002928
2929 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2930 new AnimationFilter()
2931 .animateAlpha()
2932 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002933 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002934 .animateY()
2935 .animateDimmed()
2936 .animateScale()
2937 .animateZ(),
2938
2939 // ANIMATION_TYPE_START_DRAG
2940 new AnimationFilter()
2941 .animateAlpha(),
2942
2943 // ANIMATION_TYPE_SNAP_BACK
2944 new AnimationFilter()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002945 .animateAlpha()
2946 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002947
2948 // ANIMATION_TYPE_ACTIVATED_CHILD
2949 new AnimationFilter()
2950 .animateScale()
2951 .animateAlpha(),
2952
2953 // ANIMATION_TYPE_DIMMED
2954 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002955 .animateY()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002956 .animateScale()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002957 .animateDimmed(),
2958
2959 // ANIMATION_TYPE_CHANGE_POSITION
2960 new AnimationFilter()
2961 .animateAlpha()
2962 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002963 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002964 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04002965 .animateZ(),
2966
2967 // ANIMATION_TYPE_DARK
2968 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01002969 .animateDark()
2970 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002971
2972 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2973 new AnimationFilter()
2974 .animateAlpha()
2975 .animateHeight()
2976 .animateTopInset()
2977 .animateY()
2978 .animateDimmed()
2979 .animateScale()
Jorim Jaggiae441282014-08-01 02:45:18 +02002980 .animateZ()
2981 .hasDelays(),
2982
2983 // ANIMATION_TYPE_HIDE_SENSITIVE
2984 new AnimationFilter()
2985 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02002986
2987 // ANIMATION_TYPE_VIEW_RESIZE
2988 new AnimationFilter()
2989 .animateAlpha()
2990 .animateHeight()
2991 .animateTopInset()
2992 .animateY()
2993 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02002994
Selim Cinekb5605e52015-02-20 18:21:41 +01002995 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
2996 new AnimationFilter()
2997 .animateAlpha()
2998 .animateHeight()
2999 .animateTopInset()
3000 .animateY()
3001 .animateZ(),
3002
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003003 // ANIMATION_TYPE_HEADS_UP_APPEAR
3004 new AnimationFilter()
3005 .animateAlpha()
3006 .animateHeight()
3007 .animateTopInset()
3008 .animateY()
3009 .animateZ(),
3010
3011 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3012 new AnimationFilter()
3013 .animateAlpha()
3014 .animateHeight()
3015 .animateTopInset()
3016 .animateY()
3017 .animateZ(),
3018
Selim Cineka59ecc32015-04-07 10:51:49 -07003019 // ANIMATION_TYPE_HEADS_UP_OTHER
3020 new AnimationFilter()
3021 .animateAlpha()
3022 .animateHeight()
3023 .animateTopInset()
3024 .animateY()
3025 .animateZ(),
3026
Selim Cinekd9acca52014-09-01 22:33:25 +02003027 // ANIMATION_TYPE_EVERYTHING
3028 new AnimationFilter()
3029 .animateAlpha()
3030 .animateDark()
3031 .animateScale()
3032 .animateDimmed()
3033 .animateHideSensitive()
3034 .animateHeight()
3035 .animateTopInset()
3036 .animateY()
3037 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003038 };
3039
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003040 static int[] LENGTHS = new int[] {
3041
3042 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003043 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003044
3045 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003046 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003047
3048 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3049 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3050
3051 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3052 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3053
3054 // ANIMATION_TYPE_START_DRAG
3055 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3056
3057 // ANIMATION_TYPE_SNAP_BACK
3058 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3059
3060 // ANIMATION_TYPE_ACTIVATED_CHILD
3061 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3062
3063 // ANIMATION_TYPE_DIMMED
3064 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003065
3066 // ANIMATION_TYPE_CHANGE_POSITION
3067 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04003068
3069 // ANIMATION_TYPE_DARK
3070 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003071
3072 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3073 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02003074
3075 // ANIMATION_TYPE_HIDE_SENSITIVE
3076 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02003077
3078 // ANIMATION_TYPE_VIEW_RESIZE
3079 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02003080
Selim Cinekb5605e52015-02-20 18:21:41 +01003081 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
3082 StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED,
3083
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003084 // ANIMATION_TYPE_HEADS_UP_APPEAR
3085 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
3086
3087 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3088 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3089
Selim Cineka59ecc32015-04-07 10:51:49 -07003090 // ANIMATION_TYPE_HEADS_UP_OTHER
3091 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3092
Selim Cinekd9acca52014-09-01 22:33:25 +02003093 // ANIMATION_TYPE_EVERYTHING
3094 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003095 };
3096
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003097 static final int ANIMATION_TYPE_ADD = 0;
3098 static final int ANIMATION_TYPE_REMOVE = 1;
3099 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
3100 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
3101 static final int ANIMATION_TYPE_START_DRAG = 4;
3102 static final int ANIMATION_TYPE_SNAP_BACK = 5;
3103 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
3104 static final int ANIMATION_TYPE_DIMMED = 7;
3105 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04003106 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003107 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02003108 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02003109 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01003110 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003111 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
3112 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Selim Cineka59ecc32015-04-07 10:51:49 -07003113 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 16;
3114 static final int ANIMATION_TYPE_EVERYTHING = 17;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003115
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003116 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3117 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3118
Selim Cinek572bbd42014-04-25 16:43:27 +02003119 final long eventStartTime;
3120 final View changingView;
3121 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003122 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003123 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003124 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003125 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07003126 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02003127
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003128 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003129 this(view, type, LENGTHS[type]);
3130 }
3131
3132 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003133 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3134 changingView = view;
3135 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003136 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003137 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003138 }
3139
3140 /**
3141 * Combines the length of several animation events into a single value.
3142 *
3143 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003144 * @return The combined length. Depending on the event types, this might be the maximum of
3145 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003146 */
3147 static long combineLength(ArrayList<AnimationEvent> events) {
3148 long length = 0;
3149 int size = events.size();
3150 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003151 AnimationEvent event = events.get(i);
3152 length = Math.max(length, event.length);
3153 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3154 return event.length;
3155 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003156 }
3157 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02003158 }
3159 }
3160
Selim Cinek67b22602014-03-10 15:40:16 +01003161}