blob: fe06c3affa126c3d453b1605a57dcb94e7daf1c9 [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
Selim Cinek614576e2016-01-20 10:54:09 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Mady Mellor4b80b102016-01-22 08:03:58 -080021import android.animation.AnimatorSet;
Selim Cinek614576e2016-01-20 10:54:09 -080022import android.animation.ObjectAnimator;
23import android.animation.PropertyValuesHolder;
Selim Cinekd35c2792016-01-21 13:20:57 -080024import android.animation.TimeAnimator;
25import android.animation.ValueAnimator;
Mady Mellor4b80b102016-01-22 08:03:58 -080026import android.animation.ValueAnimator.AnimatorUpdateListener;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010027import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010028import android.content.Context;
29import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010030import android.graphics.Canvas;
Selim Cinekd35c2792016-01-21 13:20:57 -080031import android.graphics.Color;
Selim Cinek67b22602014-03-10 15:40:16 +010032import android.graphics.Paint;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010033import android.graphics.PointF;
Selim Cinek6811d722016-01-19 17:53:12 -080034import android.graphics.PorterDuff;
35import android.graphics.PorterDuffXfermode;
36import android.graphics.Rect;
Mady Mellor4b80b102016-01-22 08:03:58 -080037import android.os.Handler;
Selim Cinek67b22602014-03-10 15:40:16 +010038import android.util.AttributeSet;
39import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070040import android.util.Pair;
Selim Cinek67b22602014-03-10 15:40:16 +010041import android.view.MotionEvent;
42import android.view.VelocityTracker;
43import android.view.View;
44import android.view.ViewConfiguration;
45import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020046import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020047import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080048import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010049import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020050
Selim Cinek67b22602014-03-10 15:40:16 +010051import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080052import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010053import com.android.systemui.R;
54import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070055import com.android.systemui.classifier.FalsingManager;
Selim Cineka32ab602014-06-11 15:06:01 +020056import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040057import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020058import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010059import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020060import com.android.systemui.statusbar.ExpandableView;
Selim Cinek2cd45df2015-06-09 18:00:07 -070061import com.android.systemui.statusbar.NotificationOverflowContainer;
Mady Mellor4b80b102016-01-22 08:03:58 -080062import com.android.systemui.statusbar.NotificationSettingsIconRow;
63import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010064import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020065import com.android.systemui.statusbar.StatusBarState;
Selim Cinek33223572016-02-19 19:32:22 -080066import com.android.systemui.statusbar.notification.FakeShadowView;
Selim Cinekb5605e52015-02-20 18:21:41 +010067import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020068import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070069import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070070import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010071import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010072
Selim Cinek572bbd42014-04-25 16:43:27 +020073import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -080074import java.util.Collections;
75import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020076import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020077
Selim Cinek67b22602014-03-10 15:40:16 +010078/**
79 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
80 */
81public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020082 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Mady Mellor4b80b102016-01-22 08:03:58 -080083 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
84 SettingsIconRowListener {
Selim Cinek67b22602014-03-10 15:40:16 +010085
Selim Cinekd35c2792016-01-21 13:20:57 -080086 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -080087 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +010088 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020089 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
90 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020091 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010092
93 /**
94 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
95 */
96 private static final int INVALID_POINTER = -1;
97
Selim Cinek1408eb52014-06-02 14:45:38 +020098 private ExpandHelper mExpandHelper;
Selim Cinek33223572016-02-19 19:32:22 -080099 private NotificationSwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +0200100 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +0100101 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800102 private final Paint mBackgroundPaint = new Paint();
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100103
104 /**
105 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
106 * externally from {@link #setStackHeight}
107 */
108 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100109 private int mOwnScrollY;
110 private int mMaxLayoutHeight;
111
112 private VelocityTracker mVelocityTracker;
113 private OverScroller mScroller;
114 private int mTouchSlop;
115 private int mMinimumVelocity;
116 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100117 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200118 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100119 private boolean mIsBeingDragged;
120 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200121 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +0100122 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100123 private boolean mTouchIsClick;
124 private float mInitialTouchX;
125 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100126
Selim Cinek67b22602014-03-10 15:40:16 +0100127 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100128 private int mContentHeight;
129 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200130 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100131 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100132 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800133 private int mIncreasedPaddingBetweenElements;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200134 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200135 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100136
137 /**
138 * The algorithm which calculates the properties for our children
139 */
Selim Cinekaf0dc312015-12-15 17:01:44 -0800140 private final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100141
142 /**
143 * The current State this Layout is in
144 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200145 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200146 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100147 private NotificationGroupManager mGroupManager;
Selim Cinek3776fe02016-02-04 13:32:43 -0800148 private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700149 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
150 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
151 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
152 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
153 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200154 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700155 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
156 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200157 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200158 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200159 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800160 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200161
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200162 /**
163 * The raw amount of the overScroll on the top, which is not rubber-banded.
164 */
165 private float mOverScrolledTopPixels;
166
167 /**
168 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
169 */
170 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200171 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200172 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200173 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100174 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200175 private boolean mNeedsAnimation;
176 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200177 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200178 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400179 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100180 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200181 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200182 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200183 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200184 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200185 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700186 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200187 private boolean mExpandingNotification;
188 private boolean mExpandedInThisMotion;
189 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400190 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200191 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400192 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200193
194 /**
195 * Was the scroller scrolled to the top when the down motion was observed?
196 */
197 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200198 /**
199 * The minimal amount of over scroll which is needed in order to switch to the quick settings
200 * when over scrolling on a expanded card.
201 */
202 private float mMinTopOverScrollToEscape;
203 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700204 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200205 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200206 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200207 private boolean mRequestViewResizeAnimationOnLayout;
208 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100209 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700210 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700211
Selim Cinek1408eb52014-06-02 14:45:38 +0200212 /**
213 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
214 * This is needed to avoid scrolling too far after the notification was collapsed in the same
215 * motion.
216 */
217 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400218 private SwipeHelper.LongPressListener mLongPressListener;
Mady Mellor4b80b102016-01-22 08:03:58 -0800219 private GearDisplayedListener mGearDisplayedListener;
220
221 private NotificationSettingsIconRow mCurrIconRow;
222 private View mTranslatingParentView;
223 private View mGearExposedView;
Selim Cinek1408eb52014-06-02 14:45:38 +0200224
225 /**
226 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
227 * animating.
228 */
229 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200230 private boolean mInterceptDelegateEnabled;
231 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700232 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700233 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200234 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200235 = new ViewTreeObserver.OnPreDrawListener() {
236 @Override
237 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200238 updateChildren();
239 mChildrenUpdateRequested = false;
240 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200241 return true;
242 }
243 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200244 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100245 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100246 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700247 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700248 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700249 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
250 = new HashSet<>();
251 private HeadsUpManager mHeadsUpManager;
252 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700253 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200254 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700255 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700256 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700257 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800258 private boolean mAnimationRunning;
259 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
260 = new ViewTreeObserver.OnPreDrawListener() {
261 @Override
262 public boolean onPreDraw() {
Selim Cinek614576e2016-01-20 10:54:09 -0800263 // if it needs animation
264 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
265 updateBackground();
266 }
Selim Cinek6811d722016-01-19 17:53:12 -0800267 return true;
268 }
269 };
270 private Rect mBackgroundBounds = new Rect();
Selim Cinek614576e2016-01-20 10:54:09 -0800271 private Rect mStartAnimationRect = new Rect();
272 private Rect mEndAnimationRect = new Rect();
Selim Cinekd35c2792016-01-21 13:20:57 -0800273 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
Selim Cinek614576e2016-01-20 10:54:09 -0800274 private boolean mAnimateNextBackgroundBottom;
275 private boolean mAnimateNextBackgroundTop;
276 private ObjectAnimator mBottomAnimator = null;
277 private ObjectAnimator mTopAnimator = null;
278 private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
279 private ActivatableNotificationView mLastVisibleBackgroundChild = null;
Selim Cinekd35c2792016-01-21 13:20:57 -0800280 private int mBgColor;
281 private float mDimAmount;
282 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800283 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Selim Cinekd35c2792016-01-21 13:20:57 -0800284 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
285 @Override
286 public void onAnimationEnd(Animator animation) {
287 mDimAnimator = null;
288 }
289 };
290 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
291 = new ValueAnimator.AnimatorUpdateListener() {
292
293 @Override
294 public void onAnimationUpdate(ValueAnimator animation) {
295 setDimAmount((Float) animation.getAnimatedValue());
296 }
297 };
Jason Monk16ac3772016-02-10 15:39:21 -0500298 private ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800299 private boolean mContinuousShadowUpdate;
300 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
301 = new ViewTreeObserver.OnPreDrawListener() {
302
303 @Override
304 public boolean onPreDraw() {
305 updateViewShadows();
306 return true;
307 }
308 };
309 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
310 @Override
311 public int compare(ExpandableView view, ExpandableView otherView) {
312 float endY = view.getTranslationY() + view.getActualHeight();
313 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
314 if (endY < otherEndY) {
315 return -1;
316 } else if (endY > otherEndY) {
317 return 1;
318 } else {
319 // The two notifications end at the same location
320 return 0;
321 }
322 }
323 };
Selim Cinek67b22602014-03-10 15:40:16 +0100324
325 public NotificationStackScrollLayout(Context context) {
326 this(context, null);
327 }
328
329 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
330 this(context, attrs, 0);
331 }
332
333 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
334 this(context, attrs, defStyleAttr, 0);
335 }
336
337 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
338 int defStyleRes) {
339 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinekd35c2792016-01-21 13:20:57 -0800340 mBgColor = context.getColor(R.color.notification_shade_background_color);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200341 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
342 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
343 mExpandHelper = new ExpandHelper(getContext(), this,
344 minHeight, maxHeight);
345 mExpandHelper.setEventSource(this);
346 mExpandHelper.setScrollAdapter(this);
Mady Mellor4b80b102016-01-22 08:03:58 -0800347 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
Selim Cinek1cf41c12014-08-12 20:06:19 +0200348 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800349 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100350 initView(context);
Selim Cinek6811d722016-01-19 17:53:12 -0800351 setWillNotDraw(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100352 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100353 mDebugPaint = new Paint();
354 mDebugPaint.setColor(0xffff0000);
355 mDebugPaint.setStrokeWidth(2);
356 mDebugPaint.setStyle(Paint.Style.STROKE);
357 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700358 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinekd35c2792016-01-21 13:20:57 -0800359 mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Selim Cinek67b22602014-03-10 15:40:16 +0100360 }
361
362 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800363 public void onGearTouched(ExpandableNotificationRow row) {
364 if (mLongPressListener != null) {
365 mLongPressListener.onLongPress(row, 0, 0);
366 }
367 }
368
369 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100370 protected void onDraw(Canvas canvas) {
Selim Cinekd35c2792016-01-21 13:20:57 -0800371 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100372 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800373 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100374 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200375 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200376 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200377 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
378 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100379 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
380 y = (int) getLayoutHeight();
381 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200382 y = getHeight() - getEmptyBottomMargin();
383 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100384 }
385 }
386
Selim Cinekd35c2792016-01-21 13:20:57 -0800387 private void updateBackgroundDimming() {
388 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
389 // We need to manually blend in the background color
390 int scrimColor = mScrimController.getScrimBehindColor();
391 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
392 float alphaInv = 1 - alpha;
393 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
394 (int) (Color.red(mBgColor) + alphaInv * Color.red(scrimColor)),
395 (int) (Color.green(mBgColor) + alphaInv * Color.green(scrimColor)),
396 (int) (Color.blue(mBgColor) + alphaInv * Color.blue(scrimColor)));
397 mBackgroundPaint.setColor(color);
398 invalidate();
399 }
400
Selim Cinek67b22602014-03-10 15:40:16 +0100401 private void initView(Context context) {
402 mScroller = new OverScroller(getContext());
403 setFocusable(true);
404 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200405 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100406 final ViewConfiguration configuration = ViewConfiguration.get(context);
407 mTouchSlop = configuration.getScaledTouchSlop();
408 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
409 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100410 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100411 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200412 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100413 mBottomStackPeekSize = context.getResources()
414 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800415 mStackScrollAlgorithm.initView(context);
Selim Cinek61633a82016-01-25 15:54:10 -0800416 mPaddingBetweenElements = Math.max(1, context.getResources()
Selim Cinekcacc6042016-01-21 16:16:41 -0800417 .getDimensionPixelSize(R.dimen.notification_divider_height));
Selim Cinek61633a82016-01-25 15:54:10 -0800418 mIncreasedPaddingBetweenElements = context.getResources()
419 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
420
Selim Cinek587cbf32016-01-19 11:36:18 -0800421 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
Selim Cinek1408eb52014-06-02 14:45:38 +0200422 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
423 R.dimen.min_top_overscroll_to_qs);
Selim Cinekd83771e2014-07-04 16:45:31 +0200424 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
425 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200426 }
427
Selim Cinekaef92ef2014-06-06 18:06:04 +0200428 private void notifyHeightChangeListener(ExpandableView view) {
429 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100430 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200431 }
Selim Cinek67b22602014-03-10 15:40:16 +0100432 }
433
434 @Override
435 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
436 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinek924c6122016-01-15 18:24:05 -0800437 measureChildren(widthMeasureSpec, heightMeasureSpec);
Selim Cinek67b22602014-03-10 15:40:16 +0100438 }
439
440 @Override
441 protected void onLayout(boolean changed, int l, int t, int r, int b) {
442
443 // we layout all our children centered on the top
444 float centerX = getWidth() / 2.0f;
445 for (int i = 0; i < getChildCount(); i++) {
446 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100447 if (child.getVisibility() == GONE) {
448 continue;
449 }
Selim Cinek67b22602014-03-10 15:40:16 +0100450 float width = child.getMeasuredWidth();
451 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100452 child.layout((int) (centerX - width / 2.0f),
453 0,
454 (int) (centerX + width / 2.0f),
455 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100456 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200457 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100458 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200459 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100460 if (mRequestViewResizeAnimationOnLayout) {
Selim Cinek5bc852a2015-12-21 12:19:09 -0800461 requestAnimationOnViewResize(null);
Selim Cinekb5605e52015-02-20 18:21:41 +0100462 mRequestViewResizeAnimationOnLayout = false;
463 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200464 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -0800465 updateFirstAndLastBackgroundViews();
Selim Cinek67b22602014-03-10 15:40:16 +0100466 }
467
Selim Cinek5bc852a2015-12-21 12:19:09 -0800468 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
469 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200470 mNeedViewResizeAnimation = true;
471 mNeedsAnimation = true;
472 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200473 }
474
Selim Cinekc27437b2014-05-14 10:23:33 +0200475 public void updateSpeedBumpIndex(int newIndex) {
Selim Cinekc27437b2014-05-14 10:23:33 +0200476 mAmbientState.setSpeedBumpIndex(newIndex);
477 }
478
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200479 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
480 mListener = listener;
481 }
482
483 /**
484 * Returns the location the given child is currently rendered at.
485 *
486 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100487 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200488 */
489 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100490 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200491 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100492 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200493 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100494 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100495 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100496 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200497 return childViewState.location;
498 }
499
Selim Cinek67b22602014-03-10 15:40:16 +0100500 private void setMaxLayoutHeight(int maxLayoutHeight) {
501 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200502 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100503 }
504
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200505 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700506 mAmbientState.setLayoutHeight(getLayoutHeight());
507 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100508 }
509
510 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200511 * @return whether the height of the layout needs to be adapted, in order to ensure that the
512 * last child is not in the bottom stack.
513 */
514 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200515 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200516 }
517
Selim Cinek4a1ac842014-05-01 15:51:58 +0200518 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100519 * Updates the children views according to the stack scroll algorithm. Call this whenever
520 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
521 */
522 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -0800523 updateScrollStateForAddedChildren();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200524 mAmbientState.setScrollY(mOwnScrollY);
525 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200526 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200527 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100528 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200529 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100530 }
531 }
532
Selim Cinek3776fe02016-02-04 13:32:43 -0800533 private void updateScrollStateForAddedChildren() {
534 if (mChildrenToAddAnimated.isEmpty()) {
535 return;
536 }
537 for (int i = 0; i < getChildCount(); i++) {
538 ExpandableView child = (ExpandableView) getChildAt(i);
539 if (mChildrenToAddAnimated.contains(child)) {
540 int startingPosition = getPositionInLinearLayout(child);
541 int padding = child.needsIncreasedPadding()
542 ? mIncreasedPaddingBetweenElements :
543 mPaddingBetweenElements;
544 int childHeight = getIntrinsicHeight(child) + padding;
545 if (startingPosition < mOwnScrollY) {
546 // This child starts off screen, so let's keep it offscreen to keep the others visible
547
548 mOwnScrollY += childHeight;
549 }
550 }
551 }
552 clampScrollPosition();
553 }
554
Selim Cinek319bdc42014-05-01 23:01:58 +0200555 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200556 if (!mChildrenUpdateRequested) {
557 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
558 mChildrenUpdateRequested = true;
559 invalidate();
560 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200561 }
562
Selim Cinek67b22602014-03-10 15:40:16 +0100563 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200564 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100565 }
566
Selim Cinekf7a14c02014-07-07 14:01:46 +0200567 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100568 int scrollRange = getScrollRange();
569 if (scrollRange < mOwnScrollY) {
570 mOwnScrollY = scrollRange;
571 }
572 }
573
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200574 public int getTopPadding() {
575 return mTopPadding;
576 }
577
Selim Cinek1408eb52014-06-02 14:45:38 +0200578 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200579 if (mTopPadding != topPadding) {
580 mTopPadding = topPadding;
581 updateAlgorithmHeightAndPadding();
582 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200583 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200584 mTopPaddingNeedsAnimation = true;
585 mNeedsAnimation = true;
586 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200587 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200588 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200589 }
590 }
591
592 /**
593 * Update the height of the stack to a new height.
594 *
595 * @param height the new height of the stack
596 */
597 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100598 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200599 setIsExpanded(height > 0.0f);
600 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200601 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200602 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700603 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700604 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek31aada42015-12-18 17:51:15 -0800605 int normalUnfoldPositionStart = trackingHeadsUp
606 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700607 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700608 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100609 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700610 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200611 stackHeight = newStackHeight;
612 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800613 int translationY;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700614 if (!trackingHeadsUp) {
Selim Cinek31aada42015-12-18 17:51:15 -0800615 // We did not reach the position yet where we actually start growing,
616 // so we translate the stack upwards.
617 translationY = (newStackHeight - minStackHeight);
618 // A slight parallax effect is introduced in order for the stack to catch up with
619 // the top card.
620 float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
621 / minStackHeight;
622 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700623 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
624 mCollapseSecondCardPadding);
625 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800626 translationY = (int) (height - normalUnfoldPositionStart);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700627 }
628 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200629 stackHeight = (int) (height - (translationY - mTopPadding));
630 }
631 if (stackHeight != mCurrentStackHeight) {
632 mCurrentStackHeight = stackHeight;
633 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200634 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200635 }
Selim Cinekd2281152015-04-10 14:37:46 -0700636 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700637 }
638
Selim Cinekd2281152015-04-10 14:37:46 -0700639 public float getStackTranslation() {
640 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700641 }
642
Selim Cinekd2281152015-04-10 14:37:46 -0700643 private void setStackTranslation(float stackTranslation) {
644 if (stackTranslation != mStackTranslation) {
645 mStackTranslation = stackTranslation;
646 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700647 requestChildrenUpdate();
648 }
Selim Cinek67b22602014-03-10 15:40:16 +0100649 }
650
651 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100652 * 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 +0100653 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
654 *
655 * @return either the layout height or the externally defined height, whichever is smaller
656 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200657 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100658 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
659 }
660
Selim Cinek816c8e42015-11-19 12:00:45 -0800661 public int getFirstItemMinHeight() {
662 final ExpandableView firstChild = getFirstChildNotGone();
663 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100664 }
665
666 public int getBottomStackPeekSize() {
667 return mBottomStackPeekSize;
668 }
669
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200670 public int getCollapseSecondCardPadding() {
671 return mCollapseSecondCardPadding;
672 }
673
Dan Sandler4247a5c2014-07-23 15:58:08 -0400674 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100675 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400676 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100677 }
678
Mady Mellor4b80b102016-01-22 08:03:58 -0800679 public void setGearDisplayedListener(GearDisplayedListener listener) {
680 mGearDisplayedListener = listener;
681 }
682
Jason Monk16ac3772016-02-10 15:39:21 -0500683 public void setQsContainer(ViewGroup qsContainer) {
684 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +0200685 }
686
Selim Cinek67b22602014-03-10 15:40:16 +0100687 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400688 if (mDismissAllInProgress) {
689 return;
690 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100691 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200692 if (mDragAnimPendingChildren.contains(v)) {
693 // We start the swipe and finish it in the same frame, we don't want any animation
694 // for the drag
695 mDragAnimPendingChildren.remove(v);
696 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200697 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200698 mAmbientState.onDragFinished(v);
Selim Cinek33223572016-02-19 19:32:22 -0800699 updateContinuousShadowDrawing();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700700 if (v instanceof ExpandableNotificationRow) {
701 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
702 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700703 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700704 }
705 }
706 final View veto = v.findViewById(R.id.veto);
707 if (veto != null && veto.getVisibility() != View.GONE) {
708 veto.performClick();
709 }
710 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
Blazej Magnowski72323322015-07-24 11:49:40 -0700711
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700712 mFalsingManager.onNotificationDismissed();
713 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700714 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
715 false /* dismissShade */, true /* afterKeyguardGone */);
716 }
Selim Cinekeb973562014-05-02 17:07:49 +0200717 }
718
719 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800720 public void onChildSnappedBack(View animView, float targetLeft) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200721 mAmbientState.onDragFinished(animView);
Selim Cinek33223572016-02-19 19:32:22 -0800722 updateContinuousShadowDrawing();
Selim Cinekeb973562014-05-02 17:07:49 +0200723 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200724 if (mAnimationsEnabled) {
725 mSnappedBackChildren.add(animView);
726 mNeedsAnimation = true;
727 }
Selim Cinekeb973562014-05-02 17:07:49 +0200728 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200729 } else {
730 // We start the swipe and snap back in the same frame, we don't want any animation
731 mDragAnimPendingChildren.remove(animView);
732 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800733
734 if (targetLeft == 0 && mCurrIconRow != null) {
735 mCurrIconRow.resetState();
736 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
737 mGearExposedView = null;
738 }
739 }
Selim Cinek67b22602014-03-10 15:40:16 +0100740 }
741
Adrian Roos5d9cc662014-05-28 17:08:13 +0200742 @Override
743 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700744 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700745 mScrimController.setTopHeadsUpDragAmount(animView,
746 Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
747 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800748 return true; // Don't fade out the notification
Adrian Roos5d9cc662014-05-28 17:08:13 +0200749 }
750
Selim Cinek67b22602014-03-10 15:40:16 +0100751 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700752 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100753 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200754 mAmbientState.onBeginDrag(v);
Selim Cinek33223572016-02-19 19:32:22 -0800755 updateContinuousShadowDrawing();
Selim Cinek131c1e22015-05-11 19:04:49 -0700756 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200757 mDragAnimPendingChildren.add(v);
758 mNeedsAnimation = true;
759 }
Selim Cinekeb973562014-05-02 17:07:49 +0200760 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100761 }
762
Selim Cinek684a4422015-04-15 16:18:39 -0700763 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700764 if (v instanceof ExpandableNotificationRow) {
765 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700766 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700767 }
768 return false;
769 }
770
771 private boolean isHeadsUp(View v) {
772 if (v instanceof ExpandableNotificationRow) {
773 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
774 return row.isHeadsUp();
775 }
776 return false;
777 }
778
Selim Cinek67b22602014-03-10 15:40:16 +0100779 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700780 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100781 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100782 }
783
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700784 @Override
785 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700786 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700787 }
788
Mady Mellor4b80b102016-01-22 08:03:58 -0800789 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100790 public View getChildAtPosition(MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800791 View child = getChildAtPosition(ev.getX(), ev.getY());
792 if (child instanceof ExpandableNotificationRow) {
793 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
794 ExpandableNotificationRow parent = row.getNotificationParent();
795 if (mGearExposedView != null && parent != null
796 && parent.areChildrenExpanded() && mGearExposedView == parent) {
797 // In this case the group is expanded and showing the gear for the
798 // group, further interaction should apply to the group, not any
799 // child notifications so we use the parent of the child.
800 child = row.getNotificationParent();
801 }
802 }
803 return child;
Selim Cinek67b22602014-03-10 15:40:16 +0100804 }
805
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100806 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
807 getLocationOnScreen(mTempInt2);
808 float localTouchY = touchY - mTempInt2[1];
809
810 ExpandableView closestChild = null;
811 float minDist = Float.MAX_VALUE;
812
813 // find the view closest to the location, accounting for GONE views
814 final int count = getChildCount();
815 for (int childIdx = 0; childIdx < count; childIdx++) {
816 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
817 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800818 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100819 continue;
820 }
821 float childTop = slidingChild.getTranslationY();
822 float top = childTop + slidingChild.getClipTopAmount();
823 float bottom = childTop + slidingChild.getActualHeight();
824
825 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
826 if (dist < minDist) {
827 closestChild = slidingChild;
828 minDist = dist;
829 }
830 }
831 return closestChild;
832 }
833
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200834 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100835 getLocationOnScreen(mTempInt2);
836 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100837 }
838
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200839 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100840 // find the view under the pointer, accounting for GONE views
841 final int count = getChildCount();
842 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200843 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100844 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800845 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100846 continue;
847 }
Selim Cinek89faff12014-06-19 16:29:04 -0700848 float childTop = slidingChild.getTranslationY();
849 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200850 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200851
852 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
853 // camera affordance).
854 int left = 0;
855 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100856
857 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100858 if (slidingChild instanceof ExpandableNotificationRow) {
859 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700860 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -0800861 && mHeadsUpManager.getTopEntry().entry.row != row
862 && mGroupManager.getGroupSummary(
863 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
864 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700865 continue;
866 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100867 return row.getViewAtPosition(touchY - childTop);
868 }
Selim Cinek67b22602014-03-10 15:40:16 +0100869 return slidingChild;
870 }
871 }
872 return null;
873 }
874
875 public boolean canChildBeExpanded(View v) {
876 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700877 && ((ExpandableNotificationRow) v).isExpandable()
878 && !((ExpandableNotificationRow) v).isHeadsUp();
Selim Cinek67b22602014-03-10 15:40:16 +0100879 }
880
881 public void setUserExpandedChild(View v, boolean userExpanded) {
882 if (v instanceof ExpandableNotificationRow) {
Selim Cinek388df6d2015-10-22 13:25:11 -0700883 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded,
884 true /* allowChildrenExpansion */);
Selim Cinek67b22602014-03-10 15:40:16 +0100885 }
886 }
887
888 public void setUserLockedChild(View v, boolean userLocked) {
889 if (v instanceof ExpandableNotificationRow) {
890 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
891 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200892 removeLongPressCallback();
893 requestDisallowInterceptTouchEvent(true);
894 }
895
896 @Override
897 public void expansionStateChanged(boolean isExpanding) {
898 mExpandingNotification = isExpanding;
899 if (!mExpandedInThisMotion) {
900 mMaxScrollAfterExpand = mOwnScrollY;
901 mExpandedInThisMotion = true;
902 }
903 }
904
905 public void setScrollingEnabled(boolean enable) {
906 mScrollingEnabled = enable;
907 }
908
909 public void setExpandingEnabled(boolean enable) {
910 mExpandHelper.setEnabled(enable);
911 }
912
913 private boolean isScrollingEnabled() {
914 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100915 }
916
Selim Cinek67b22602014-03-10 15:40:16 +0100917 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -0700918 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +0100919 }
920
Selim Cinek19c8c702014-08-25 22:09:19 +0200921 @Override
922 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200923 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200924 }
925
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100926 private void setSwipingInProgress(boolean isSwiped) {
927 mSwipingInProgress = isSwiped;
928 if(isSwiped) {
929 requestDisallowInterceptTouchEvent(true);
930 }
Selim Cinek67b22602014-03-10 15:40:16 +0100931 }
932
933 @Override
934 protected void onConfigurationChanged(Configuration newConfig) {
935 super.onConfigurationChanged(newConfig);
936 float densityScale = getResources().getDisplayMetrics().density;
937 mSwipeHelper.setDensityScale(densityScale);
938 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
939 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
940 initView(getContext());
941 }
942
Dan Sandlereceda3d2014-07-21 15:35:01 -0400943 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400944 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100945 }
946
947 @Override
948 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200949 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
950 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100951 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200952 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +0200953 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200954 if (isCancelOrUp) {
955 mExpandHelper.onlyObserveMovements(false);
956 }
957 boolean wasExpandingBefore = mExpandingNotification;
958 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200959 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
960 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200961 dispatchDownEventToScroller(ev);
962 }
963 }
Selim Cinek67b22602014-03-10 15:40:16 +0100964 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700965 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
966 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100967 scrollerWantsIt = onScrollTouch(ev);
968 }
969 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200970 if (!mIsBeingDragged
971 && !mExpandingNotification
972 && !mExpandedInThisMotion
973 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100974 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
975 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200976 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
977 }
978
979 private void dispatchDownEventToScroller(MotionEvent ev) {
980 MotionEvent downEvent = MotionEvent.obtain(ev);
981 downEvent.setAction(MotionEvent.ACTION_DOWN);
982 onScrollTouch(downEvent);
983 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100984 }
985
986 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200987 if (!isScrollingEnabled()) {
988 return false;
989 }
Jason Monk16ac3772016-02-10 15:39:21 -0500990 if (ev.getY() < mQsContainer.getBottom()) {
991 return false;
992 }
Selim Cinek67b22602014-03-10 15:40:16 +0100993 initVelocityTrackerIfNotExists();
994 mVelocityTracker.addMovement(ev);
995
996 final int action = ev.getAction();
997
998 switch (action & MotionEvent.ACTION_MASK) {
999 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001000 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +01001001 return false;
1002 }
1003 boolean isBeingDragged = !mScroller.isFinished();
1004 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +01001005
1006 /*
1007 * If being flinged and user touches, stop the fling. isFinished
1008 * will be false if being flinged.
1009 */
1010 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001011 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +01001012 }
1013
1014 // Remember where the motion event started
1015 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001016 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001017 mActivePointerId = ev.getPointerId(0);
1018 break;
1019 }
1020 case MotionEvent.ACTION_MOVE:
1021 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1022 if (activePointerIndex == -1) {
1023 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1024 break;
1025 }
1026
1027 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001028 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001029 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001030 final int xDiff = Math.abs(x - mDownX);
1031 final int yDiff = Math.abs(deltaY);
1032 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001033 setIsBeingDragged(true);
1034 if (deltaY > 0) {
1035 deltaY -= mTouchSlop;
1036 } else {
1037 deltaY += mTouchSlop;
1038 }
1039 }
1040 if (mIsBeingDragged) {
1041 // Scroll to follow the motion event
1042 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001043 int range = getScrollRange();
1044 if (mExpandedInThisMotion) {
1045 range = Math.min(range, mMaxScrollAfterExpand);
1046 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001047
1048 float scrollAmount;
1049 if (deltaY < 0) {
1050 scrollAmount = overScrollDown(deltaY);
1051 } else {
1052 scrollAmount = overScrollUp(deltaY, range);
1053 }
Selim Cinek67b22602014-03-10 15:40:16 +01001054
1055 // Calling overScrollBy will call onOverScrolled, which
1056 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001057 if (scrollAmount != 0.0f) {
1058 // The scrolling motion could not be compensated with the
1059 // existing overScroll, we have to scroll the view
1060 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1061 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +01001062 }
Selim Cinek67b22602014-03-10 15:40:16 +01001063 }
1064 break;
1065 case MotionEvent.ACTION_UP:
1066 if (mIsBeingDragged) {
1067 final VelocityTracker velocityTracker = mVelocityTracker;
1068 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1069 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1070
Selim Cinek1408eb52014-06-02 14:45:38 +02001071 if (shouldOverScrollFling(initialVelocity)) {
1072 onOverScrollFling(true, initialVelocity);
1073 } else {
1074 if (getChildCount() > 0) {
1075 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1076 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1077 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1078 fling(-initialVelocity);
1079 } else {
1080 onOverScrollFling(false, initialVelocity);
1081 }
1082 } else {
1083 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1084 getScrollRange())) {
1085 postInvalidateOnAnimation();
1086 }
Selim Cinek67b22602014-03-10 15:40:16 +01001087 }
1088 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001089 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001090
1091 mActivePointerId = INVALID_POINTER;
1092 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +01001093 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001094
Selim Cinek67b22602014-03-10 15:40:16 +01001095 break;
1096 case MotionEvent.ACTION_CANCEL:
1097 if (mIsBeingDragged && getChildCount() > 0) {
1098 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1099 postInvalidateOnAnimation();
1100 }
1101 mActivePointerId = INVALID_POINTER;
1102 endDrag();
1103 }
1104 break;
1105 case MotionEvent.ACTION_POINTER_DOWN: {
1106 final int index = ev.getActionIndex();
1107 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +02001108 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +01001109 mActivePointerId = ev.getPointerId(index);
1110 break;
1111 }
1112 case MotionEvent.ACTION_POINTER_UP:
1113 onSecondaryPointerUp(ev);
1114 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +02001115 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +01001116 break;
1117 }
1118 return true;
1119 }
1120
Selim Cinek1408eb52014-06-02 14:45:38 +02001121 private void onOverScrollFling(boolean open, int initialVelocity) {
1122 if (mOverscrollTopChangedListener != null) {
1123 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1124 }
1125 mDontReportNextOverScroll = true;
1126 setOverScrollAmount(0.0f, true, false);
1127 }
1128
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001129 /**
1130 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1131 *
1132 * @param deltaY The amount to scroll upwards, has to be positive.
1133 * @return The amount of scrolling to be performed by the scroller,
1134 * not handled by the overScroll amount.
1135 */
1136 private float overScrollUp(int deltaY, int range) {
1137 deltaY = Math.max(deltaY, 0);
1138 float currentTopAmount = getCurrentOverScrollAmount(true);
1139 float newTopAmount = currentTopAmount - deltaY;
1140 if (currentTopAmount > 0) {
1141 setOverScrollAmount(newTopAmount, true /* onTop */,
1142 false /* animate */);
1143 }
1144 // Top overScroll might not grab all scrolling motion,
1145 // we have to scroll as well.
1146 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1147 float newScrollY = mOwnScrollY + scrollAmount;
1148 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001149 if (!mExpandedInThisMotion) {
1150 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1151 // We overScroll on the top
1152 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1153 false /* onTop */,
1154 false /* animate */);
1155 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001156 mOwnScrollY = range;
1157 scrollAmount = 0.0f;
1158 }
1159 return scrollAmount;
1160 }
1161
1162 /**
1163 * Perform a scroll downward and adapt the overscroll amounts accordingly
1164 *
1165 * @param deltaY The amount to scroll downwards, has to be negative.
1166 * @return The amount of scrolling to be performed by the scroller,
1167 * not handled by the overScroll amount.
1168 */
1169 private float overScrollDown(int deltaY) {
1170 deltaY = Math.min(deltaY, 0);
1171 float currentBottomAmount = getCurrentOverScrollAmount(false);
1172 float newBottomAmount = currentBottomAmount + deltaY;
1173 if (currentBottomAmount > 0) {
1174 setOverScrollAmount(newBottomAmount, false /* onTop */,
1175 false /* animate */);
1176 }
1177 // Bottom overScroll might not grab all scrolling motion,
1178 // we have to scroll as well.
1179 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1180 float newScrollY = mOwnScrollY + scrollAmount;
1181 if (newScrollY < 0) {
1182 float currentTopPixels = getCurrentOverScrolledPixels(true);
1183 // We overScroll on the top
1184 setOverScrolledPixels(currentTopPixels - newScrollY,
1185 true /* onTop */,
1186 false /* animate */);
1187 mOwnScrollY = 0;
1188 scrollAmount = 0.0f;
1189 }
1190 return scrollAmount;
1191 }
1192
Selim Cinek67b22602014-03-10 15:40:16 +01001193 private void onSecondaryPointerUp(MotionEvent ev) {
1194 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1195 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1196 final int pointerId = ev.getPointerId(pointerIndex);
1197 if (pointerId == mActivePointerId) {
1198 // This was our active pointer going up. Choose a new
1199 // active pointer and adjust accordingly.
1200 // TODO: Make this decision more intelligent.
1201 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1202 mLastMotionY = (int) ev.getY(newPointerIndex);
1203 mActivePointerId = ev.getPointerId(newPointerIndex);
1204 if (mVelocityTracker != null) {
1205 mVelocityTracker.clear();
1206 }
1207 }
1208 }
1209
1210 private void initVelocityTrackerIfNotExists() {
1211 if (mVelocityTracker == null) {
1212 mVelocityTracker = VelocityTracker.obtain();
1213 }
1214 }
1215
1216 private void recycleVelocityTracker() {
1217 if (mVelocityTracker != null) {
1218 mVelocityTracker.recycle();
1219 mVelocityTracker = null;
1220 }
1221 }
1222
1223 private void initOrResetVelocityTracker() {
1224 if (mVelocityTracker == null) {
1225 mVelocityTracker = VelocityTracker.obtain();
1226 } else {
1227 mVelocityTracker.clear();
1228 }
1229 }
1230
1231 @Override
1232 public void computeScroll() {
1233 if (mScroller.computeScrollOffset()) {
1234 // This is called at drawing time by ViewGroup.
1235 int oldX = mScrollX;
1236 int oldY = mOwnScrollY;
1237 int x = mScroller.getCurrX();
1238 int y = mScroller.getCurrY();
1239
1240 if (oldX != x || oldY != y) {
1241 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001242 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1243 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001244 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001245 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1246 }
1247 }
Selim Cinek67b22602014-03-10 15:40:16 +01001248
1249 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001250 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001251 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001252 }
1253
1254 // Keep on drawing until the animation has finished.
1255 postInvalidateOnAnimation();
1256 }
1257 }
1258
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001259 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001260 protected boolean overScrollBy(int deltaX, int deltaY,
1261 int scrollX, int scrollY,
1262 int scrollRangeX, int scrollRangeY,
1263 int maxOverScrollX, int maxOverScrollY,
1264 boolean isTouchEvent) {
1265
1266 int newScrollY = scrollY + deltaY;
1267
1268 final int top = -maxOverScrollY;
1269 final int bottom = maxOverScrollY + scrollRangeY;
1270
1271 boolean clampedY = false;
1272 if (newScrollY > bottom) {
1273 newScrollY = bottom;
1274 clampedY = true;
1275 } else if (newScrollY < top) {
1276 newScrollY = top;
1277 clampedY = true;
1278 }
1279
1280 onOverScrolled(0, newScrollY, false, clampedY);
1281
1282 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001283 }
1284
1285 /**
1286 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1287 * overscroll effect based on numPixels. By default this will also cancel animations on the
1288 * same overScroll edge.
1289 *
1290 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1291 * the rubber-banding logic.
1292 * @param onTop Should the effect be applied on top of the scroller.
1293 * @param animate Should an animation be performed.
1294 */
1295 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001296 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001297 }
1298
1299 /**
1300 * Set the effective overScroll amount which will be directly reflected in the layout.
1301 * By default this will also cancel animations on the same overScroll edge.
1302 *
1303 * @param amount The amount to overScroll by.
1304 * @param onTop Should the effect be applied on top of the scroller.
1305 * @param animate Should an animation be performed.
1306 */
1307 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1308 setOverScrollAmount(amount, onTop, animate, true);
1309 }
1310
1311 /**
1312 * Set the effective overScroll amount which will be directly reflected in the layout.
1313 *
1314 * @param amount The amount to overScroll by.
1315 * @param onTop Should the effect be applied on top of the scroller.
1316 * @param animate Should an animation be performed.
1317 * @param cancelAnimators Should running animations be cancelled.
1318 */
1319 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1320 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001321 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1322 }
1323
1324 /**
1325 * Set the effective overScroll amount which will be directly reflected in the layout.
1326 *
1327 * @param amount The amount to overScroll by.
1328 * @param onTop Should the effect be applied on top of the scroller.
1329 * @param animate Should an animation be performed.
1330 * @param cancelAnimators Should running animations be cancelled.
1331 * @param isRubberbanded The value which will be passed to
1332 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1333 */
1334 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1335 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001336 if (cancelAnimators) {
1337 mStateAnimator.cancelOverScrollAnimators(onTop);
1338 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001339 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001340 }
1341
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001342 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1343 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001344 amount = Math.max(0, amount);
1345 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001346 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001347 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001348 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001349 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001350 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001351 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001352 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001353 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001354 }
1355 }
1356
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001357 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001358 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1359 if (mDontReportNextOverScroll) {
1360 mDontReportNextOverScroll = false;
1361 return;
1362 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001363 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001364 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001365 }
1366 }
1367
1368 public void setOverscrollTopChangedListener(
1369 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1370 mOverscrollTopChangedListener = overscrollTopChangedListener;
1371 }
1372
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001373 public float getCurrentOverScrollAmount(boolean top) {
1374 return mAmbientState.getOverScrollAmount(top);
1375 }
1376
1377 public float getCurrentOverScrolledPixels(boolean top) {
1378 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1379 }
1380
1381 private void setOverScrolledPixels(float amount, boolean onTop) {
1382 if (onTop) {
1383 mOverScrolledTopPixels = amount;
1384 } else {
1385 mOverScrolledBottomPixels = amount;
1386 }
1387 }
1388
Selim Cinek319bdc42014-05-01 23:01:58 +02001389 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001390 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001391 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001392 }
1393
1394 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001395 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001396 // Treat animating scrolls differently; see #computeScroll() for why.
1397 if (!mScroller.isFinished()) {
1398 final int oldX = mScrollX;
1399 final int oldY = mOwnScrollY;
1400 mScrollX = scrollX;
1401 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001402 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001403 springBack();
1404 } else {
1405 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1406 invalidateParentIfNeeded();
1407 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001408 float overScrollTop = getCurrentOverScrollAmount(true);
1409 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001410 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001411 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001412 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001413 }
Selim Cinek67b22602014-03-10 15:40:16 +01001414 }
Selim Cinek67b22602014-03-10 15:40:16 +01001415 } else {
1416 customScrollTo(scrollY);
1417 scrollTo(scrollX, mScrollY);
1418 }
1419 }
1420
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001421 private void springBack() {
1422 int scrollRange = getScrollRange();
1423 boolean overScrolledTop = mOwnScrollY <= 0;
1424 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1425 if (overScrolledTop || overScrolledBottom) {
1426 boolean onTop;
1427 float newAmount;
1428 if (overScrolledTop) {
1429 onTop = true;
1430 newAmount = -mOwnScrollY;
1431 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001432 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001433 } else {
1434 onTop = false;
1435 newAmount = mOwnScrollY - scrollRange;
1436 mOwnScrollY = scrollRange;
1437 }
1438 setOverScrollAmount(newAmount, onTop, false);
1439 setOverScrollAmount(0.0f, onTop, true);
1440 mScroller.forceFinished(true);
1441 }
1442 }
1443
Selim Cinek67b22602014-03-10 15:40:16 +01001444 private int getScrollRange() {
1445 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001446 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001447 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001448 int contentHeight = getContentHeight();
Selim Cineka5eaa602014-05-12 21:27:47 +02001449 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1450 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001451 if (scrollRange > 0) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001452 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cinek343e6e22014-04-11 21:23:30 +02001453 // We want to at least be able collapse the first item and not ending in a weird
1454 // end state.
Selim Cinek816c8e42015-11-19 12:00:45 -08001455 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
1456 - firstChild.getMinHeight());
Selim Cinek343e6e22014-04-11 21:23:30 +02001457 }
Selim Cinek67b22602014-03-10 15:40:16 +01001458 }
1459 return scrollRange;
1460 }
1461
Selim Cinek343e6e22014-04-11 21:23:30 +02001462 /**
1463 * @return the first child which has visibility unequal to GONE
1464 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001465 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001466 int childCount = getChildCount();
1467 for (int i = 0; i < childCount; i++) {
1468 View child = getChildAt(i);
1469 if (child.getVisibility() != View.GONE) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001470 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001471 }
1472 }
1473 return null;
1474 }
1475
Selim Cinek4a1ac842014-05-01 15:51:58 +02001476 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001477 * @return The first child which has visibility unequal to GONE which is currently below the
1478 * given translationY or equal to it.
1479 */
1480 private View getFirstChildBelowTranlsationY(float translationY) {
1481 int childCount = getChildCount();
1482 for (int i = 0; i < childCount; i++) {
1483 View child = getChildAt(i);
1484 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1485 return child;
1486 }
1487 }
1488 return null;
1489 }
1490
1491 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001492 * @return the last child which has visibility unequal to GONE
1493 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001494 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001495 int childCount = getChildCount();
1496 for (int i = childCount - 1; i >= 0; i--) {
1497 View child = getChildAt(i);
1498 if (child.getVisibility() != View.GONE) {
1499 return child;
1500 }
1501 }
1502 return null;
1503 }
1504
Jorim Jaggi069cd032014-05-15 03:09:01 +02001505 /**
1506 * @return the number of children which have visibility unequal to GONE
1507 */
1508 public int getNotGoneChildCount() {
1509 int childCount = getChildCount();
1510 int count = 0;
1511 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001512 ExpandableView child = (ExpandableView) getChildAt(i);
1513 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001514 count++;
1515 }
1516 }
1517 return count;
1518 }
1519
Selim Cinek343e6e22014-04-11 21:23:30 +02001520 private int getMaxExpandHeight(View view) {
1521 if (view instanceof ExpandableNotificationRow) {
1522 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001523 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001524 }
1525 return view.getHeight();
1526 }
1527
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001528 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001529 return mContentHeight;
1530 }
1531
1532 private void updateContentHeight() {
1533 int height = 0;
Selim Cinek61633a82016-01-25 15:54:10 -08001534 boolean previousNeedsIncreasedPaddings = false;
Selim Cinek67b22602014-03-10 15:40:16 +01001535 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08001536 ExpandableView expandableView = (ExpandableView) getChildAt(i);
1537 if (expandableView.getVisibility() != View.GONE) {
1538 boolean needsIncreasedPaddings = expandableView.needsIncreasedPadding();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001539 if (height != 0) {
Selim Cinek61633a82016-01-25 15:54:10 -08001540 int padding = needsIncreasedPaddings || previousNeedsIncreasedPaddings
1541 ? mIncreasedPaddingBetweenElements
1542 : mPaddingBetweenElements;
1543 height += padding;
Jorim Jaggid4a57442014-04-10 02:45:55 +02001544 }
Selim Cinek61633a82016-01-25 15:54:10 -08001545 previousNeedsIncreasedPaddings = needsIncreasedPaddings;
1546 height += expandableView.getIntrinsicHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001547 }
1548 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001549 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001550 }
1551
Selim Cinek6811d722016-01-19 17:53:12 -08001552 private void updateBackground() {
1553 if (mAmbientState.isDark()) {
1554 return;
1555 }
1556 updateBackgroundBounds();
1557 if (!mCurrentBounds.equals(mBackgroundBounds)) {
Selim Cinek614576e2016-01-20 10:54:09 -08001558 if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
1559 startBackgroundAnimation();
1560 } else {
1561 mCurrentBounds.set(mBackgroundBounds);
1562 applyCurrentBackgroundBounds();
1563 }
1564 } else {
1565 if (mBottomAnimator != null) {
1566 mBottomAnimator.cancel();
1567 }
1568 if (mTopAnimator != null) {
1569 mTopAnimator.cancel();
1570 }
Selim Cinek6811d722016-01-19 17:53:12 -08001571 }
Selim Cinek614576e2016-01-20 10:54:09 -08001572 mAnimateNextBackgroundBottom = false;
1573 mAnimateNextBackgroundTop = false;
1574 }
1575
1576 private boolean areBoundsAnimating() {
1577 return mBottomAnimator != null || mTopAnimator != null;
1578 }
1579
1580 private void startBackgroundAnimation() {
1581 startBottomAnimation();
1582 startTopAnimation();
1583 }
1584
1585 private void startTopAnimation() {
1586 int previousEndValue = mEndAnimationRect.top;
1587 int newEndValue = mBackgroundBounds.top;
1588 ObjectAnimator previousAnimator = mTopAnimator;
1589 if (previousAnimator != null && previousEndValue == newEndValue) {
1590 return;
1591 }
1592 if (!mAnimateNextBackgroundTop) {
1593 // just a local update was performed
1594 if (previousAnimator != null) {
1595 // we need to increase all animation keyframes of the previous animator by the
1596 // relative change to the end value
1597 int previousStartValue = mStartAnimationRect.top;
1598 PropertyValuesHolder[] values = previousAnimator.getValues();
1599 values[0].setIntValues(previousStartValue, newEndValue);
1600 mStartAnimationRect.top = previousStartValue;
1601 mEndAnimationRect.top = newEndValue;
1602 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1603 return;
1604 } else {
1605 // no new animation needed, let's just apply the value
1606 setBackgroundTop(newEndValue);
1607 return;
1608 }
1609 }
1610 if (previousAnimator != null) {
1611 previousAnimator.cancel();
1612 }
1613 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
1614 mCurrentBounds.top, newEndValue);
1615 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1616 animator.setInterpolator(interpolator);
1617 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1618 // remove the tag when the animation is finished
1619 animator.addListener(new AnimatorListenerAdapter() {
1620 @Override
1621 public void onAnimationEnd(Animator animation) {
1622 mStartAnimationRect.top = -1;
1623 mEndAnimationRect.top = -1;
1624 mTopAnimator = null;
1625 }
1626 });
1627 animator.start();
1628 mStartAnimationRect.top = mCurrentBounds.top;
1629 mEndAnimationRect.top = newEndValue;
1630 mTopAnimator = animator;
1631 }
1632
1633 private void startBottomAnimation() {
1634 int previousStartValue = mStartAnimationRect.bottom;
1635 int previousEndValue = mEndAnimationRect.bottom;
1636 int newEndValue = mBackgroundBounds.bottom;
1637 ObjectAnimator previousAnimator = mBottomAnimator;
1638 if (previousAnimator != null && previousEndValue == newEndValue) {
1639 return;
1640 }
1641 if (!mAnimateNextBackgroundBottom) {
1642 // just a local update was performed
1643 if (previousAnimator != null) {
1644 // we need to increase all animation keyframes of the previous animator by the
1645 // relative change to the end value
1646 PropertyValuesHolder[] values = previousAnimator.getValues();
1647 values[0].setIntValues(previousStartValue, newEndValue);
1648 mStartAnimationRect.bottom = previousStartValue;
1649 mEndAnimationRect.bottom = newEndValue;
1650 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1651 return;
1652 } else {
1653 // no new animation needed, let's just apply the value
1654 setBackgroundBottom(newEndValue);
1655 return;
1656 }
1657 }
1658 if (previousAnimator != null) {
1659 previousAnimator.cancel();
1660 }
1661 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
1662 mCurrentBounds.bottom, newEndValue);
1663 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1664 animator.setInterpolator(interpolator);
1665 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1666 // remove the tag when the animation is finished
1667 animator.addListener(new AnimatorListenerAdapter() {
1668 @Override
1669 public void onAnimationEnd(Animator animation) {
1670 mStartAnimationRect.bottom = -1;
1671 mEndAnimationRect.bottom = -1;
1672 mBottomAnimator = null;
1673 }
1674 });
1675 animator.start();
1676 mStartAnimationRect.bottom = mCurrentBounds.bottom;
1677 mEndAnimationRect.bottom = newEndValue;
1678 mBottomAnimator = animator;
1679 }
1680
1681 private void setBackgroundTop(int top) {
1682 mCurrentBounds.top = top;
1683 applyCurrentBackgroundBounds();
1684 }
1685
1686 public void setBackgroundBottom(int bottom) {
1687 mCurrentBounds.bottom = bottom;
1688 applyCurrentBackgroundBounds();
1689 }
1690
1691 private void applyCurrentBackgroundBounds() {
1692 mScrimController.setExcludedBackgroundArea(mCurrentBounds);
Selim Cinek614576e2016-01-20 10:54:09 -08001693 invalidate();
Selim Cinek6811d722016-01-19 17:53:12 -08001694 }
1695
1696 /**
1697 * Update the background bounds to the new desired bounds
1698 */
1699 private void updateBackgroundBounds() {
Selim Cinek614576e2016-01-20 10:54:09 -08001700 mBackgroundBounds.left = (int) getX();
1701 mBackgroundBounds.right = (int) (getX() + getWidth());
1702 if (!mIsExpanded) {
1703 mBackgroundBounds.top = 0;
1704 mBackgroundBounds.bottom = 0;
1705 }
1706 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001707 int top = 0;
1708 if (firstView != null) {
1709 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
Selim Cinek614576e2016-01-20 10:54:09 -08001710 if (mAnimateNextBackgroundTop
1711 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
1712 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
Selim Cinek6811d722016-01-19 17:53:12 -08001713 // we're ending up at the same location as we are now, lets just skip the animation
1714 top = finalTranslationY;
1715 } else {
1716 top = (int) firstView.getTranslationY();
1717 }
1718 }
Selim Cinek614576e2016-01-20 10:54:09 -08001719 ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001720 int bottom = 0;
1721 if (lastView != null) {
1722 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
1723 int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
1724 int finalBottom = finalTranslationY + finalHeight;
1725 finalBottom = Math.min(finalBottom, getHeight());
Selim Cinek614576e2016-01-20 10:54:09 -08001726 if (mAnimateNextBackgroundBottom
1727 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
1728 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
Selim Cinek6811d722016-01-19 17:53:12 -08001729 // we're ending up at the same location as we are now, lets just skip the animation
1730 bottom = finalBottom;
1731 } else {
1732 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
1733 bottom = Math.min(bottom, getHeight());
1734 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001735 } else {
1736 top = (int) (mTopPadding + mStackTranslation);
Selim Cinek7db42982016-02-02 15:21:41 -08001737 bottom = top;
Selim Cinek6811d722016-01-19 17:53:12 -08001738 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001739 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1740 mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top);
1741 } else {
1742 // otherwise the animation from the shade to the keyguard will jump as it's maxed
1743 mBackgroundBounds.top = Math.max(0, top);
1744 }
1745 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
Selim Cinek6811d722016-01-19 17:53:12 -08001746 }
1747
Selim Cinek614576e2016-01-20 10:54:09 -08001748 private ActivatableNotificationView getFirstPinnedHeadsUp() {
1749 int childCount = getChildCount();
1750 for (int i = 0; i < childCount; i++) {
1751 View child = getChildAt(i);
1752 if (child.getVisibility() != View.GONE
1753 && child instanceof ExpandableNotificationRow) {
1754 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1755 if (row.isPinned()) {
1756 return row;
1757 }
1758 }
1759 }
1760 return null;
1761 }
1762
1763 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001764 int childCount = getChildCount();
1765 for (int i = childCount - 1; i >= 0; i--) {
1766 View child = getChildAt(i);
1767 if (child.getVisibility() != View.GONE
1768 && child instanceof ActivatableNotificationView) {
1769 return (ActivatableNotificationView) child;
1770 }
1771 }
1772 return null;
1773 }
1774
Selim Cinek614576e2016-01-20 10:54:09 -08001775 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001776 int childCount = getChildCount();
1777 for (int i = 0; i < childCount; i++) {
1778 View child = getChildAt(i);
1779 if (child.getVisibility() != View.GONE
1780 && child instanceof ActivatableNotificationView) {
1781 return (ActivatableNotificationView) child;
1782 }
1783 }
1784 return null;
1785 }
1786
Selim Cinek67b22602014-03-10 15:40:16 +01001787 /**
1788 * Fling the scroll view
1789 *
1790 * @param velocityY The initial velocity in the Y direction. Positive
1791 * numbers mean that the finger/cursor is moving down the screen,
1792 * which means we want to scroll towards the top.
1793 */
1794 private void fling(int velocityY) {
1795 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001796 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001797
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001798 float topAmount = getCurrentOverScrollAmount(true);
1799 float bottomAmount = getCurrentOverScrollAmount(false);
1800 if (velocityY < 0 && topAmount > 0) {
1801 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001802 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001803 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001804 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001805 * mOverflingDistance + topAmount;
1806 } else if (velocityY > 0 && bottomAmount > 0) {
1807 mOwnScrollY += bottomAmount;
1808 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001809 mMaxOverScroll = Math.abs(velocityY) / 1000f
1810 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1811 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001812 } else {
1813 // it will be set once we reach the boundary
1814 mMaxOverScroll = 0.0f;
1815 }
1816 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek4195dd02014-05-19 18:16:14 +02001817 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001818
1819 postInvalidateOnAnimation();
1820 }
1821 }
1822
Selim Cinek1408eb52014-06-02 14:45:38 +02001823 /**
1824 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1825 * overScroll view (i.e QS).
1826 */
1827 private boolean shouldOverScrollFling(int initialVelocity) {
1828 float topOverScroll = getCurrentOverScrollAmount(true);
1829 return mScrolledToTopOnFirstDown
1830 && !mExpandedInThisMotion
1831 && topOverScroll > mMinTopOverScrollToEscape
1832 && initialVelocity > 0;
1833 }
1834
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001835 /**
1836 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1837 * account.
1838 *
1839 * @param qsHeight the top padding imposed by the quick settings panel
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001840 * @param animate whether to animate the change
1841 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1842 * {@code qsHeight} is the final top padding
1843 */
Jason Monk16ac3772016-02-10 15:39:21 -05001844 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001845 boolean ignoreIntrinsicPadding) {
Jason Monk16ac3772016-02-10 15:39:21 -05001846 float start = qsHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001847 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001848 int minStackHeight = getMinStackHeight();
1849 if (stackHeight <= minStackHeight) {
1850 float overflow = minStackHeight - stackHeight;
1851 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001852 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001853 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001854 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001855 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001856 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001857 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1858 animate);
1859 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001860 }
1861
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001862 public int getMinStackHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001863 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek31aada42015-12-18 17:51:15 -08001864 final int firstChildMinHeight = firstChild != null ? firstChild.getMinHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08001865 : mCollapsedSize;
1866 return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001867 }
1868
1869 public float getTopPaddingOverflow() {
1870 return mTopPaddingOverflow;
1871 }
1872
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001873 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001874 final ExpandableView firstChild = getFirstChildNotGone();
1875 final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
1876 : mCollapsedSize;
1877 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001878 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001879 }
1880
Selim Cinek1408eb52014-06-02 14:45:38 +02001881 private int clampPadding(int desiredPadding) {
1882 return Math.max(desiredPadding, mIntrinsicPadding);
1883 }
1884
Selim Cinekfed1ab62014-06-17 14:10:33 -07001885 private float getRubberBandFactor(boolean onTop) {
1886 if (!onTop) {
1887 return RUBBER_BAND_FACTOR_NORMAL;
1888 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001889 if (mExpandedInThisMotion) {
1890 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001891 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001892 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1893 } else if (mScrolledToTopOnFirstDown) {
1894 return 1.0f;
1895 }
1896 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001897 }
1898
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001899 /**
1900 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1901 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1902 * overscroll view (e.g. expand QS).
1903 */
1904 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001905 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001906 || !mScrolledToTopOnFirstDown;
1907 }
1908
Selim Cinek67b22602014-03-10 15:40:16 +01001909 private void endDrag() {
1910 setIsBeingDragged(false);
1911
1912 recycleVelocityTracker();
1913
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001914 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1915 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1916 }
1917 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1918 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1919 }
Selim Cinek67b22602014-03-10 15:40:16 +01001920 }
1921
Jorim Jaggi56306252014-07-03 00:40:09 +02001922 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1923 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1924 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1925 }
1926
Selim Cinek67b22602014-03-10 15:40:16 +01001927 @Override
1928 public boolean onInterceptTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001929 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001930 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001931 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001932 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001933 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1934 }
Selim Cinek67b22602014-03-10 15:40:16 +01001935 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001936 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001937 scrollWantsIt = onInterceptTouchEventScroll(ev);
1938 }
1939 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001940 if (!mIsBeingDragged
1941 && !mExpandingNotification
1942 && !mExpandedInThisMotion
1943 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001944 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1945 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001946 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1947 }
1948
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001949 private void handleEmptySpaceClick(MotionEvent ev) {
1950 switch (ev.getActionMasked()) {
1951 case MotionEvent.ACTION_MOVE:
1952 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1953 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1954 mTouchIsClick = false;
1955 }
1956 break;
1957 case MotionEvent.ACTION_UP:
1958 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1959 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1960 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1961 }
1962 break;
1963 }
1964 }
1965
Selim Cinek1408eb52014-06-02 14:45:38 +02001966 private void initDownStates(MotionEvent ev) {
1967 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1968 mExpandedInThisMotion = false;
1969 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001970 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001971 mTouchIsClick = true;
1972 mInitialTouchX = ev.getX();
1973 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001974 }
Selim Cinek67b22602014-03-10 15:40:16 +01001975 }
1976
Selim Cinekef5127e2015-12-21 16:55:58 -08001977 public void setChildTransferInProgress(boolean childTransferInProgress) {
1978 mChildTransferInProgress = childTransferInProgress;
1979 }
1980
Christoph Studer068f5922014-04-08 17:43:07 -04001981 @Override
Adam Powell6690d012015-06-17 16:41:56 -07001982 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04001983 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001984 // we only call our internal methods if this is actually a removal and not just a
1985 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08001986 if (!mChildTransferInProgress) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001987 onViewRemovedInternal(child);
1988 }
1989 }
1990
1991 private void onViewRemovedInternal(View child) {
Selim Cinekb55386d2015-12-16 17:26:49 -08001992 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001993 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001994 // This is only a position change, don't do anything special
1995 return;
1996 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001997 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04001998 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek61633a82016-01-25 15:54:10 -08001999 updateScrollStateForRemovedChild((ExpandableView) child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002000 boolean animationGenerated = generateRemoveAnimation(child);
2001 if (animationGenerated && !mSwipedOutViews.contains(child)) {
2002 // Add this view to an overlay in order to ensure that it will still be temporary
2003 // drawn when removed
2004 getOverlay().add(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002005 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002006 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002007
2008 // Make sure the clipRect we might have set is removed
Selim Cineka272dfe2015-02-20 18:12:28 +01002009 ((ExpandableView) child).setClipTopOptimization(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02002010 }
2011
Selim Cinekb5605e52015-02-20 18:21:41 +01002012 private boolean isChildInGroup(View child) {
2013 return child instanceof ExpandableNotificationRow
2014 && mGroupManager.isChildInGroupWithSummary(
2015 ((ExpandableNotificationRow) child).getStatusBarNotification());
2016 }
2017
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002018 /**
2019 * Generate a remove animation for a child view.
2020 *
2021 * @param child The view to generate the remove animation for.
2022 * @return Whether an animation was generated.
2023 */
2024 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002025 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002026 mAddedHeadsUpChildren.remove(child);
2027 return false;
2028 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002029 if (isClickedHeadsUp(child)) {
2030 // An animation is already running, add it to the Overlay
2031 mClearOverlayViewsWhenFinished.add(child);
2032 return true;
2033 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002034 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002035 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002036 // Generate Animations
2037 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002038 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002039 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002040 } else {
2041 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002042 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002043 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002044 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002045 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002046 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002047 }
2048
Selim Cinek0fccc722015-07-29 17:04:36 -07002049 private boolean isClickedHeadsUp(View child) {
2050 return HeadsUpManager.isClickedHeadsUpNotification(child);
2051 }
2052
Selim Cineke0890e52015-06-17 11:17:08 -07002053 /**
2054 * Remove a removed child view from the heads up animations if it was just added there
2055 *
2056 * @return whether any child was removed from the list to animate
2057 */
2058 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2059 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002060 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2061 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002062 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002063 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002064 mTmpList.add(eventPair);
2065 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002066 }
2067 }
Selim Cineke0890e52015-06-17 11:17:08 -07002068 if (hasAddEvent) {
2069 // This child was just added lets remove all events.
2070 mHeadsUpChangeAnimations.removeAll(mTmpList);
2071 }
2072 mTmpList.clear();
2073 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002074 }
2075
Selim Cinek572bbd42014-04-25 16:43:27 +02002076 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002077 * @param child the child to query
2078 * @return whether a view is not a top level child but a child notification and that group is
2079 * not expanded
2080 */
2081 private boolean isChildInInvisibleGroup(View child) {
2082 if (child instanceof ExpandableNotificationRow) {
2083 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2084 ExpandableNotificationRow groupSummary =
2085 mGroupManager.getGroupSummary(row.getStatusBarNotification());
2086 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07002087 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01002088 }
2089 }
2090 return false;
2091 }
2092
2093 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02002094 * Updates the scroll position when a child was removed
2095 *
2096 * @param removedChild the removed child
2097 */
Selim Cinek61633a82016-01-25 15:54:10 -08002098 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002099 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinek61633a82016-01-25 15:54:10 -08002100 int padding = removedChild.needsIncreasedPadding()
2101 ? mIncreasedPaddingBetweenElements :
2102 mPaddingBetweenElements;
2103 int childHeight = getIntrinsicHeight(removedChild) + padding;
Selim Cinek572bbd42014-04-25 16:43:27 +02002104 int endPosition = startingPosition + childHeight;
2105 if (endPosition <= mOwnScrollY) {
2106 // This child is fully scrolled of the top, so we have to deduct its height from the
2107 // scrollPosition
2108 mOwnScrollY -= childHeight;
2109 } else if (startingPosition < mOwnScrollY) {
2110 // This child is currently being scrolled into, set the scroll position to the start of
2111 // this child
2112 mOwnScrollY = startingPosition;
2113 }
2114 }
2115
Selim Cinekd7c4e002014-07-04 18:36:42 +02002116 private int getIntrinsicHeight(View view) {
2117 if (view instanceof ExpandableView) {
2118 ExpandableView expandableView = (ExpandableView) view;
2119 return expandableView.getIntrinsicHeight();
2120 }
2121 return view.getHeight();
2122 }
2123
Selim Cinek572bbd42014-04-25 16:43:27 +02002124 private int getPositionInLinearLayout(View requestedChild) {
2125 int position = 0;
Selim Cinek61633a82016-01-25 15:54:10 -08002126 boolean previousNeedsIncreasedPaddings = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002127 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002128 ExpandableView child = (ExpandableView) getChildAt(i);
2129 boolean notGone = child.getVisibility() != View.GONE;
2130 if (notGone) {
2131 boolean needsIncreasedPaddings = child.needsIncreasedPadding();
2132 if (position != 0) {
2133 int padding = needsIncreasedPaddings || previousNeedsIncreasedPaddings
2134 ? mIncreasedPaddingBetweenElements :
2135 mPaddingBetweenElements;
2136 position += padding;
2137 }
2138 previousNeedsIncreasedPaddings = needsIncreasedPaddings;
2139 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002140 if (child == requestedChild) {
2141 return position;
2142 }
Selim Cinek61633a82016-01-25 15:54:10 -08002143 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02002144 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002145 }
2146 }
2147 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02002148 }
2149
2150 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002151 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02002152 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002153 onViewAddedInternal(child);
2154 }
2155
Selim Cinek614576e2016-01-20 10:54:09 -08002156 private void updateFirstAndLastBackgroundViews() {
2157 ActivatableNotificationView firstChild = getFirstChildWithBackground();
2158 ActivatableNotificationView lastChild = getLastChildWithBackground();
2159 if (mAnimationsEnabled && mIsExpanded) {
2160 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2161 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2162 } else {
2163 mAnimateNextBackgroundTop = false;
2164 mAnimateNextBackgroundBottom = false;
2165 }
2166 mFirstVisibleBackgroundChild = firstChild;
2167 mLastVisibleBackgroundChild = lastChild;
2168 }
2169
Selim Cinekb5605e52015-02-20 18:21:41 +01002170 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002171 updateHideSensitiveForChild(child);
Selim Cinekb55386d2015-12-16 17:26:49 -08002172 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02002173 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00002174 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002175 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02002176 updateChronometerForChild(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002177 }
2178
Selim Cinekd06c41c2015-07-06 14:51:36 -07002179 private void updateHideSensitiveForChild(View child) {
2180 if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) {
2181 ExpandableView expandableView = (ExpandableView) child;
2182 expandableView.setHideSensitiveForIntrinsicHeight(true);
2183 }
2184 }
2185
Selim Cinekb5605e52015-02-20 18:21:41 +01002186 public void notifyGroupChildRemoved(View row) {
2187 onViewRemovedInternal(row);
2188 }
2189
2190 public void notifyGroupChildAdded(View row) {
2191 onViewAddedInternal(row);
2192 }
2193
Jorim Jaggi75c95042014-05-16 19:09:59 +02002194 public void setAnimationsEnabled(boolean animationsEnabled) {
2195 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002196 updateNotificationAnimationStates();
2197 }
2198
2199 private void updateNotificationAnimationStates() {
Selim Cinek8d490d42015-04-10 00:05:50 -07002200 boolean running = mAnimationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002201 int childCount = getChildCount();
2202 for (int i = 0; i < childCount; i++) {
2203 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07002204 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02002205 updateAnimationState(running, child);
2206 }
2207 }
2208
Selim Cinek51ae05d2014-09-09 15:51:38 +02002209 private void updateAnimationState(View child) {
Selim Cinek01af3342016-02-09 19:25:31 -08002210 updateAnimationState(mAnimationsEnabled && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002211 }
2212
2213
Selim Cinekcab4a602014-09-03 14:47:57 +02002214 private void updateAnimationState(boolean running, View child) {
2215 if (child instanceof ExpandableNotificationRow) {
2216 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2217 row.setIconAnimationRunning(running);
2218 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02002219 }
2220
2221 public boolean isAddOrRemoveAnimationPending() {
2222 return mNeedsAnimation
2223 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2224 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002225 /**
2226 * Generate an animation for an added child view.
2227 *
2228 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00002229 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002230 */
Jorim Jaggif6411742014-08-05 17:10:43 +00002231 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002232 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002233 // Generate Animations
2234 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00002235 if (fromMoreCard) {
2236 mFromMoreCardAdditions.add(child);
2237 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002238 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02002239 }
Adrian Roos777ef562015-12-01 17:37:14 -08002240 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002241 mAddedHeadsUpChildren.add(child);
2242 mChildrenToAddAnimated.remove(child);
2243 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002244 }
2245
2246 /**
2247 * Change the position of child to a new location
2248 *
2249 * @param child the view to change the position for
2250 * @param newIndex the new index
2251 */
2252 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002253 int currentIndex = indexOfChild(child);
2254 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002255 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02002256 removeView(child);
2257 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02002258 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04002259 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002260 mChildrenChangingPositions.add(child);
2261 mNeedsAnimation = true;
2262 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002263 }
2264 }
2265
Selim Cinekf4c19962014-05-01 21:55:31 +02002266 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002267 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002268 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002269 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002270 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002271 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002272 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2273 mGoToFullShadeDelay);
Selim Cinek614576e2016-01-20 10:54:09 -08002274 setAnimationRunning(true);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002275 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08002276 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002277 updateViewShadows();
Selim Cinekf4c19962014-05-01 21:55:31 +02002278 } else {
2279 applyCurrentState();
2280 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002281 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02002282 }
2283
2284 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07002285 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02002286 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002287 generateChildAdditionEvents();
2288 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02002289 generateSnapBackEvents();
2290 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002291 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002292 generateActivateEvent();
2293 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02002294 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04002295 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002296 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02002297 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01002298 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02002299 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002300 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002301 }
2302
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002303 private void generateHeadsUpAnimationEvents() {
2304 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002305 ExpandableNotificationRow row = eventPair.first;
2306 boolean isHeadsUp = eventPair.second;
2307 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2308 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07002309 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07002310 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002311 type = row.wasJustClicked()
2312 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2313 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinekeaee9c02015-06-25 11:04:20 -04002314 } else {
2315 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
2316 if (viewState == null) {
2317 // A view state was never generated for this view, so we don't need to animate
2318 // this. This may happen with notification children.
2319 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07002320 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002321 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2322 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2323 // Our custom add animation
2324 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2325 } else {
2326 // Normal add animation
2327 type = AnimationEvent.ANIMATION_TYPE_ADD;
2328 }
2329 onBottom = !pinnedAndClosed;
2330 }
Selim Cineka59ecc32015-04-07 10:51:49 -07002331 }
2332 AnimationEvent event = new AnimationEvent(row, type);
2333 event.headsUpFromBottom = onBottom;
2334 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002335 }
2336 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07002337 mAddedHeadsUpChildren.clear();
2338 }
2339
Selim Cinekeaee9c02015-06-25 11:04:20 -04002340 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002341 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2342 return false;
2343 }
2344 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002345 }
2346
Selim Cinekb5605e52015-02-20 18:21:41 +01002347 private void generateGroupExpansionEvent() {
2348 // Generate a group expansion/collapsing event if there is such a group at all
2349 if (mExpandedGroupView != null) {
2350 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2351 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2352 mExpandedGroupView = null;
2353 }
2354 }
2355
Selim Cineka5e211b2014-08-11 17:35:48 +02002356 private void generateViewResizeEvent() {
2357 if (mNeedViewResizeAnimation) {
2358 mAnimationEvents.add(
2359 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2360 }
2361 mNeedViewResizeAnimation = false;
2362 }
2363
Selim Cinekeb973562014-05-02 17:07:49 +02002364 private void generateSnapBackEvents() {
2365 for (View child : mSnappedBackChildren) {
2366 mAnimationEvents.add(new AnimationEvent(child,
2367 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2368 }
2369 mSnappedBackChildren.clear();
2370 }
2371
2372 private void generateDragEvents() {
2373 for (View child : mDragAnimPendingChildren) {
2374 mAnimationEvents.add(new AnimationEvent(child,
2375 AnimationEvent.ANIMATION_TYPE_START_DRAG));
2376 }
2377 mDragAnimPendingChildren.clear();
2378 }
2379
Selim Cinek572bbd42014-04-25 16:43:27 +02002380 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002381 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002382 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2383 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002384 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2385 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002386 AnimationEvent event = new AnimationEvent(child, animationType);
2387
2388 // we need to know the view after this one
2389 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2390 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02002391 }
2392 mSwipedOutViews.clear();
2393 mChildrenToRemoveAnimated.clear();
2394 }
2395
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002396 private void generatePositionChangeEvents() {
2397 for (View child : mChildrenChangingPositions) {
2398 mAnimationEvents.add(new AnimationEvent(child,
2399 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2400 }
2401 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002402 if (mGenerateChildOrderChangedEvent) {
2403 mAnimationEvents.add(new AnimationEvent(null,
2404 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2405 mGenerateChildOrderChangedEvent = false;
2406 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002407 }
2408
Selim Cinek572bbd42014-04-25 16:43:27 +02002409 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002410 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002411 if (mFromMoreCardAdditions.contains(child)) {
2412 mAnimationEvents.add(new AnimationEvent(child,
2413 AnimationEvent.ANIMATION_TYPE_ADD,
2414 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2415 } else {
2416 mAnimationEvents.add(new AnimationEvent(child,
2417 AnimationEvent.ANIMATION_TYPE_ADD));
2418 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002419 }
2420 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002421 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002422 }
2423
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002424 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002425 if (mTopPaddingNeedsAnimation) {
2426 mAnimationEvents.add(
2427 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2428 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002429 mTopPaddingNeedsAnimation = false;
2430 }
2431
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002432 private void generateActivateEvent() {
2433 if (mActivateNeedsAnimation) {
2434 mAnimationEvents.add(
2435 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2436 }
2437 mActivateNeedsAnimation = false;
2438 }
2439
Selim Cinekd9acca52014-09-01 22:33:25 +02002440 private void generateAnimateEverythingEvent() {
2441 if (mEverythingNeedsAnimation) {
2442 mAnimationEvents.add(
2443 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2444 }
2445 mEverythingNeedsAnimation = false;
2446 }
2447
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002448 private void generateDimmedEvent() {
2449 if (mDimmedNeedsAnimation) {
2450 mAnimationEvents.add(
2451 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2452 }
2453 mDimmedNeedsAnimation = false;
2454 }
2455
Jorim Jaggiae441282014-08-01 02:45:18 +02002456 private void generateHideSensitiveEvent() {
2457 if (mHideSensitiveNeedsAnimation) {
2458 mAnimationEvents.add(
2459 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2460 }
2461 mHideSensitiveNeedsAnimation = false;
2462 }
2463
John Spurlockbf370992014-06-17 13:58:31 -04002464 private void generateDarkEvent() {
2465 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002466 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2467 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2468 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04002469 }
2470 mDarkNeedsAnimation = false;
2471 }
2472
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002473 private void generateGoToFullShadeEvent() {
2474 if (mGoToFullShadeNeedsAnimation) {
2475 mAnimationEvents.add(
2476 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2477 }
2478 mGoToFullShadeNeedsAnimation = false;
2479 }
2480
Selim Cinek67b22602014-03-10 15:40:16 +01002481 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002482 if (!isScrollingEnabled()) {
2483 return false;
2484 }
Selim Cinek67b22602014-03-10 15:40:16 +01002485 /*
2486 * This method JUST determines whether we want to intercept the motion.
2487 * If we return true, onMotionEvent will be called and we do the actual
2488 * scrolling there.
2489 */
2490
2491 /*
2492 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002493 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002494 * motion.
2495 */
2496 final int action = ev.getAction();
2497 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2498 return true;
2499 }
2500
Selim Cinek67b22602014-03-10 15:40:16 +01002501 switch (action & MotionEvent.ACTION_MASK) {
2502 case MotionEvent.ACTION_MOVE: {
2503 /*
2504 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04002505 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01002506 */
2507
2508 /*
2509 * Locally do absolute value. mLastMotionY is set to the y value
2510 * of the down event.
2511 */
2512 final int activePointerId = mActivePointerId;
2513 if (activePointerId == INVALID_POINTER) {
2514 // If we don't have a valid id, the touch down wasn't on content.
2515 break;
2516 }
2517
2518 final int pointerIndex = ev.findPointerIndex(activePointerId);
2519 if (pointerIndex == -1) {
2520 Log.e(TAG, "Invalid pointerId=" + activePointerId
2521 + " in onInterceptTouchEvent");
2522 break;
2523 }
2524
2525 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002526 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002527 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002528 final int xDiff = Math.abs(x - mDownX);
2529 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002530 setIsBeingDragged(true);
2531 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002532 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002533 initVelocityTrackerIfNotExists();
2534 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002535 }
2536 break;
2537 }
2538
2539 case MotionEvent.ACTION_DOWN: {
2540 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05302541 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002542 if (getChildAtPosition(ev.getX(), y) == null) {
2543 setIsBeingDragged(false);
2544 recycleVelocityTracker();
2545 break;
2546 }
2547
2548 /*
2549 * Remember location of down touch.
2550 * ACTION_DOWN always refers to pointer index 0.
2551 */
2552 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002553 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002554 mActivePointerId = ev.getPointerId(0);
2555
2556 initOrResetVelocityTracker();
2557 mVelocityTracker.addMovement(ev);
2558 /*
2559 * If being flinged and user touches the screen, initiate drag;
2560 * otherwise don't. mScroller.isFinished should be false when
2561 * being flinged.
2562 */
2563 boolean isBeingDragged = !mScroller.isFinished();
2564 setIsBeingDragged(isBeingDragged);
2565 break;
2566 }
2567
2568 case MotionEvent.ACTION_CANCEL:
2569 case MotionEvent.ACTION_UP:
2570 /* Release the drag */
2571 setIsBeingDragged(false);
2572 mActivePointerId = INVALID_POINTER;
2573 recycleVelocityTracker();
2574 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2575 postInvalidateOnAnimation();
2576 }
2577 break;
2578 case MotionEvent.ACTION_POINTER_UP:
2579 onSecondaryPointerUp(ev);
2580 break;
2581 }
2582
2583 /*
2584 * The only time we want to intercept motion events is if we are in the
2585 * drag mode.
2586 */
2587 return mIsBeingDragged;
2588 }
2589
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002590 /**
2591 * @return Whether the specified motion event is actually happening over the content.
2592 */
2593 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002594 return isInContentBounds(event.getY());
2595 }
2596
2597 /**
2598 * @return Whether a y coordinate is inside the content.
2599 */
2600 public boolean isInContentBounds(float y) {
2601 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002602 }
2603
Selim Cinek67b22602014-03-10 15:40:16 +01002604 private void setIsBeingDragged(boolean isDragged) {
2605 mIsBeingDragged = isDragged;
2606 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002607 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002608 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002609 }
2610 }
2611
2612 @Override
2613 public void onWindowFocusChanged(boolean hasWindowFocus) {
2614 super.onWindowFocusChanged(hasWindowFocus);
2615 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002616 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002617 }
2618 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002619
Selim Cinek1408eb52014-06-02 14:45:38 +02002620 public void removeLongPressCallback() {
2621 mSwipeHelper.removeLongPressCallback();
2622 }
2623
Selim Cinekfab078b2014-03-27 22:45:58 +01002624 @Override
2625 public boolean isScrolledToTop() {
2626 return mOwnScrollY == 0;
2627 }
2628
2629 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002630 public boolean isScrolledToBottom() {
2631 return mOwnScrollY >= getScrollRange();
2632 }
2633
2634 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002635 public View getHostView() {
2636 return this;
2637 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002638
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002639 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002640 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002641 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002642 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02002643 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002644 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002645 }
2646 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002647 }
2648
Selim Cinek5f71bee2015-11-18 10:25:23 -08002649 public float getKeyguardBottomStackSize() {
2650 return mBottomStackPeekSize + getResources().getDimensionPixelSize(
2651 R.dimen.bottom_stack_slow_down_length);
2652 }
2653
Selim Cinek1685e632014-04-08 02:27:49 +02002654 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002655 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002656 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2657 }
2658
2659 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002660 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02002661 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002662 if (!mIsExpanded) {
2663 mOwnScrollY = 0;
Selim Cinekf336f4c2014-11-12 16:58:16 +01002664
2665 // lets make sure nothing is in the overlay anymore
2666 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002667 }
Selim Cinek1685e632014-04-08 02:27:49 +02002668 }
2669
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002670 public void onPanelTrackingStarted() {
2671 mPanelTracking = true;
2672 }
2673 public void onPanelTrackingStopped() {
2674 mPanelTracking = false;
2675 }
2676
Selim Cinekb24e0a92015-06-09 20:17:30 -07002677 public void resetScrollPosition() {
2678 mScroller.abortAnimation();
2679 mOwnScrollY = 0;
2680 }
2681
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002682 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002683 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002684 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002685 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002686 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08002687 if (!mIsExpanded) {
2688 mGroupManager.collapseAllGroups();
2689 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002690 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02002691 updateChronometers();
2692 }
2693 }
2694
2695 private void updateChronometers() {
2696 int childCount = getChildCount();
2697 for (int i = 0; i < childCount; i++) {
2698 updateChronometerForChild(getChildAt(i));
2699 }
2700 }
2701
2702 private void updateChronometerForChild(View child) {
2703 if (child instanceof ExpandableNotificationRow) {
2704 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2705 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002706 }
Selim Cinek1685e632014-04-08 02:27:49 +02002707 }
2708
Jorim Jaggibe565df2014-04-28 17:51:23 +02002709 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002710 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002711 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002712 updateScrollPositionOnExpandInBottom(view);
2713 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002714 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002715 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08002716 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
2717 ? (ExpandableNotificationRow) view
2718 : null;
2719 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01002720 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002721 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002722 }
2723
Selim Cineka5e211b2014-08-11 17:35:48 +02002724 @Override
2725 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002726 if (mIsExpanded && mAnimationsEnabled) {
2727 mRequestViewResizeAnimationOnLayout = true;
2728 }
Selim Cinek31094df2014-08-14 19:28:15 +02002729 mStackScrollAlgorithm.onReset(view);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002730 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02002731 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002732 }
2733
Selim Cinekf7a14c02014-07-07 14:01:46 +02002734 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2735 if (view instanceof ExpandableNotificationRow) {
2736 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002737 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02002738 // We are actually expanding this view
Selim Cinek388df6d2015-10-22 13:25:11 -07002739 float endPosition;
2740 if (row.isChildInGroup()) {
2741 ExpandableNotificationRow parent = row.getNotificationParent();
2742 endPosition = parent.getTranslationY() + parent.getActualHeight();
2743 } else {
2744 endPosition = row.getTranslationY() + row.getActualHeight();
2745 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02002746 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
Selim Cinekcb9400a2015-06-03 16:56:13 +02002747 mBottomStackSlowDownHeight + (int) mStackTranslation;
Selim Cinekf7a14c02014-07-07 14:01:46 +02002748 if (endPosition > stackEnd) {
2749 mOwnScrollY += endPosition - stackEnd;
2750 mDisallowScrollingInThisMotion = true;
2751 }
2752 }
2753 }
2754 }
2755
Jorim Jaggibe565df2014-04-28 17:51:23 +02002756 public void setOnHeightChangedListener(
2757 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2758 this.mOnHeightChangedListener = mOnHeightChangedListener;
2759 }
2760
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002761 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2762 mOnEmptySpaceClickListener = listener;
2763 }
2764
Selim Cinek572bbd42014-04-25 16:43:27 +02002765 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08002766 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02002767 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07002768 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07002769 clearViewOverlays();
2770 }
2771
2772 private void clearViewOverlays() {
2773 for (View view : mClearOverlayViewsWhenFinished) {
2774 getOverlay().remove(view);
2775 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002776 }
2777
2778 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002779 for (Runnable runnable : mAnimationFinishedRunnables) {
2780 runnable.run();
2781 }
2782 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002783 }
2784
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002785 /**
2786 * See {@link AmbientState#setDimmed}.
2787 */
2788 public void setDimmed(boolean dimmed, boolean animate) {
2789 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002790 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002791 mDimmedNeedsAnimation = true;
2792 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08002793 animateDimmed(dimmed);
2794 } else {
2795 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002796 }
2797 requestChildrenUpdate();
2798 }
2799
Selim Cinekd35c2792016-01-21 13:20:57 -08002800 private void setDimAmount(float dimAmount) {
2801 mDimAmount = dimAmount;
2802 updateBackgroundDimming();
2803 }
2804
2805 private void animateDimmed(boolean dimmed) {
2806 if (mDimAnimator != null) {
2807 mDimAnimator.cancel();
2808 }
2809 float target = dimmed ? 1.0f : 0.0f;
2810 if (target == mDimAmount) {
2811 return;
2812 }
2813 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
2814 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
2815 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
2816 mDimAnimator.addListener(mDimEndListener);
2817 mDimAnimator.addUpdateListener(mDimUpdateListener);
2818 mDimAnimator.start();
2819 }
2820
Jorim Jaggiae441282014-08-01 02:45:18 +02002821 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2822 if (hideSensitive != mAmbientState.isHideSensitive()) {
2823 int childCount = getChildCount();
2824 for (int i = 0; i < childCount; i++) {
2825 ExpandableView v = (ExpandableView) getChildAt(i);
2826 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2827 }
2828 mAmbientState.setHideSensitive(hideSensitive);
2829 if (animate && mAnimationsEnabled) {
2830 mHideSensitiveNeedsAnimation = true;
2831 mNeedsAnimation = true;
2832 }
2833 requestChildrenUpdate();
2834 }
2835 }
2836
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002837 /**
2838 * See {@link AmbientState#setActivatedChild}.
2839 */
Selim Cineka32ab602014-06-11 15:06:01 +02002840 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002841 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002842 if (mAnimationsEnabled) {
2843 mActivateNeedsAnimation = true;
2844 mNeedsAnimation = true;
2845 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002846 requestChildrenUpdate();
2847 }
2848
Selim Cineka32ab602014-06-11 15:06:01 +02002849 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002850 return mAmbientState.getActivatedChild();
2851 }
2852
Selim Cinek572bbd42014-04-25 16:43:27 +02002853 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002854 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002855 if (mListener != null) {
2856 mListener.onChildLocationsChanged(this);
2857 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002858 runAnimationFinishedRunnables();
Selim Cinek6811d722016-01-19 17:53:12 -08002859 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002860 updateViewShadows();
2861 }
2862
2863 private void updateViewShadows() {
2864 // we need to work around an issue where the shadow would not cast between siblings when
2865 // their z difference is between 0 and 0.1
2866
2867 // Lefts first sort by Z difference
2868 for (int i = 0; i < getChildCount(); i++) {
2869 ExpandableView child = (ExpandableView) getChildAt(i);
2870 if (child.getVisibility() != GONE) {
2871 mTmpSortedChildren.add(child);
2872 }
2873 }
2874 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
2875
2876 // Now lets update the shadow for the views
2877 ExpandableView previous = null;
2878 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
2879 ExpandableView expandableView = mTmpSortedChildren.get(i);
2880 float translationZ = expandableView.getTranslationZ();
2881 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
2882 float diff = otherZ - translationZ;
2883 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
2884 // There is no fake shadow to be drawn
2885 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
2886 } else {
2887 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
2888 expandableView.getTranslationY();
2889 expandableView.setFakeShadowIntensity(diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
2890 previous.getOutlineAlpha(), (int) yLocation,
2891 previous.getOutlineTranslation());
2892 }
2893 previous = expandableView;
2894 }
2895
2896 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002897 }
2898
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002899 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002900 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002901 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002902 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002903 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002904 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002905 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002906 }
2907
Selim Cinek1408eb52014-06-02 14:45:38 +02002908 public void cancelExpandHelper() {
2909 mExpandHelper.cancel();
2910 }
2911
2912 public void setIntrinsicPadding(int intrinsicPadding) {
2913 mIntrinsicPadding = intrinsicPadding;
2914 }
2915
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002916 public int getIntrinsicPadding() {
2917 return mIntrinsicPadding;
2918 }
2919
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002920 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002921 * @return the y position of the first notification
2922 */
2923 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07002924 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02002925 }
2926
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002927 @Override
2928 public boolean shouldDelayChildPressedState() {
2929 return true;
2930 }
2931
Jorim Jaggi457cc352014-06-02 22:47:42 +02002932 /**
John Spurlockbf370992014-06-17 13:58:31 -04002933 * See {@link AmbientState#setDark}.
2934 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002935 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04002936 mAmbientState.setDark(dark);
2937 if (animate && mAnimationsEnabled) {
2938 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002939 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04002940 mNeedsAnimation = true;
2941 }
2942 requestChildrenUpdate();
Selim Cinek6811d722016-01-19 17:53:12 -08002943 if (dark) {
2944 setWillNotDraw(!DEBUG);
2945 mScrimController.setExcludedBackgroundArea(null);
2946 } else {
2947 updateBackground();
2948 setWillNotDraw(false);
2949 // TODO: fade in background
2950 }
John Spurlockbf370992014-06-17 13:58:31 -04002951 }
2952
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002953 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2954 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2955 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2956 }
2957 if (screenLocation.y > getBottomMostNotificationBottom()) {
2958 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2959 }
2960 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2961 if (child != null) {
2962 return getNotGoneIndex(child);
2963 } else {
2964 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2965 }
2966 }
2967
2968 private int getNotGoneIndex(View child) {
2969 int count = getChildCount();
2970 int notGoneIndex = 0;
2971 for (int i = 0; i < count; i++) {
2972 View v = getChildAt(i);
2973 if (child == v) {
2974 return notGoneIndex;
2975 }
2976 if (v.getVisibility() != View.GONE) {
2977 notGoneIndex++;
2978 }
2979 }
2980 return -1;
2981 }
2982
Dan Sandlereceda3d2014-07-21 15:35:01 -04002983 public void setDismissView(DismissView dismissView) {
Selim Cinek01af3342016-02-09 19:25:31 -08002984 int index = -1;
2985 if (mDismissView != null) {
2986 index = indexOfChild(mDismissView);
2987 removeView(mDismissView);
2988 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002989 mDismissView = dismissView;
Selim Cinek01af3342016-02-09 19:25:31 -08002990 addView(mDismissView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002991 }
2992
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002993 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08002994 int index = -1;
2995 if (mEmptyShadeView != null) {
2996 index = indexOfChild(mEmptyShadeView);
2997 removeView(mEmptyShadeView);
2998 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002999 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08003000 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003001 }
3002
3003 public void updateEmptyShadeView(boolean visible) {
3004 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
3005 int newVisibility = visible ? VISIBLE : GONE;
3006 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003007 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003008 if (mEmptyShadeView.willBeGone()) {
3009 mEmptyShadeView.cancelAnimation();
3010 } else {
3011 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003012 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003013 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003014 mEmptyShadeView.setWillBeGone(false);
3015 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003016 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003017 } else {
Selim Cinek20867102014-12-10 17:09:17 +01003018 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003019 @Override
3020 public void run() {
3021 mEmptyShadeView.setVisibility(GONE);
3022 mEmptyShadeView.setWillBeGone(false);
3023 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003024 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003025 }
Selim Cinek20867102014-12-10 17:09:17 +01003026 };
3027 if (mAnimationsEnabled) {
3028 mEmptyShadeView.setWillBeGone(true);
3029 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
3030 } else {
3031 mEmptyShadeView.setInvisible();
3032 onFinishedRunnable.run();
3033 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003034 }
3035 }
3036 }
3037
Selim Cinek2cd45df2015-06-09 18:00:07 -07003038 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
3039 mOverflowContainer = overFlowContainer;
3040 addView(mOverflowContainer);
3041 }
3042
3043 public void updateOverflowContainerVisibility(boolean visible) {
3044 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
3045 : mOverflowContainer.getVisibility();
3046 final int newVisibility = visible ? VISIBLE : GONE;
3047 if (oldVisibility != newVisibility) {
3048 Runnable onFinishedRunnable = new Runnable() {
3049 @Override
3050 public void run() {
3051 mOverflowContainer.setVisibility(newVisibility);
3052 mOverflowContainer.setWillBeGone(false);
3053 updateContentHeight();
3054 notifyHeightChangeListener(mOverflowContainer);
3055 }
3056 };
3057 if (!mAnimationsEnabled || !mIsExpanded) {
3058 mOverflowContainer.cancelAppearDrawing();
3059 onFinishedRunnable.run();
3060 } else if (newVisibility != GONE) {
3061 mOverflowContainer.performAddAnimation(0,
3062 StackStateAnimator.ANIMATION_DURATION_STANDARD);
3063 mOverflowContainer.setVisibility(newVisibility);
3064 mOverflowContainer.setWillBeGone(false);
3065 updateContentHeight();
3066 notifyHeightChangeListener(mOverflowContainer);
3067 } else {
3068 mOverflowContainer.performRemoveAnimation(
3069 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3070 0.0f,
3071 onFinishedRunnable);
3072 mOverflowContainer.setWillBeGone(true);
3073 }
3074 }
3075 }
3076
Dan Sandlereceda3d2014-07-21 15:35:01 -04003077 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003078 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003079 int newVisibility = visible ? VISIBLE : GONE;
3080 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003081 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003082 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003083 mDismissView.cancelAnimation();
3084 } else {
3085 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003086 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003087 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003088 mDismissView.setWillBeGone(false);
3089 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003090 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003091 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003092 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003093 @Override
3094 public void run() {
3095 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003096 mDismissView.setWillBeGone(false);
3097 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003098 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003099 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01003100 };
Selim Cinek20867102014-12-10 17:09:17 +01003101 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003102 mDismissView.setWillBeGone(true);
3103 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3104 } else {
3105 dimissHideFinishRunnable.run();
Selim Cinek7d5f3742014-11-07 18:07:49 +01003106 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003107 }
3108 }
3109 }
3110
3111 public void setDismissAllInProgress(boolean dismissAllInProgress) {
3112 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07003113 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cineka272dfe2015-02-20 18:12:28 +01003114 if (dismissAllInProgress) {
3115 disableClipOptimization();
3116 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003117 handleDismissAllClipping();
Mady Mellor3cb40242016-02-22 10:43:35 -08003118 if (mCurrIconRow != null && mCurrIconRow.isVisible()) {
Mady Mellor4b80b102016-01-22 08:03:58 -08003119 mCurrIconRow.getNotificationParent().animateTranslateNotification(0 /* left target */);
3120 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003121 }
3122
3123 private void handleDismissAllClipping() {
3124 final int count = getChildCount();
3125 boolean previousChildWillBeDismissed = false;
3126 for (int i = 0; i < count; i++) {
3127 ExpandableView child = (ExpandableView) getChildAt(i);
3128 if (child.getVisibility() == GONE) {
3129 continue;
3130 }
3131 if (mDismissAllInProgress && previousChildWillBeDismissed) {
3132 child.setMinClipTopAmount(child.getClipTopAmount());
3133 } else {
3134 child.setMinClipTopAmount(0);
3135 }
3136 previousChildWillBeDismissed = canChildBeDismissed(child);
3137 }
Selim Cineka272dfe2015-02-20 18:12:28 +01003138 }
3139
3140 private void disableClipOptimization() {
3141 final int count = getChildCount();
3142 for (int i = 0; i < count; i++) {
3143 ExpandableView child = (ExpandableView) getChildAt(i);
3144 if (child.getVisibility() == GONE) {
3145 continue;
3146 }
3147 child.setClipTopOptimization(0);
3148 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003149 }
3150
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003151 public boolean isDismissViewNotGone() {
3152 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3153 }
3154
3155 public boolean isDismissViewVisible() {
3156 return mDismissView.isVisible();
3157 }
3158
3159 public int getDismissViewHeight() {
Selim Cinek61633a82016-01-25 15:54:10 -08003160 int height = mDismissView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi1d49ec92014-08-25 18:44:01 +02003161
3162 // Hack: Accommodate for additional distance when we only have one notification and the
3163 // dismiss all button.
3164 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
3165 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
3166 height += mCollapseSecondCardPadding;
3167 }
3168 return height;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003169 }
3170
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01003171 public int getEmptyShadeViewHeight() {
3172 return mEmptyShadeView.getHeight();
3173 }
3174
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003175 public float getBottomMostNotificationBottom() {
3176 final int count = getChildCount();
3177 float max = 0;
3178 for (int childIdx = 0; childIdx < count; childIdx++) {
3179 ExpandableView child = (ExpandableView) getChildAt(childIdx);
3180 if (child.getVisibility() == GONE) {
3181 continue;
3182 }
3183 float bottom = child.getTranslationY() + child.getActualHeight();
3184 if (bottom > max) {
3185 max = bottom;
3186 }
3187 }
Selim Cinekd2281152015-04-10 14:37:46 -07003188 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003189 }
3190
Selim Cinek19c8c702014-08-25 22:09:19 +02003191 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3192 this.mPhoneStatusBar = phoneStatusBar;
3193 }
3194
Selim Cinekb5605e52015-02-20 18:21:41 +01003195 public void setGroupManager(NotificationGroupManager groupManager) {
3196 this.mGroupManager = groupManager;
3197 }
3198
Selim Cinekd9acca52014-09-01 22:33:25 +02003199 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01003200 requestAnimateEverything();
3201 }
3202
3203 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02003204 if (mIsExpanded && mAnimationsEnabled) {
3205 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01003206 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02003207 requestChildrenUpdate();
3208 }
3209 }
3210
Selim Cinek04fb2582015-06-02 19:58:09 +02003211 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01003212 int childCount = getChildCount();
3213 for (int i = childCount - 1; i >= 0; i--) {
3214 ExpandableView child = (ExpandableView) getChildAt(i);
3215 if (child.getVisibility() != View.GONE) {
3216 float childTop = child.getY();
3217 if (childTop > touchY) {
3218 // we are above a notification entirely let's abort
3219 return false;
3220 }
3221 boolean belowChild = touchY > childTop + child.getActualHeight();
3222 if (child == mDismissView) {
3223 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3224 touchY - childTop)) {
3225 // We clicked on the dismiss button
3226 return false;
3227 }
3228 } else if (child == mEmptyShadeView) {
3229 // We arrived at the empty shade view, for which we accept all clicks
3230 return true;
3231 } else if (!belowChild){
3232 // We are on a child
3233 return false;
3234 }
3235 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003236 }
Selim Cinek04fb2582015-06-02 19:58:09 +02003237 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003238 }
3239
Selim Cinekb5605e52015-02-20 18:21:41 +01003240 @Override
3241 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08003242 boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01003243 if (animated) {
3244 mExpandedGroupView = changedRow;
3245 mNeedsAnimation = true;
3246 }
3247 changedRow.setChildrenExpanded(expanded, animated);
3248 onHeightChanged(changedRow, false /* needsAnimation */);
3249 }
3250
3251 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003252 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08003253 mPhoneStatusBar.requestNotificationUpdate();
3254 }
3255
3256 @Override
3257 public void onChildIsolationChanged() {
3258 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01003259 }
3260
3261 public void generateChildOrderChangedEvent() {
3262 if (mIsExpanded && mAnimationsEnabled) {
3263 mGenerateChildOrderChangedEvent = true;
3264 mNeedsAnimation = true;
3265 requestChildrenUpdate();
3266 }
3267 }
3268
Selim Cinek684a4422015-04-15 16:18:39 -07003269 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003270 mAnimationFinishedRunnables.add(runnable);
3271 }
3272
3273 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3274 mHeadsUpManager = headsUpManager;
3275 mAmbientState.setHeadsUpManager(headsUpManager);
3276 }
3277
3278 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3279 if (mAnimationsEnabled) {
3280 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3281 mNeedsAnimation = true;
3282 requestChildrenUpdate();
3283 }
3284 }
3285
3286 public void setShadeExpanded(boolean shadeExpanded) {
3287 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07003288 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003289 }
3290
Selim Cineka59ecc32015-04-07 10:51:49 -07003291 /**
3292 * Set the boundary for the bottom heads up position. The heads up will always be above this
3293 * position.
3294 *
3295 * @param height the height of the screen
3296 * @param bottomBarHeight the height of the bar on the bottom
3297 */
3298 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3299 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3300 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003301 requestChildrenUpdate();
3302 }
3303
3304 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3305 mTrackingHeadsUp = trackingHeadsUp;
3306 }
3307
Selim Cinekaac93252015-04-14 20:04:12 -07003308 public void setScrimController(ScrimController scrimController) {
3309 mScrimController = scrimController;
Selim Cinekd35c2792016-01-21 13:20:57 -08003310 mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3311 @Override
3312 public void run() {
3313 updateBackgroundDimming();
3314 }
3315 });
Selim Cinekaac93252015-04-14 20:04:12 -07003316 }
3317
Selim Cinekbbc580b2015-06-03 14:11:03 +02003318 public void forceNoOverlappingRendering(boolean force) {
3319 mForceNoOverlappingRendering = force;
3320 }
3321
3322 @Override
3323 public boolean hasOverlappingRendering() {
3324 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3325 }
3326
Selim Cinek6811d722016-01-19 17:53:12 -08003327 public void setAnimationRunning(boolean animationRunning) {
3328 if (animationRunning != mAnimationRunning) {
3329 if (animationRunning) {
3330 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
3331 } else {
3332 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
3333 }
3334 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08003335 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08003336 }
3337 }
3338
Selim Cinek3776fe02016-02-04 13:32:43 -08003339 public boolean isExpanded() {
3340 return mIsExpanded;
3341 }
3342
Selim Cinek3afd00e2014-08-11 22:32:57 +02003343 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003344 * A listener that is notified when some child locations might have changed.
3345 */
3346 public interface OnChildLocationsChangedListener {
3347 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
3348 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003349
Jorim Jaggi290600a2014-05-30 17:02:20 +02003350 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003351 * A listener that is notified when the empty space below the notifications is clicked on
3352 */
3353 public interface OnEmptySpaceClickListener {
3354 public void onEmptySpaceClicked(float x, float y);
3355 }
3356
3357 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02003358 * A listener that gets notified when the overscroll at the top has changed.
3359 */
3360 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02003361
3362 /**
3363 * Notifies a listener that the overscroll has changed.
3364 *
3365 * @param amount the amount of overscroll, in pixels
3366 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
3367 * unrubberbanded motion to directly expand overscroll view (e.g expand
3368 * QS)
3369 */
3370 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02003371
3372 /**
3373 * Notify a listener that the scroller wants to escape from the scrolling motion and
3374 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
3375 *
3376 * @param velocity The velocity that the Scroller had when over flinging
3377 * @param open Should the fling open or close the overscroll view.
3378 */
3379 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02003380 }
3381
Mady Mellor4b80b102016-01-22 08:03:58 -08003382 /**
3383 * A listener that is notified when the gear is shown behind a notification.
3384 */
3385 public interface GearDisplayedListener {
3386 void onGearDisplayed(ExpandableNotificationRow row);
3387 }
3388
3389 private class NotificationSwipeHelper extends SwipeHelper {
3390 private static final int MOVE_STATE_LEFT = -1;
3391 private static final int MOVE_STATE_UNDEFINED = 0;
3392 private static final int MOVE_STATE_RIGHT = 1;
3393
3394 private static final long GEAR_SHOW_DELAY = 60;
3395
3396 private ArrayList<View> mTranslatingViews = new ArrayList<>();
3397 private CheckForDrag mCheckForDrag;
3398 private Handler mHandler;
3399 private int mMoveState = MOVE_STATE_UNDEFINED;
3400
3401 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
3402 super(swipeDirection, callback, context);
3403 mHandler = new Handler();
3404 }
3405
3406 @Override
3407 public void onDownUpdate(View currView) {
3408 // Set the active view
3409 mTranslatingParentView = currView;
3410
3411 // Reset check for drag gesture
3412 mCheckForDrag = null;
3413
3414 // Slide back any notifications that might be showing a gear
3415 resetExposedGearView();
3416
3417 if (currView instanceof ExpandableNotificationRow) {
3418 // Set the listener for the current row's gear
3419 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
3420 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
3421
3422 // And the translating children
3423 mTranslatingViews = ((ExpandableNotificationRow) currView).getContentViews();
3424 }
3425 mMoveState = MOVE_STATE_UNDEFINED;
3426 }
3427
3428 @Override
3429 public void onMoveUpdate(View view, float translation, float delta) {
3430 final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT;
3431 if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) {
3432 // Changed directions, make sure we check for drag again.
3433 mCheckForDrag = null;
3434 }
3435 mMoveState = newMoveState;
3436
3437 if (view instanceof ExpandableNotificationRow) {
3438 ((ExpandableNotificationRow) view).setTranslationForOutline(translation);
3439 if (!isPinnedHeadsUp(view)) {
3440 // Only show the gear if we're not a heads up view.
3441 checkForDrag();
3442 if (mCurrIconRow != null) {
3443 mCurrIconRow.updateSettingsIcons(translation, getSize(view));
3444 }
3445 }
3446 }
3447 }
3448
3449 @Override
3450 public void dismissChild(final View view, float velocity) {
3451 cancelCheckForDrag();
3452 super.dismissChild(view, velocity);
3453 }
3454
3455 @Override
3456 public void snapChild(final View animView, final float targetLeft, float velocity) {
3457 final float snapBackThreshold = getSpaceForGear(animView);
3458 final float translation = getTranslation(animView);
3459 final boolean fromLeft = translation > 0;
3460 final float absTrans = Math.abs(translation);
3461 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3462
3463 boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f
3464 && translation <= notiThreshold) ||
3465 (!fromLeft && absTrans >= snapBackThreshold * 0.4f
3466 && absTrans <= notiThreshold);
3467
3468 if (pastGear && !isPinnedHeadsUp(animView)) {
3469 // bouncity
3470 final float target = fromLeft ? snapBackThreshold : -snapBackThreshold;
3471 mGearExposedView = mTranslatingParentView;
3472 if (mGearDisplayedListener != null
3473 && (animView instanceof ExpandableNotificationRow)) {
3474 mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
3475 }
3476 super.snapChild(animView, target, velocity);
3477 } else {
3478 super.snapChild(animView, 0, velocity);
3479 }
3480 }
3481
3482 @Override
3483 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
3484 if (mDismissAllInProgress) {
3485 // When dismissing all, we translate the entire view instead.
3486 super.onTranslationUpdate(animView, value, canBeDismissed);
3487 return;
3488 }
3489 if (animView instanceof ExpandableNotificationRow) {
3490 ((ExpandableNotificationRow) animView).setTranslationForOutline(value);
3491 }
3492 if (mCurrIconRow != null) {
3493 mCurrIconRow.updateSettingsIcons(value, getSize(animView));
3494 }
3495 }
3496
3497 @Override
3498 public Animator getViewTranslationAnimator(View v, float target,
3499 AnimatorUpdateListener listener) {
3500 if (mDismissAllInProgress) {
3501 // When dismissing all, we translate the entire view instead.
3502 return super.getViewTranslationAnimator(v, target, listener);
3503 }
3504 ArrayList<Animator> animators = new ArrayList<Animator>();
3505 for (int i = 0; i < mTranslatingViews.size(); i++) {
3506 ObjectAnimator anim = createTranslationAnimation(mTranslatingViews.get(i), target);
3507 animators.add(anim);
3508 if (i == 0 && listener != null) {
3509 anim.addUpdateListener(listener);
3510 }
3511 }
3512 AnimatorSet set = new AnimatorSet();
3513 set.playTogether(animators);
3514 return set;
3515 }
3516
3517 @Override
3518 public void setTranslation(View v, float translate) {
3519 if (mDismissAllInProgress) {
3520 // When dismissing all, we translate the entire view instead.
3521 super.setTranslation(v, translate);
3522 return;
3523 }
3524 // Translate the group of views
3525 for (int i = 0; i < mTranslatingViews.size(); i++) {
3526 if (mTranslatingViews.get(i) != null) {
3527 super.setTranslation(mTranslatingViews.get(i), translate);
3528 }
3529 }
3530 }
3531
3532 @Override
3533 public float getTranslation(View v) {
3534 if (mDismissAllInProgress) {
3535 // When dismissing all, we translate the entire view instead.
3536 return super.getTranslation(v);
3537 }
3538 // All of the views in the list should have same translation, just use first one.
3539 if (mTranslatingViews.size() > 0) {
3540 return super.getTranslation(mTranslatingViews.get(0));
3541 }
3542 return 0;
3543 }
3544
3545
3546 /**
3547 * Returns the horizontal space in pixels required to display the gear behind a
3548 * notification.
3549 */
3550 private float getSpaceForGear(View view) {
3551 if (view instanceof ExpandableNotificationRow) {
3552 return ((ExpandableNotificationRow) view).getSpaceForGear();
3553 }
3554 return 0;
3555 }
3556
3557 private void checkForDrag() {
3558 if (mCheckForDrag == null) {
3559 mCheckForDrag = new CheckForDrag();
3560 mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
3561 }
3562 }
3563
3564 private void cancelCheckForDrag() {
3565 if (mCurrIconRow != null) {
3566 mCurrIconRow.cancelFadeAnimator();
3567 }
3568 mHandler.removeCallbacks(mCheckForDrag);
3569 mCheckForDrag = null;
3570 }
3571
3572 private final class CheckForDrag implements Runnable {
3573 @Override
3574 public void run() {
3575 final float translation = getTranslation(mTranslatingParentView);
3576 final float absTransX = Math.abs(translation);
3577 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
3578 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3579 if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4
3580 && absTransX < notiThreshold) {
3581 // Show icon
3582 mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
3583 notiThreshold);
3584 } else {
3585 // Allow more to be posted if this wasn't a drag.
3586 mCheckForDrag = null;
3587 }
3588 }
3589 }
3590
3591 private void resetExposedGearView() {
3592 if (mGearExposedView == null || mGearExposedView == mTranslatingParentView) {
3593 // If no gear is showing or it's showing for this view we do nothing.
3594 return;
3595 }
3596
3597 final View prevGearExposedView = mGearExposedView;
3598 mGearExposedView = null;
3599
3600 AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
3601 public void onAnimationEnd(Animator animator) {
3602 if (prevGearExposedView instanceof ExpandableNotificationRow) {
3603 ((ExpandableNotificationRow) prevGearExposedView).getSettingsRow()
3604 .resetState();
3605 }
3606 }
3607 };
3608 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
3609 @Override
3610 public void onAnimationUpdate(ValueAnimator animation) {
3611 if (prevGearExposedView instanceof ExpandableNotificationRow) {
3612 ((ExpandableNotificationRow) prevGearExposedView)
3613 .setTranslationForOutline((float) animation.getAnimatedValue());
3614 }
3615 }
3616 };
3617 Animator set = getViewTranslationAnimator(prevGearExposedView, 0, updateListener);
3618 set.addListener(listener);
3619 set.start();
3620 }
3621 }
3622
Selim Cinek33223572016-02-19 19:32:22 -08003623 private void updateContinuousShadowDrawing() {
3624 boolean continuousShadowUpdate = mAnimationRunning
3625 || !mAmbientState.getDraggedViews().isEmpty();
3626 if (continuousShadowUpdate != mContinuousShadowUpdate) {
3627 if (continuousShadowUpdate) {
3628 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
3629 } else {
3630 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
3631 }
3632 }
3633 }
3634
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003635 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02003636
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003637 static AnimationFilter[] FILTERS = new AnimationFilter[] {
3638
3639 // ANIMATION_TYPE_ADD
3640 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003641 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003642 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003643 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003644 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003645 .animateZ()
3646 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003647
3648 // ANIMATION_TYPE_REMOVE
3649 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003650 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003651 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003652 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003653 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003654 .animateZ()
3655 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003656
3657 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3658 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003659 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003660 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003661 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003662 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003663 .animateZ()
3664 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003665
3666 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3667 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003668 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003669 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003670 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003671 .animateY()
3672 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003673 .animateZ(),
3674
3675 // ANIMATION_TYPE_START_DRAG
3676 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003677 .animateShadowAlpha(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003678
3679 // ANIMATION_TYPE_SNAP_BACK
3680 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003681 .animateShadowAlpha()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003682 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003683
3684 // ANIMATION_TYPE_ACTIVATED_CHILD
3685 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003686 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003687
3688 // ANIMATION_TYPE_DIMMED
3689 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02003690 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003691 .animateDimmed(),
3692
3693 // ANIMATION_TYPE_CHANGE_POSITION
3694 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003695 .animateAlpha() // maybe the children change positions
3696 .animateShadowAlpha()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003697 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003698 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003699 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04003700 .animateZ(),
3701
3702 // ANIMATION_TYPE_DARK
3703 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01003704 .animateDark()
3705 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003706
3707 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3708 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003709 .animateShadowAlpha()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003710 .animateHeight()
3711 .animateTopInset()
3712 .animateY()
3713 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02003714 .animateZ()
3715 .hasDelays(),
3716
3717 // ANIMATION_TYPE_HIDE_SENSITIVE
3718 new AnimationFilter()
3719 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02003720
3721 // ANIMATION_TYPE_VIEW_RESIZE
3722 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003723 .animateShadowAlpha()
Selim Cineka5e211b2014-08-11 17:35:48 +02003724 .animateHeight()
3725 .animateTopInset()
3726 .animateY()
3727 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02003728
Selim Cinekb5605e52015-02-20 18:21:41 +01003729 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
3730 new AnimationFilter()
3731 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003732 .animateShadowAlpha()
Selim Cinekb5605e52015-02-20 18:21:41 +01003733 .animateHeight()
3734 .animateTopInset()
3735 .animateY()
3736 .animateZ(),
3737
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003738 // ANIMATION_TYPE_HEADS_UP_APPEAR
3739 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003740 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003741 .animateHeight()
3742 .animateTopInset()
3743 .animateY()
3744 .animateZ(),
3745
3746 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3747 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003748 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003749 .animateHeight()
3750 .animateTopInset()
3751 .animateY()
3752 .animateZ(),
3753
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003754 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3755 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003756 .animateShadowAlpha()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003757 .animateHeight()
3758 .animateTopInset()
3759 .animateY()
3760 .animateZ()
3761 .hasDelays(),
3762
Selim Cineka59ecc32015-04-07 10:51:49 -07003763 // ANIMATION_TYPE_HEADS_UP_OTHER
3764 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003765 .animateShadowAlpha()
Selim Cineka59ecc32015-04-07 10:51:49 -07003766 .animateHeight()
3767 .animateTopInset()
3768 .animateY()
3769 .animateZ(),
3770
Selim Cinekd9acca52014-09-01 22:33:25 +02003771 // ANIMATION_TYPE_EVERYTHING
3772 new AnimationFilter()
3773 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003774 .animateShadowAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02003775 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02003776 .animateDimmed()
3777 .animateHideSensitive()
3778 .animateHeight()
3779 .animateTopInset()
3780 .animateY()
3781 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003782 };
3783
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003784 static int[] LENGTHS = new int[] {
3785
3786 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003787 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003788
3789 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003790 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003791
3792 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3793 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3794
3795 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3796 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3797
3798 // ANIMATION_TYPE_START_DRAG
3799 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3800
3801 // ANIMATION_TYPE_SNAP_BACK
3802 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3803
3804 // ANIMATION_TYPE_ACTIVATED_CHILD
3805 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3806
3807 // ANIMATION_TYPE_DIMMED
3808 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003809
3810 // ANIMATION_TYPE_CHANGE_POSITION
3811 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04003812
3813 // ANIMATION_TYPE_DARK
3814 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003815
3816 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3817 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02003818
3819 // ANIMATION_TYPE_HIDE_SENSITIVE
3820 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02003821
3822 // ANIMATION_TYPE_VIEW_RESIZE
3823 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02003824
Selim Cinekb5605e52015-02-20 18:21:41 +01003825 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08003826 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01003827
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003828 // ANIMATION_TYPE_HEADS_UP_APPEAR
3829 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
3830
3831 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3832 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3833
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003834 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3835 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3836
Selim Cineka59ecc32015-04-07 10:51:49 -07003837 // ANIMATION_TYPE_HEADS_UP_OTHER
3838 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3839
Selim Cinekd9acca52014-09-01 22:33:25 +02003840 // ANIMATION_TYPE_EVERYTHING
3841 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003842 };
3843
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003844 static final int ANIMATION_TYPE_ADD = 0;
3845 static final int ANIMATION_TYPE_REMOVE = 1;
3846 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
3847 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
3848 static final int ANIMATION_TYPE_START_DRAG = 4;
3849 static final int ANIMATION_TYPE_SNAP_BACK = 5;
3850 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
3851 static final int ANIMATION_TYPE_DIMMED = 7;
3852 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04003853 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003854 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02003855 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02003856 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01003857 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003858 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
3859 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003860 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
3861 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
3862 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003863
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003864 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3865 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3866
Selim Cinek572bbd42014-04-25 16:43:27 +02003867 final long eventStartTime;
3868 final View changingView;
3869 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003870 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003871 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003872 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003873 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07003874 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02003875
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003876 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003877 this(view, type, LENGTHS[type]);
3878 }
3879
3880 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003881 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3882 changingView = view;
3883 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003884 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003885 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003886 }
3887
3888 /**
3889 * Combines the length of several animation events into a single value.
3890 *
3891 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003892 * @return The combined length. Depending on the event types, this might be the maximum of
3893 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003894 */
3895 static long combineLength(ArrayList<AnimationEvent> events) {
3896 long length = 0;
3897 int size = events.size();
3898 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003899 AnimationEvent event = events.get(i);
3900 length = Math.max(length, event.length);
3901 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3902 return event.length;
3903 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003904 }
3905 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02003906 }
3907 }
3908
Selim Cinek67b22602014-03-10 15:40:16 +01003909}