blob: 480ed0aae156dba908d27f6153069b49c19c0856 [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;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
Selim Cinekd35c2792016-01-21 13:20:57 -080023import android.animation.TimeAnimator;
24import android.animation.ValueAnimator;
Mady Mellor4b80b102016-01-22 08:03:58 -080025import android.animation.ValueAnimator.AnimatorUpdateListener;
Selim Cinek31d37b92016-04-26 09:56:42 -070026import android.annotation.FloatRange;
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;
Selim Cinekc22fff62016-05-20 12:44:30 -070037import android.os.Bundle;
Mady Mellor4b80b102016-01-22 08:03:58 -080038import android.os.Handler;
Selim Cinek67b22602014-03-10 15:40:16 +010039import android.util.AttributeSet;
Selim Cinek972123d2016-05-03 14:25:58 -070040import android.util.FloatProperty;
Selim Cinek67b22602014-03-10 15:40:16 +010041import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070042import android.util.Pair;
Selim Cinek972123d2016-05-03 14:25:58 -070043import android.util.Property;
Selim Cinek67b22602014-03-10 15:40:16 +010044import android.view.MotionEvent;
45import android.view.VelocityTracker;
46import android.view.View;
47import android.view.ViewConfiguration;
48import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020049import android.view.ViewTreeObserver;
Adrian Roos5153d4a2016-03-22 10:01:56 -070050import android.view.WindowInsets;
Selim Cinekc22fff62016-05-20 12:44:30 -070051import android.view.accessibility.AccessibilityEvent;
52import android.view.accessibility.AccessibilityNodeInfo;
Selim Cinek572bbd42014-04-25 16:43:27 +020053import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080054import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010055import android.widget.OverScroller;
Selim Cinek41fe89a2016-06-02 15:27:56 -070056import android.widget.ScrollView;
Jorim Jaggi56306252014-07-03 00:40:09 +020057
Mady Mellora41587b2016-02-11 18:43:06 -080058import com.android.internal.logging.MetricsLogger;
Tamas Berghammercbd3f0c2016-06-22 15:21:38 +010059import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Selim Cinek67b22602014-03-10 15:40:16 +010060import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080061import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010062import com.android.systemui.R;
63import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070064import com.android.systemui.classifier.FalsingManager;
Selim Cineka32ab602014-06-11 15:06:01 +020065import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040066import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020067import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010068import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020069import com.android.systemui.statusbar.ExpandableView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -070070import com.android.systemui.statusbar.NotificationGuts;
Selim Cinek2cd45df2015-06-09 18:00:07 -070071import com.android.systemui.statusbar.NotificationOverflowContainer;
Mady Mellor4b80b102016-01-22 08:03:58 -080072import com.android.systemui.statusbar.NotificationSettingsIconRow;
73import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010074import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020075import com.android.systemui.statusbar.StatusBarState;
Selim Cinek33223572016-02-19 19:32:22 -080076import com.android.systemui.statusbar.notification.FakeShadowView;
Selim Cinek42357e02016-02-24 18:48:01 -080077import com.android.systemui.statusbar.notification.NotificationUtils;
Selim Cinekb5605e52015-02-20 18:21:41 +010078import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020079import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070080import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070081import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010082import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010083
Selim Cinek572bbd42014-04-25 16:43:27 +020084import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -080085import java.util.Collections;
86import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020087import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020088
Selim Cinek67b22602014-03-10 15:40:16 +010089/**
90 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
91 */
92public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020093 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Mady Mellor4b80b102016-01-22 08:03:58 -080094 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
Adrian Roos5153d4a2016-03-22 10:01:56 -070095 SettingsIconRowListener, ScrollContainer {
Selim Cinek67b22602014-03-10 15:40:16 +010096
Selim Cinekd35c2792016-01-21 13:20:57 -080097 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -080098 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +010099 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200100 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
101 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200102 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +0100103 /**
104 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
105 */
106 private static final int INVALID_POINTER = -1;
107
Selim Cinek1408eb52014-06-02 14:45:38 +0200108 private ExpandHelper mExpandHelper;
Selim Cinek33223572016-02-19 19:32:22 -0800109 private NotificationSwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +0200110 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +0100111 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800112 private final Paint mBackgroundPaint = new Paint();
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100113
Selim Cinekbc243a92016-09-27 16:35:13 -0700114 private float mExpandedHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100115 private int mOwnScrollY;
116 private int mMaxLayoutHeight;
117
118 private VelocityTracker mVelocityTracker;
119 private OverScroller mScroller;
Ricky Waicd35def2016-05-03 11:07:07 +0100120 private Runnable mFinishScrollingCallback;
Selim Cinek67b22602014-03-10 15:40:16 +0100121 private int mTouchSlop;
122 private int mMinimumVelocity;
123 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100124 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200125 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100126 private boolean mIsBeingDragged;
127 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200128 private int mDownX;
Dong-wan Kimb9266662016-09-21 13:08:30 -0700129 private int mActivePointerId = INVALID_POINTER;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100130 private boolean mTouchIsClick;
131 private float mInitialTouchX;
132 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100133
Selim Cinek67b22602014-03-10 15:40:16 +0100134 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100135 private int mContentHeight;
136 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200137 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100138 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100139 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800140 private int mIncreasedPaddingBetweenElements;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200141 private int mTopPadding;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700142 private int mBottomInset = 0;
Selim Cinek67b22602014-03-10 15:40:16 +0100143
144 /**
145 * The algorithm which calculates the properties for our children
146 */
Selim Cinekaf0dc312015-12-15 17:01:44 -0800147 private final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100148
149 /**
150 * The current State this Layout is in
151 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200152 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200153 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100154 private NotificationGroupManager mGroupManager;
Selim Cinek3776fe02016-02-04 13:32:43 -0800155 private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700156 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
157 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
158 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
159 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
160 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200161 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700162 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
163 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200164 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200165 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200166 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800167 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200168
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200169 /**
170 * The raw amount of the overScroll on the top, which is not rubber-banded.
171 */
172 private float mOverScrolledTopPixels;
173
174 /**
175 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
176 */
177 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200178 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200179 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200180 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100181 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200182 private boolean mNeedsAnimation;
183 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200184 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200185 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400186 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100187 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200188 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200189 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200190 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200191 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200192 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700193 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200194 private boolean mExpandingNotification;
195 private boolean mExpandedInThisMotion;
196 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400197 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200198 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400199 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200200
201 /**
202 * Was the scroller scrolled to the top when the down motion was observed?
203 */
204 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200205 /**
206 * The minimal amount of over scroll which is needed in order to switch to the quick settings
207 * when over scrolling on a expanded card.
208 */
209 private float mMinTopOverScrollToEscape;
210 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700211 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200212 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200213 private boolean mDontReportNextOverScroll;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700214 private boolean mDontClampNextScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200215 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100216 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700217 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700218
Selim Cinek1408eb52014-06-02 14:45:38 +0200219 /**
220 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
221 * This is needed to avoid scrolling too far after the notification was collapsed in the same
222 * motion.
223 */
224 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400225 private SwipeHelper.LongPressListener mLongPressListener;
Mady Mellor4b80b102016-01-22 08:03:58 -0800226
227 private NotificationSettingsIconRow mCurrIconRow;
228 private View mTranslatingParentView;
229 private View mGearExposedView;
Selim Cinek1408eb52014-06-02 14:45:38 +0200230
231 /**
232 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
233 * animating.
234 */
235 private boolean mOnlyScrollingInThisMotion;
Adrian Roosfa139752016-04-27 09:59:08 -0700236 private boolean mDisallowDismissInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200237 private boolean mInterceptDelegateEnabled;
238 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700239 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700240 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200241 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200242 = new ViewTreeObserver.OnPreDrawListener() {
243 @Override
244 public boolean onPreDraw() {
Adrian Roos181385c2016-05-05 17:45:44 -0400245 updateForcedScroll();
Selim Cinek1f553cf2014-05-02 12:01:36 +0200246 updateChildren();
247 mChildrenUpdateRequested = false;
248 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200249 return true;
250 }
251 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200252 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100253 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100254 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700255 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700256 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700257 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
258 = new HashSet<>();
259 private HeadsUpManager mHeadsUpManager;
260 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700261 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200262 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700263 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700264 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700265 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800266 private boolean mAnimationRunning;
267 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
268 = new ViewTreeObserver.OnPreDrawListener() {
269 @Override
270 public boolean onPreDraw() {
Selim Cinek614576e2016-01-20 10:54:09 -0800271 // if it needs animation
272 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
273 updateBackground();
274 }
Selim Cinek6811d722016-01-19 17:53:12 -0800275 return true;
276 }
277 };
278 private Rect mBackgroundBounds = new Rect();
Selim Cinek614576e2016-01-20 10:54:09 -0800279 private Rect mStartAnimationRect = new Rect();
280 private Rect mEndAnimationRect = new Rect();
Selim Cinekd35c2792016-01-21 13:20:57 -0800281 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
Selim Cinek614576e2016-01-20 10:54:09 -0800282 private boolean mAnimateNextBackgroundBottom;
283 private boolean mAnimateNextBackgroundTop;
284 private ObjectAnimator mBottomAnimator = null;
285 private ObjectAnimator mTopAnimator = null;
286 private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
287 private ActivatableNotificationView mLastVisibleBackgroundChild = null;
Selim Cinekd35c2792016-01-21 13:20:57 -0800288 private int mBgColor;
289 private float mDimAmount;
290 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800291 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Selim Cinekd35c2792016-01-21 13:20:57 -0800292 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
293 @Override
294 public void onAnimationEnd(Animator animation) {
295 mDimAnimator = null;
296 }
297 };
298 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
299 = new ValueAnimator.AnimatorUpdateListener() {
300
301 @Override
302 public void onAnimationUpdate(ValueAnimator animation) {
303 setDimAmount((Float) animation.getAnimatedValue());
304 }
305 };
Muyuan Li4fe4a402016-03-30 16:50:11 -0700306 protected ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800307 private boolean mContinuousShadowUpdate;
308 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
309 = new ViewTreeObserver.OnPreDrawListener() {
310
311 @Override
312 public boolean onPreDraw() {
313 updateViewShadows();
314 return true;
315 }
316 };
317 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
318 @Override
319 public int compare(ExpandableView view, ExpandableView otherView) {
320 float endY = view.getTranslationY() + view.getActualHeight();
321 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
322 if (endY < otherEndY) {
323 return -1;
324 } else if (endY > otherEndY) {
325 return 1;
326 } else {
327 // The two notifications end at the same location
328 return 0;
329 }
330 }
331 };
Selim Cinek25503252016-03-03 15:31:43 -0800332 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
Selim Cinekcd5b22f2016-03-08 16:15:41 -0800333 private boolean mPulsing;
Selim Cinek31d37b92016-04-26 09:56:42 -0700334 private boolean mDrawBackgroundAsSrc;
Selim Cinek07304f5222016-05-19 18:31:36 -0700335 private boolean mFadingOut;
336 private boolean mParentFadingOut;
Selim Cinek1b2a05e2016-04-28 14:20:39 -0700337 private boolean mGroupExpandedForMeasure;
Selim Cinekc22fff62016-05-20 12:44:30 -0700338 private boolean mScrollable;
Adrian Roos181385c2016-05-05 17:45:44 -0400339 private View mForcedScroll;
Selim Cinek972123d2016-05-03 14:25:58 -0700340 private float mBackgroundFadeAmount = 1.0f;
341 private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
342 new FloatProperty<NotificationStackScrollLayout>("backgroundFade") {
343 @Override
344 public void setValue(NotificationStackScrollLayout object, float value) {
345 object.setBackgroundFadeAmount(value);
346 }
347
348 @Override
349 public Float get(NotificationStackScrollLayout object) {
350 return object.getBackgroundFadeAmount();
351 }
352 };
Selim Cinekbc243a92016-09-27 16:35:13 -0700353 private boolean mQsExpanded;
Selim Cinekef406062016-09-29 17:33:13 -0700354 private boolean mForwardScrollable;
355 private boolean mBackwardScrollable;
Selim Cinek67b22602014-03-10 15:40:16 +0100356
357 public NotificationStackScrollLayout(Context context) {
358 this(context, null);
359 }
360
361 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
362 this(context, attrs, 0);
363 }
364
365 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
366 this(context, attrs, defStyleAttr, 0);
367 }
368
369 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
370 int defStyleRes) {
371 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinekd35c2792016-01-21 13:20:57 -0800372 mBgColor = context.getColor(R.color.notification_shade_background_color);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200373 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
374 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
375 mExpandHelper = new ExpandHelper(getContext(), this,
376 minHeight, maxHeight);
377 mExpandHelper.setEventSource(this);
378 mExpandHelper.setScrollAdapter(this);
Mady Mellor4b80b102016-01-22 08:03:58 -0800379 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
Selim Cinek1cf41c12014-08-12 20:06:19 +0200380 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800381 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100382 initView(context);
Selim Cinek6811d722016-01-19 17:53:12 -0800383 setWillNotDraw(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100384 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100385 mDebugPaint = new Paint();
386 mDebugPaint.setColor(0xffff0000);
387 mDebugPaint.setStrokeWidth(2);
388 mDebugPaint.setStyle(Paint.Style.STROKE);
389 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700390 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100391 }
392
393 @Override
Mady Mellorb53bc272016-02-11 18:28:23 -0800394 public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800395 if (mLongPressListener != null) {
Mady Mellora41587b2016-02-11 18:43:06 -0800396 MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
397 row.getStatusBarNotification().getPackageName());
Mady Mellorb53bc272016-02-11 18:28:23 -0800398 mLongPressListener.onLongPress(row, x, y);
Mady Mellor4b80b102016-01-22 08:03:58 -0800399 }
400 }
401
402 @Override
Mady Mellor7a9b2a62016-03-23 07:41:47 -0700403 public void onSettingsIconRowReset(ExpandableNotificationRow row) {
404 if (mTranslatingParentView != null && row == mTranslatingParentView) {
405 mSwipeHelper.setSnappedToGear(false);
406 mGearExposedView = null;
407 mTranslatingParentView = null;
408 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000409 }
410
411 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100412 protected void onDraw(Canvas canvas) {
Selim Cinekd381bc32016-08-15 12:40:57 -0700413 if (mCurrentBounds.top < mCurrentBounds.bottom) {
414 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
415 mBackgroundPaint);
416 }
Selim Cinek67b22602014-03-10 15:40:16 +0100417 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800418 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100419 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200420 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200421 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200422 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
423 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100424 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
425 y = (int) getLayoutHeight();
426 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200427 y = getHeight() - getEmptyBottomMargin();
428 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100429 }
430 }
431
Selim Cinekd35c2792016-01-21 13:20:57 -0800432 private void updateBackgroundDimming() {
433 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
Selim Cinek972123d2016-05-03 14:25:58 -0700434 alpha *= mBackgroundFadeAmount;
Selim Cinekd35c2792016-01-21 13:20:57 -0800435 // We need to manually blend in the background color
436 int scrimColor = mScrimController.getScrimBehindColor();
437 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
438 float alphaInv = 1 - alpha;
439 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
Selim Cinek972123d2016-05-03 14:25:58 -0700440 (int) (mBackgroundFadeAmount * Color.red(mBgColor)
441 + alphaInv * Color.red(scrimColor)),
442 (int) (mBackgroundFadeAmount * Color.green(mBgColor)
443 + alphaInv * Color.green(scrimColor)),
444 (int) (mBackgroundFadeAmount * Color.blue(mBgColor)
445 + alphaInv * Color.blue(scrimColor)));
Selim Cinekd35c2792016-01-21 13:20:57 -0800446 mBackgroundPaint.setColor(color);
447 invalidate();
448 }
449
Selim Cinek67b22602014-03-10 15:40:16 +0100450 private void initView(Context context) {
451 mScroller = new OverScroller(getContext());
Selim Cinek67b22602014-03-10 15:40:16 +0100452 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200453 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100454 final ViewConfiguration configuration = ViewConfiguration.get(context);
455 mTouchSlop = configuration.getScaledTouchSlop();
456 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
457 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100458 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100459 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200460 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100461 mBottomStackPeekSize = context.getResources()
462 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800463 mStackScrollAlgorithm.initView(context);
Selim Cinek61633a82016-01-25 15:54:10 -0800464 mPaddingBetweenElements = Math.max(1, context.getResources()
Selim Cinekcacc6042016-01-21 16:16:41 -0800465 .getDimensionPixelSize(R.dimen.notification_divider_height));
Selim Cinek61633a82016-01-25 15:54:10 -0800466 mIncreasedPaddingBetweenElements = context.getResources()
467 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
Selim Cinek587cbf32016-01-19 11:36:18 -0800468 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
Selim Cinek1408eb52014-06-02 14:45:38 +0200469 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
470 R.dimen.min_top_overscroll_to_qs);
Selim Cineka5eaa602014-05-12 21:27:47 +0200471 }
472
Selim Cinek25503252016-03-03 15:31:43 -0800473 public void setDrawBackgroundAsSrc(boolean asSrc) {
Selim Cinek31d37b92016-04-26 09:56:42 -0700474 mDrawBackgroundAsSrc = asSrc;
475 updateSrcDrawing();
476 }
477
478 private void updateSrcDrawing() {
Selim Cinek07304f5222016-05-19 18:31:36 -0700479 mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut)
480 ? mSrcMode : null);
Selim Cinek25503252016-03-03 15:31:43 -0800481 invalidate();
482 }
483
Selim Cinekaef92ef2014-06-06 18:06:04 +0200484 private void notifyHeightChangeListener(ExpandableView view) {
485 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100486 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200487 }
Selim Cinek67b22602014-03-10 15:40:16 +0100488 }
489
490 @Override
491 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
492 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -0400493 // We need to measure all children even the GONE ones, such that the heights are calculated
494 // correctly as they are used to calculate how many we can fit on the screen.
495 final int size = getChildCount();
496 for (int i = 0; i < size; i++) {
497 measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
498 }
Selim Cinek67b22602014-03-10 15:40:16 +0100499 }
500
501 @Override
502 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinek67b22602014-03-10 15:40:16 +0100503 // we layout all our children centered on the top
504 float centerX = getWidth() / 2.0f;
505 for (int i = 0; i < getChildCount(); i++) {
506 View child = getChildAt(i);
Selim Cinekfa760d42016-05-10 15:50:53 -0400507 // We need to layout all children even the GONE ones, such that the heights are
508 // calculated correctly as they are used to calculate how many we can fit on the screen
Selim Cinek67b22602014-03-10 15:40:16 +0100509 float width = child.getMeasuredWidth();
510 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100511 child.layout((int) (centerX - width / 2.0f),
512 0,
513 (int) (centerX + width / 2.0f),
514 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100515 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200516 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100517 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200518 clampScrollPosition();
Selim Cinek319bdc42014-05-01 23:01:58 +0200519 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -0800520 updateFirstAndLastBackgroundViews();
Selim Cinekbc243a92016-09-27 16:35:13 -0700521 updateAlgorithmLayoutMinHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100522 }
523
Selim Cinek5bc852a2015-12-21 12:19:09 -0800524 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
525 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200526 mNeedViewResizeAnimation = true;
527 mNeedsAnimation = true;
528 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200529 }
530
Selim Cinekc27437b2014-05-14 10:23:33 +0200531 public void updateSpeedBumpIndex(int newIndex) {
Selim Cinekc27437b2014-05-14 10:23:33 +0200532 mAmbientState.setSpeedBumpIndex(newIndex);
533 }
534
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200535 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
536 mListener = listener;
537 }
538
539 /**
540 * Returns the location the given child is currently rendered at.
541 *
542 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100543 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200544 */
545 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100546 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200547 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100548 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200549 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100550 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100551 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100552 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200553 return childViewState.location;
554 }
555
Selim Cinek67b22602014-03-10 15:40:16 +0100556 private void setMaxLayoutHeight(int maxLayoutHeight) {
557 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200558 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100559 }
560
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200561 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700562 mAmbientState.setLayoutHeight(getLayoutHeight());
Selim Cinekbc243a92016-09-27 16:35:13 -0700563 updateAlgorithmLayoutMinHeight();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700564 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100565 }
566
Selim Cinekbc243a92016-09-27 16:35:13 -0700567 private void updateAlgorithmLayoutMinHeight() {
568 mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() ? getLayoutMinHeight() : 0);
569 }
570
Selim Cinek67b22602014-03-10 15:40:16 +0100571 /**
572 * Updates the children views according to the stack scroll algorithm. Call this whenever
573 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
574 */
575 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -0800576 updateScrollStateForAddedChildren();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200577 mAmbientState.setScrollY(mOwnScrollY);
578 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200579 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200580 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100581 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200582 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100583 }
584 }
585
Selim Cinek3776fe02016-02-04 13:32:43 -0800586 private void updateScrollStateForAddedChildren() {
587 if (mChildrenToAddAnimated.isEmpty()) {
588 return;
589 }
590 for (int i = 0; i < getChildCount(); i++) {
591 ExpandableView child = (ExpandableView) getChildAt(i);
592 if (mChildrenToAddAnimated.contains(child)) {
593 int startingPosition = getPositionInLinearLayout(child);
Selim Cinek42357e02016-02-24 18:48:01 -0800594 int padding = child.getIncreasedPaddingAmount() == 1.0f
Selim Cinek3776fe02016-02-04 13:32:43 -0800595 ? mIncreasedPaddingBetweenElements :
596 mPaddingBetweenElements;
597 int childHeight = getIntrinsicHeight(child) + padding;
598 if (startingPosition < mOwnScrollY) {
599 // This child starts off screen, so let's keep it offscreen to keep the others visible
600
Selim Cinekef406062016-09-29 17:33:13 -0700601 setOwnScrollY(mOwnScrollY + childHeight);
Selim Cinek3776fe02016-02-04 13:32:43 -0800602 }
603 }
604 }
605 clampScrollPosition();
606 }
607
Adrian Roos181385c2016-05-05 17:45:44 -0400608 private void updateForcedScroll() {
609 if (mForcedScroll != null && (!mForcedScroll.hasFocus()
610 || !mForcedScroll.isAttachedToWindow())) {
611 mForcedScroll = null;
612 }
613 if (mForcedScroll != null) {
614 ExpandableView expandableView = (ExpandableView) mForcedScroll;
615 int positionInLinearLayout = getPositionInLinearLayout(expandableView);
616 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
Adrian Roos4a579672016-05-24 16:54:37 -0700617 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Adrian Roos181385c2016-05-05 17:45:44 -0400618
619 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
Adrian Roos4a579672016-05-24 16:54:37 -0700620
621 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
622 // that it is not visible anymore.
623 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
Selim Cinekef406062016-09-29 17:33:13 -0700624 setOwnScrollY(targetScroll);
Adrian Roos181385c2016-05-05 17:45:44 -0400625 }
626 }
627 }
628
Selim Cinek319bdc42014-05-01 23:01:58 +0200629 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200630 if (!mChildrenUpdateRequested) {
631 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
632 mChildrenUpdateRequested = true;
633 invalidate();
634 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200635 }
636
Selim Cinek67b22602014-03-10 15:40:16 +0100637 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200638 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100639 }
640
Selim Cinekf7a14c02014-07-07 14:01:46 +0200641 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100642 int scrollRange = getScrollRange();
643 if (scrollRange < mOwnScrollY) {
Selim Cinekef406062016-09-29 17:33:13 -0700644 setOwnScrollY(scrollRange);
Selim Cinek67b22602014-03-10 15:40:16 +0100645 }
646 }
647
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200648 public int getTopPadding() {
649 return mTopPadding;
650 }
651
Selim Cinek1408eb52014-06-02 14:45:38 +0200652 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200653 if (mTopPadding != topPadding) {
654 mTopPadding = topPadding;
655 updateAlgorithmHeightAndPadding();
656 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200657 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200658 mTopPaddingNeedsAnimation = true;
659 mNeedsAnimation = true;
660 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200661 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200662 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200663 }
664 }
665
666 /**
Selim Cinekbc243a92016-09-27 16:35:13 -0700667 * Update the height of the panel.
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200668 *
Selim Cinekbc243a92016-09-27 16:35:13 -0700669 * @param height the expanded height of the panel
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200670 */
Selim Cinekbc243a92016-09-27 16:35:13 -0700671 public void setExpandedHeight(float height) {
672 mExpandedHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200673 setIsExpanded(height > 0.0f);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200674 int stackHeight;
Selim Cinek94c2d822016-07-13 18:50:04 -0700675 float translationY;
676 float appearEndPosition = getAppearEndPosition();
677 float appearStartPosition = getAppearStartPosition();
678 if (height >= appearEndPosition) {
Selim Cinekbc243a92016-09-27 16:35:13 -0700679 translationY = 0;
Selim Cinek94c2d822016-07-13 18:50:04 -0700680 stackHeight = (int) height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200681 } else {
Selim Cinek94c2d822016-07-13 18:50:04 -0700682 float appearFraction = getAppearFraction(height);
683 if (appearFraction >= 0) {
684 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
685 appearFraction);
686 } else {
687 // This may happen when pushing up a heads up. We linearly push it up from the
688 // start
689 translationY = height - appearStartPosition + getExpandTranslationStart();
690 }
691 stackHeight = (int) (height - translationY);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200692 }
693 if (stackHeight != mCurrentStackHeight) {
694 mCurrentStackHeight = stackHeight;
695 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200696 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200697 }
Selim Cinek94c2d822016-07-13 18:50:04 -0700698 setStackTranslation(translationY);
699 }
700
701 /**
702 * @return The translation at the beginning when expanding.
703 * Measured relative to the resting position.
704 */
705 private float getExpandTranslationStart() {
Selim Cinekbc243a92016-09-27 16:35:13 -0700706 int startPosition = 0;
707 if (!mTrackingHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
708 startPosition = - Math.min(getFirstChildIntrinsicHeight(),
709 mMaxLayoutHeight - mIntrinsicPadding - mBottomStackSlowDownHeight
710 - mBottomStackPeekSize);
711 }
Selim Cinek94c2d822016-07-13 18:50:04 -0700712 return startPosition - mTopPadding;
713 }
714
715 /**
716 * @return the position from where the appear transition starts when expanding.
717 * Measured in absolute height.
718 */
719 private float getAppearStartPosition() {
720 return mTrackingHeadsUp
721 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
722 : 0;
723 }
724
725 /**
726 * @return the position from where the appear transition ends when expanding.
727 * Measured in absolute height.
728 */
729 private float getAppearEndPosition() {
730 int firstItemHeight = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
731 ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize
732 + mBottomStackSlowDownHeight
733 : getLayoutMinHeight();
Selim Cinekbc243a92016-09-27 16:35:13 -0700734 return firstItemHeight + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
Selim Cinek94c2d822016-07-13 18:50:04 -0700735 }
736
737 /**
738 * @param height the height of the panel
739 * @return the fraction of the appear animation that has been performed
740 */
741 public float getAppearFraction(float height) {
742 float appearEndPosition = getAppearEndPosition();
743 float appearStartPosition = getAppearStartPosition();
744 return (height - appearStartPosition)
745 / (appearEndPosition - appearStartPosition);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700746 }
747
Selim Cinekd2281152015-04-10 14:37:46 -0700748 public float getStackTranslation() {
749 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700750 }
751
Selim Cinekd2281152015-04-10 14:37:46 -0700752 private void setStackTranslation(float stackTranslation) {
753 if (stackTranslation != mStackTranslation) {
754 mStackTranslation = stackTranslation;
755 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700756 requestChildrenUpdate();
757 }
Selim Cinek67b22602014-03-10 15:40:16 +0100758 }
759
760 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100761 * 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 +0100762 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
763 *
764 * @return either the layout height or the externally defined height, whichever is smaller
765 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200766 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100767 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
768 }
769
Selim Cinek816c8e42015-11-19 12:00:45 -0800770 public int getFirstItemMinHeight() {
771 final ExpandableView firstChild = getFirstChildNotGone();
772 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100773 }
774
775 public int getBottomStackPeekSize() {
776 return mBottomStackPeekSize;
777 }
778
Selim Cinekd1ad9ab2016-03-01 17:52:20 -0800779 public int getBottomStackSlowDownHeight() {
780 return mBottomStackSlowDownHeight;
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200781 }
782
Dan Sandler4247a5c2014-07-23 15:58:08 -0400783 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100784 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400785 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100786 }
787
Jason Monk16ac3772016-02-10 15:39:21 -0500788 public void setQsContainer(ViewGroup qsContainer) {
789 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +0200790 }
791
Mady Mellorb0a82462016-04-30 17:31:02 -0700792 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100793 public void onChildDismissed(View v) {
Selim Cinekd1395642016-04-28 12:22:42 -0700794 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
795 if (!row.isDismissed()) {
796 handleChildDismissed(v);
797 }
798 ViewGroup transientContainer = row.getTransientContainer();
799 if (transientContainer != null) {
800 transientContainer.removeTransientView(v);
801 }
802 }
803
804 private void handleChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400805 if (mDismissAllInProgress) {
806 return;
807 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100808 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200809 if (mDragAnimPendingChildren.contains(v)) {
810 // We start the swipe and finish it in the same frame, we don't want any animation
811 // for the drag
812 mDragAnimPendingChildren.remove(v);
813 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200814 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200815 mAmbientState.onDragFinished(v);
Selim Cinek33223572016-02-19 19:32:22 -0800816 updateContinuousShadowDrawing();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700817 if (v instanceof ExpandableNotificationRow) {
818 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
819 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700820 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700821 }
822 }
Selim Cineke9bad242016-06-15 11:46:37 -0700823 performDismiss(v, mGroupManager, false /* fromAccessibility */);
Blazej Magnowski72323322015-07-24 11:49:40 -0700824
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700825 mFalsingManager.onNotificationDismissed();
826 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700827 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
Felipe Lemeee5d6302016-04-22 16:11:19 -0700828 false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
Blazej Magnowski72323322015-07-24 11:49:40 -0700829 }
Selim Cinekeb973562014-05-02 17:07:49 +0200830 }
831
Selim Cineke9bad242016-06-15 11:46:37 -0700832 public static void performDismiss(View v, NotificationGroupManager groupManager,
833 boolean fromAccessibility) {
Selim Cinek9e624e72016-07-20 13:46:49 -0700834 if (!(v instanceof ExpandableNotificationRow)) {
835 return;
Selim Cinek2a739342016-03-17 10:28:55 -0700836 }
Selim Cinek9e624e72016-07-20 13:46:49 -0700837 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
838 if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
839 ExpandableNotificationRow groupSummary =
840 groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
841 if (groupSummary.isClearable()) {
842 performDismiss(groupSummary, groupManager, fromAccessibility);
843 }
844 }
845 row.setDismissed(true, fromAccessibility);
846 if (row.isClearable()) {
847 row.performDismiss();
Selim Cinek2a739342016-03-17 10:28:55 -0700848 }
849 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
850 }
851
Selim Cinekeb973562014-05-02 17:07:49 +0200852 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800853 public void onChildSnappedBack(View animView, float targetLeft) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200854 mAmbientState.onDragFinished(animView);
Selim Cinek33223572016-02-19 19:32:22 -0800855 updateContinuousShadowDrawing();
Selim Cinekeb973562014-05-02 17:07:49 +0200856 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200857 if (mAnimationsEnabled) {
858 mSnappedBackChildren.add(animView);
859 mNeedsAnimation = true;
860 }
Selim Cinekeb973562014-05-02 17:07:49 +0200861 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200862 } else {
863 // We start the swipe and snap back in the same frame, we don't want any animation
864 mDragAnimPendingChildren.remove(animView);
865 }
Mady Mellor7a9b2a62016-03-23 07:41:47 -0700866 if (mCurrIconRow != null && targetLeft == 0) {
867 mCurrIconRow.resetState();
868 mCurrIconRow = null;
Mady Mellor4b80b102016-01-22 08:03:58 -0800869 }
Selim Cinek67b22602014-03-10 15:40:16 +0100870 }
871
Adrian Roos5d9cc662014-05-28 17:08:13 +0200872 @Override
873 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700874 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700875 mScrimController.setTopHeadsUpDragAmount(animView,
Winsonbde852d2016-04-15 19:06:54 -0700876 Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f));
Selim Cinekaac93252015-04-14 20:04:12 -0700877 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800878 return true; // Don't fade out the notification
Adrian Roos5d9cc662014-05-28 17:08:13 +0200879 }
880
Mady Mellorb0a82462016-04-30 17:31:02 -0700881 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100882 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700883 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100884 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200885 mAmbientState.onBeginDrag(v);
Selim Cinek33223572016-02-19 19:32:22 -0800886 updateContinuousShadowDrawing();
Selim Cinek131c1e22015-05-11 19:04:49 -0700887 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200888 mDragAnimPendingChildren.add(v);
889 mNeedsAnimation = true;
890 }
Selim Cinekeb973562014-05-02 17:07:49 +0200891 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100892 }
893
Selim Cinek684a4422015-04-15 16:18:39 -0700894 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700895 if (v instanceof ExpandableNotificationRow) {
896 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700897 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700898 }
899 return false;
900 }
901
902 private boolean isHeadsUp(View v) {
903 if (v instanceof ExpandableNotificationRow) {
904 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
905 return row.isHeadsUp();
906 }
907 return false;
908 }
909
Mady Mellorb0a82462016-04-30 17:31:02 -0700910 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100911 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700912 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100913 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100914 }
915
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700916 @Override
917 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700918 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700919 }
920
Mady Mellor4b80b102016-01-22 08:03:58 -0800921 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100922 public View getChildAtPosition(MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800923 View child = getChildAtPosition(ev.getX(), ev.getY());
924 if (child instanceof ExpandableNotificationRow) {
925 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
926 ExpandableNotificationRow parent = row.getNotificationParent();
Selim Cinek43d30f02016-03-04 10:51:32 -0800927 if (parent != null && parent.areChildrenExpanded()
Selim Cinek38d429f2016-06-03 11:46:56 -0700928 && (parent.areGutsExposed()
929 || mGearExposedView == parent
Selim Cinek43d30f02016-03-04 10:51:32 -0800930 || (parent.getNotificationChildren().size() == 1
931 && parent.isClearable()))) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800932 // In this case the group is expanded and showing the gear for the
933 // group, further interaction should apply to the group, not any
Selim Cinek43d30f02016-03-04 10:51:32 -0800934 // child notifications so we use the parent of the child. We also do the same
935 // if we only have a single child.
936 child = parent;
Mady Mellor4b80b102016-01-22 08:03:58 -0800937 }
938 }
939 return child;
Selim Cinek67b22602014-03-10 15:40:16 +0100940 }
941
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100942 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
943 getLocationOnScreen(mTempInt2);
944 float localTouchY = touchY - mTempInt2[1];
945
946 ExpandableView closestChild = null;
947 float minDist = Float.MAX_VALUE;
948
949 // find the view closest to the location, accounting for GONE views
950 final int count = getChildCount();
951 for (int childIdx = 0; childIdx < count; childIdx++) {
952 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
953 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800954 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100955 continue;
956 }
957 float childTop = slidingChild.getTranslationY();
958 float top = childTop + slidingChild.getClipTopAmount();
959 float bottom = childTop + slidingChild.getActualHeight();
960
961 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
962 if (dist < minDist) {
963 closestChild = slidingChild;
964 minDist = dist;
965 }
966 }
967 return closestChild;
968 }
969
Mady Mellorb0a82462016-04-30 17:31:02 -0700970 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200971 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100972 getLocationOnScreen(mTempInt2);
973 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100974 }
975
Mady Mellorb0a82462016-04-30 17:31:02 -0700976 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200977 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100978 // find the view under the pointer, accounting for GONE views
979 final int count = getChildCount();
980 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200981 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100982 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800983 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100984 continue;
985 }
Selim Cinek89faff12014-06-19 16:29:04 -0700986 float childTop = slidingChild.getTranslationY();
987 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200988 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200989
990 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
991 // camera affordance).
992 int left = 0;
993 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100994
995 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100996 if (slidingChild instanceof ExpandableNotificationRow) {
997 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700998 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -0800999 && mHeadsUpManager.getTopEntry().entry.row != row
1000 && mGroupManager.getGroupSummary(
1001 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
1002 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001003 continue;
1004 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001005 return row.getViewAtPosition(touchY - childTop);
1006 }
Selim Cinek67b22602014-03-10 15:40:16 +01001007 return slidingChild;
1008 }
1009 }
1010 return null;
1011 }
1012
Mady Mellorb0a82462016-04-30 17:31:02 -07001013 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001014 public boolean canChildBeExpanded(View v) {
1015 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -07001016 && ((ExpandableNotificationRow) v).isExpandable()
Selim Cinek38d429f2016-06-03 11:46:56 -07001017 && !((ExpandableNotificationRow) v).areGutsExposed()
Selim Cinek166f7c42016-03-03 16:21:37 -08001018 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
Selim Cinek67b22602014-03-10 15:40:16 +01001019 }
1020
Chris Wren6abeeb92016-05-26 14:44:38 -04001021 /* Only ever called as a consequence of an expansion gesture in the shade. */
Mady Mellorb0a82462016-04-30 17:31:02 -07001022 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001023 public void setUserExpandedChild(View v, boolean userExpanded) {
1024 if (v instanceof ExpandableNotificationRow) {
Chris Wren6abeeb92016-05-26 14:44:38 -04001025 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1026 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
1027 row.onExpandedByGesture(userExpanded);
Selim Cinek67b22602014-03-10 15:40:16 +01001028 }
1029 }
1030
Mady Mellorb0a82462016-04-30 17:31:02 -07001031 @Override
1032 public void setExpansionCancelled(View v) {
1033 if (v instanceof ExpandableNotificationRow) {
1034 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
1035 }
1036 }
1037
1038 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001039 public void setUserLockedChild(View v, boolean userLocked) {
1040 if (v instanceof ExpandableNotificationRow) {
1041 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
1042 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001043 removeLongPressCallback();
1044 requestDisallowInterceptTouchEvent(true);
1045 }
1046
1047 @Override
1048 public void expansionStateChanged(boolean isExpanding) {
1049 mExpandingNotification = isExpanding;
1050 if (!mExpandedInThisMotion) {
1051 mMaxScrollAfterExpand = mOwnScrollY;
1052 mExpandedInThisMotion = true;
1053 }
1054 }
1055
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001056 @Override
1057 public int getMaxExpandHeight(ExpandableView view) {
1058 int maxContentHeight = view.getMaxContentHeight();
Selim Cinek3e779842016-10-12 11:34:52 -07001059 if (view.isSummaryWithChildren() && view.getParent() == this) {
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001060 // Faking a measure with the group expanded to simulate how the group would look if
1061 // it was. Doing a calculation here would be highly non-trivial because of the
1062 // algorithm
1063 mGroupExpandedForMeasure = true;
1064 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1065 mGroupManager.toggleGroupExpansion(row.getStatusBarNotification());
1066 row.setForceUnlocked(true);
1067 mAmbientState.setLayoutHeight(mMaxLayoutHeight);
1068 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
1069 mAmbientState.setLayoutHeight(getLayoutHeight());
1070 mGroupManager.toggleGroupExpansion(
1071 row.getStatusBarNotification());
1072 mGroupExpandedForMeasure = false;
1073 row.setForceUnlocked(false);
Selim Cinek3e779842016-10-12 11:34:52 -07001074 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(view);
1075 if (viewState != null) {
1076 // The view could have been removed
1077 return Math.min(viewState.height, maxContentHeight);
1078 }
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001079 }
1080 return maxContentHeight;
1081 }
1082
Selim Cinek1408eb52014-06-02 14:45:38 +02001083 public void setScrollingEnabled(boolean enable) {
1084 mScrollingEnabled = enable;
1085 }
1086
Adrian Roos181385c2016-05-05 17:45:44 -04001087 @Override
1088 public void lockScrollTo(View v) {
1089 if (mForcedScroll == v) {
1090 return;
1091 }
1092 mForcedScroll = v;
1093 scrollTo(v);
1094 }
1095
1096 @Override
Ricky Waicd35def2016-05-03 11:07:07 +01001097 public boolean scrollTo(View v) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001098 ExpandableView expandableView = (ExpandableView) v;
Adrian Roos4a579672016-05-24 16:54:37 -07001099 int positionInLinearLayout = getPositionInLinearLayout(v);
1100 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1101 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Ricky Waicd35def2016-05-03 11:07:07 +01001102
Adrian Roos4a579672016-05-24 16:54:37 -07001103 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1104 // that it is not visible anymore.
1105 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001106 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1107 mDontReportNextOverScroll = true;
1108 postInvalidateOnAnimation();
Ricky Waicd35def2016-05-03 11:07:07 +01001109 return true;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001110 }
Ricky Waicd35def2016-05-03 11:07:07 +01001111 return false;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001112 }
1113
Adrian Roos181385c2016-05-05 17:45:44 -04001114 /**
1115 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
1116 * the IME.
1117 */
1118 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1119 return positionInLinearLayout + v.getIntrinsicHeight() +
1120 getImeInset() - getHeight() + getTopPadding();
1121 }
1122
Adrian Roos5153d4a2016-03-22 10:01:56 -07001123 @Override
1124 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Selim Cineka424c502016-04-05 13:07:54 -07001125 mBottomInset = insets.getSystemWindowInsetBottom();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001126
1127 int range = getScrollRange();
1128 if (mOwnScrollY > range) {
1129 // HACK: We're repeatedly getting staggered insets here while the IME is
1130 // animating away. To work around that we'll wait until things have settled.
1131 removeCallbacks(mReclamp);
1132 postDelayed(mReclamp, 50);
Adrian Roos4a579672016-05-24 16:54:37 -07001133 } else if (mForcedScroll != null) {
1134 // The scroll was requested before we got the actual inset - in case we need
1135 // to scroll up some more do so now.
1136 scrollTo(mForcedScroll);
Adrian Roos5153d4a2016-03-22 10:01:56 -07001137 }
1138 return insets;
1139 }
1140
1141 private Runnable mReclamp = new Runnable() {
1142 @Override
1143 public void run() {
1144 int range = getScrollRange();
1145 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1146 mDontReportNextOverScroll = true;
1147 mDontClampNextScroll = true;
1148 postInvalidateOnAnimation();
1149 }
1150 };
1151
Selim Cinek1408eb52014-06-02 14:45:38 +02001152 public void setExpandingEnabled(boolean enable) {
1153 mExpandHelper.setEnabled(enable);
1154 }
1155
1156 private boolean isScrollingEnabled() {
1157 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +01001158 }
1159
Mady Mellorb0a82462016-04-30 17:31:02 -07001160 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001161 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -07001162 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +01001163 }
1164
Selim Cinek19c8c702014-08-25 22:09:19 +02001165 @Override
1166 public boolean isAntiFalsingNeeded() {
Selim Cinekbc243a92016-09-27 16:35:13 -07001167 return onKeyguard();
1168 }
1169
1170 private boolean onKeyguard() {
Selim Cinekcb2b6732014-09-05 16:17:22 +02001171 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +02001172 }
1173
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001174 private void setSwipingInProgress(boolean isSwiped) {
1175 mSwipingInProgress = isSwiped;
1176 if(isSwiped) {
1177 requestDisallowInterceptTouchEvent(true);
1178 }
Selim Cinek67b22602014-03-10 15:40:16 +01001179 }
1180
1181 @Override
1182 protected void onConfigurationChanged(Configuration newConfig) {
1183 super.onConfigurationChanged(newConfig);
1184 float densityScale = getResources().getDisplayMetrics().density;
1185 mSwipeHelper.setDensityScale(densityScale);
1186 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1187 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1188 initView(getContext());
1189 }
1190
Dan Sandlereceda3d2014-07-21 15:35:01 -04001191 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07001192 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1193 true /* isDismissAll */);
Selim Cinek67b22602014-03-10 15:40:16 +01001194 }
1195
Mady Mellor86889c22016-04-18 16:37:06 -07001196 public void snapViewIfNeeded(ExpandableNotificationRow child) {
dongwan0605.kim30637e42016-03-02 17:16:47 +09001197 boolean animate = mIsExpanded || isPinnedHeadsUp(child);
Mady Mellor86889c22016-04-18 16:37:06 -07001198 // If the child is showing the gear to go to settings, snap to that
1199 float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
1200 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
dongwan0605.kim30637e42016-03-02 17:16:47 +09001201 }
1202
Selim Cinek67b22602014-03-10 15:40:16 +01001203 @Override
1204 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001205 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1206 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001207 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001208 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001209 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001210 if (isCancelOrUp) {
1211 mExpandHelper.onlyObserveMovements(false);
1212 }
1213 boolean wasExpandingBefore = mExpandingNotification;
1214 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +02001215 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
1216 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001217 dispatchDownEventToScroller(ev);
1218 }
1219 }
Selim Cinek67b22602014-03-10 15:40:16 +01001220 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -07001221 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
1222 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001223 scrollerWantsIt = onScrollTouch(ev);
1224 }
1225 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001226 if (!mIsBeingDragged
1227 && !mExpandingNotification
1228 && !mExpandedInThisMotion
Adrian Roosfa139752016-04-27 09:59:08 -07001229 && !mOnlyScrollingInThisMotion
1230 && !mDisallowDismissInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001231 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
1232 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001233 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
1234 }
1235
1236 private void dispatchDownEventToScroller(MotionEvent ev) {
1237 MotionEvent downEvent = MotionEvent.obtain(ev);
1238 downEvent.setAction(MotionEvent.ACTION_DOWN);
1239 onScrollTouch(downEvent);
1240 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +01001241 }
1242
1243 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001244 if (!isScrollingEnabled()) {
1245 return false;
1246 }
Selim Cinekecfc6b52016-09-27 16:31:21 -07001247 if (ev.getY() < mQsContainer.getBottom() && !mIsBeingDragged) {
Jason Monk16ac3772016-02-10 15:39:21 -05001248 return false;
1249 }
Adrian Roos181385c2016-05-05 17:45:44 -04001250 mForcedScroll = null;
Selim Cinek67b22602014-03-10 15:40:16 +01001251 initVelocityTrackerIfNotExists();
1252 mVelocityTracker.addMovement(ev);
1253
1254 final int action = ev.getAction();
1255
1256 switch (action & MotionEvent.ACTION_MASK) {
1257 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001258 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +01001259 return false;
1260 }
1261 boolean isBeingDragged = !mScroller.isFinished();
1262 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +01001263
1264 /*
1265 * If being flinged and user touches, stop the fling. isFinished
1266 * will be false if being flinged.
1267 */
1268 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001269 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +01001270 }
1271
1272 // Remember where the motion event started
1273 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001274 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001275 mActivePointerId = ev.getPointerId(0);
1276 break;
1277 }
1278 case MotionEvent.ACTION_MOVE:
1279 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1280 if (activePointerIndex == -1) {
1281 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1282 break;
1283 }
1284
1285 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001286 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001287 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001288 final int xDiff = Math.abs(x - mDownX);
1289 final int yDiff = Math.abs(deltaY);
1290 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001291 setIsBeingDragged(true);
1292 if (deltaY > 0) {
1293 deltaY -= mTouchSlop;
1294 } else {
1295 deltaY += mTouchSlop;
1296 }
1297 }
1298 if (mIsBeingDragged) {
1299 // Scroll to follow the motion event
1300 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001301 int range = getScrollRange();
1302 if (mExpandedInThisMotion) {
1303 range = Math.min(range, mMaxScrollAfterExpand);
1304 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001305
1306 float scrollAmount;
1307 if (deltaY < 0) {
1308 scrollAmount = overScrollDown(deltaY);
1309 } else {
1310 scrollAmount = overScrollUp(deltaY, range);
1311 }
Selim Cinek67b22602014-03-10 15:40:16 +01001312
1313 // Calling overScrollBy will call onOverScrolled, which
1314 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001315 if (scrollAmount != 0.0f) {
1316 // The scrolling motion could not be compensated with the
1317 // existing overScroll, we have to scroll the view
1318 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1319 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +01001320 }
Selim Cinek67b22602014-03-10 15:40:16 +01001321 }
1322 break;
1323 case MotionEvent.ACTION_UP:
1324 if (mIsBeingDragged) {
1325 final VelocityTracker velocityTracker = mVelocityTracker;
1326 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1327 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1328
Selim Cinek1408eb52014-06-02 14:45:38 +02001329 if (shouldOverScrollFling(initialVelocity)) {
1330 onOverScrollFling(true, initialVelocity);
1331 } else {
1332 if (getChildCount() > 0) {
1333 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1334 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1335 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1336 fling(-initialVelocity);
1337 } else {
1338 onOverScrollFling(false, initialVelocity);
1339 }
1340 } else {
1341 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1342 getScrollRange())) {
1343 postInvalidateOnAnimation();
1344 }
Selim Cinek67b22602014-03-10 15:40:16 +01001345 }
1346 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001347 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001348
1349 mActivePointerId = INVALID_POINTER;
1350 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +01001351 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001352
Selim Cinek67b22602014-03-10 15:40:16 +01001353 break;
1354 case MotionEvent.ACTION_CANCEL:
1355 if (mIsBeingDragged && getChildCount() > 0) {
1356 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1357 postInvalidateOnAnimation();
1358 }
1359 mActivePointerId = INVALID_POINTER;
1360 endDrag();
1361 }
1362 break;
1363 case MotionEvent.ACTION_POINTER_DOWN: {
1364 final int index = ev.getActionIndex();
1365 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +02001366 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +01001367 mActivePointerId = ev.getPointerId(index);
1368 break;
1369 }
1370 case MotionEvent.ACTION_POINTER_UP:
1371 onSecondaryPointerUp(ev);
1372 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +02001373 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +01001374 break;
1375 }
1376 return true;
1377 }
1378
Selim Cinek1408eb52014-06-02 14:45:38 +02001379 private void onOverScrollFling(boolean open, int initialVelocity) {
1380 if (mOverscrollTopChangedListener != null) {
1381 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1382 }
1383 mDontReportNextOverScroll = true;
1384 setOverScrollAmount(0.0f, true, false);
1385 }
1386
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001387 /**
1388 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1389 *
1390 * @param deltaY The amount to scroll upwards, has to be positive.
1391 * @return The amount of scrolling to be performed by the scroller,
1392 * not handled by the overScroll amount.
1393 */
1394 private float overScrollUp(int deltaY, int range) {
1395 deltaY = Math.max(deltaY, 0);
1396 float currentTopAmount = getCurrentOverScrollAmount(true);
1397 float newTopAmount = currentTopAmount - deltaY;
1398 if (currentTopAmount > 0) {
1399 setOverScrollAmount(newTopAmount, true /* onTop */,
1400 false /* animate */);
1401 }
1402 // Top overScroll might not grab all scrolling motion,
1403 // we have to scroll as well.
1404 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1405 float newScrollY = mOwnScrollY + scrollAmount;
1406 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001407 if (!mExpandedInThisMotion) {
1408 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1409 // We overScroll on the top
1410 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1411 false /* onTop */,
1412 false /* animate */);
1413 }
Selim Cinekef406062016-09-29 17:33:13 -07001414 setOwnScrollY(range);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001415 scrollAmount = 0.0f;
1416 }
1417 return scrollAmount;
1418 }
1419
1420 /**
1421 * Perform a scroll downward and adapt the overscroll amounts accordingly
1422 *
1423 * @param deltaY The amount to scroll downwards, has to be negative.
1424 * @return The amount of scrolling to be performed by the scroller,
1425 * not handled by the overScroll amount.
1426 */
1427 private float overScrollDown(int deltaY) {
1428 deltaY = Math.min(deltaY, 0);
1429 float currentBottomAmount = getCurrentOverScrollAmount(false);
1430 float newBottomAmount = currentBottomAmount + deltaY;
1431 if (currentBottomAmount > 0) {
1432 setOverScrollAmount(newBottomAmount, false /* onTop */,
1433 false /* animate */);
1434 }
1435 // Bottom overScroll might not grab all scrolling motion,
1436 // we have to scroll as well.
1437 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1438 float newScrollY = mOwnScrollY + scrollAmount;
1439 if (newScrollY < 0) {
1440 float currentTopPixels = getCurrentOverScrolledPixels(true);
1441 // We overScroll on the top
1442 setOverScrolledPixels(currentTopPixels - newScrollY,
1443 true /* onTop */,
1444 false /* animate */);
Selim Cinekef406062016-09-29 17:33:13 -07001445 setOwnScrollY(0);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001446 scrollAmount = 0.0f;
1447 }
1448 return scrollAmount;
1449 }
1450
Selim Cinek67b22602014-03-10 15:40:16 +01001451 private void onSecondaryPointerUp(MotionEvent ev) {
1452 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1453 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1454 final int pointerId = ev.getPointerId(pointerIndex);
1455 if (pointerId == mActivePointerId) {
1456 // This was our active pointer going up. Choose a new
1457 // active pointer and adjust accordingly.
1458 // TODO: Make this decision more intelligent.
1459 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1460 mLastMotionY = (int) ev.getY(newPointerIndex);
1461 mActivePointerId = ev.getPointerId(newPointerIndex);
1462 if (mVelocityTracker != null) {
1463 mVelocityTracker.clear();
1464 }
1465 }
1466 }
1467
1468 private void initVelocityTrackerIfNotExists() {
1469 if (mVelocityTracker == null) {
1470 mVelocityTracker = VelocityTracker.obtain();
1471 }
1472 }
1473
1474 private void recycleVelocityTracker() {
1475 if (mVelocityTracker != null) {
1476 mVelocityTracker.recycle();
1477 mVelocityTracker = null;
1478 }
1479 }
1480
1481 private void initOrResetVelocityTracker() {
1482 if (mVelocityTracker == null) {
1483 mVelocityTracker = VelocityTracker.obtain();
1484 } else {
1485 mVelocityTracker.clear();
1486 }
1487 }
1488
Ricky Waicd35def2016-05-03 11:07:07 +01001489 public void setFinishScrollingCallback(Runnable runnable) {
1490 mFinishScrollingCallback = runnable;
1491 }
1492
Selim Cinek67b22602014-03-10 15:40:16 +01001493 @Override
1494 public void computeScroll() {
1495 if (mScroller.computeScrollOffset()) {
1496 // This is called at drawing time by ViewGroup.
1497 int oldX = mScrollX;
1498 int oldY = mOwnScrollY;
1499 int x = mScroller.getCurrX();
1500 int y = mScroller.getCurrY();
1501
1502 if (oldX != x || oldY != y) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001503 int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001504 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1505 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001506 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001507 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1508 }
1509 }
Selim Cinek67b22602014-03-10 15:40:16 +01001510
Adrian Roos5153d4a2016-03-22 10:01:56 -07001511 if (mDontClampNextScroll) {
1512 range = Math.max(range, oldY);
1513 }
Selim Cinek67b22602014-03-10 15:40:16 +01001514 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001515 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001516 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001517 }
1518
1519 // Keep on drawing until the animation has finished.
1520 postInvalidateOnAnimation();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001521 } else {
1522 mDontClampNextScroll = false;
Ricky Waicd35def2016-05-03 11:07:07 +01001523 if (mFinishScrollingCallback != null) {
1524 mFinishScrollingCallback.run();
1525 }
Selim Cinek67b22602014-03-10 15:40:16 +01001526 }
1527 }
1528
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001529 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001530 protected boolean overScrollBy(int deltaX, int deltaY,
1531 int scrollX, int scrollY,
1532 int scrollRangeX, int scrollRangeY,
1533 int maxOverScrollX, int maxOverScrollY,
1534 boolean isTouchEvent) {
1535
1536 int newScrollY = scrollY + deltaY;
1537
1538 final int top = -maxOverScrollY;
1539 final int bottom = maxOverScrollY + scrollRangeY;
1540
1541 boolean clampedY = false;
1542 if (newScrollY > bottom) {
1543 newScrollY = bottom;
1544 clampedY = true;
1545 } else if (newScrollY < top) {
1546 newScrollY = top;
1547 clampedY = true;
1548 }
1549
1550 onOverScrolled(0, newScrollY, false, clampedY);
1551
1552 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001553 }
1554
1555 /**
1556 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1557 * overscroll effect based on numPixels. By default this will also cancel animations on the
1558 * same overScroll edge.
1559 *
1560 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1561 * the rubber-banding logic.
1562 * @param onTop Should the effect be applied on top of the scroller.
1563 * @param animate Should an animation be performed.
1564 */
1565 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001566 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001567 }
1568
1569 /**
1570 * Set the effective overScroll amount which will be directly reflected in the layout.
1571 * By default this will also cancel animations on the same overScroll edge.
1572 *
1573 * @param amount The amount to overScroll by.
1574 * @param onTop Should the effect be applied on top of the scroller.
1575 * @param animate Should an animation be performed.
1576 */
1577 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1578 setOverScrollAmount(amount, onTop, animate, true);
1579 }
1580
1581 /**
1582 * Set the effective overScroll amount which will be directly reflected in the layout.
1583 *
1584 * @param amount The amount to overScroll by.
1585 * @param onTop Should the effect be applied on top of the scroller.
1586 * @param animate Should an animation be performed.
1587 * @param cancelAnimators Should running animations be cancelled.
1588 */
1589 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1590 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001591 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1592 }
1593
1594 /**
1595 * Set the effective overScroll amount which will be directly reflected in the layout.
1596 *
1597 * @param amount The amount to overScroll by.
1598 * @param onTop Should the effect be applied on top of the scroller.
1599 * @param animate Should an animation be performed.
1600 * @param cancelAnimators Should running animations be cancelled.
1601 * @param isRubberbanded The value which will be passed to
1602 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1603 */
1604 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1605 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001606 if (cancelAnimators) {
1607 mStateAnimator.cancelOverScrollAnimators(onTop);
1608 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001609 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001610 }
1611
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001612 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1613 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001614 amount = Math.max(0, amount);
1615 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001616 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001617 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001618 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001619 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001620 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001621 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001622 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001623 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001624 }
1625 }
1626
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001627 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001628 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1629 if (mDontReportNextOverScroll) {
1630 mDontReportNextOverScroll = false;
1631 return;
1632 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001633 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001634 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001635 }
1636 }
1637
1638 public void setOverscrollTopChangedListener(
1639 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1640 mOverscrollTopChangedListener = overscrollTopChangedListener;
1641 }
1642
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001643 public float getCurrentOverScrollAmount(boolean top) {
1644 return mAmbientState.getOverScrollAmount(top);
1645 }
1646
1647 public float getCurrentOverScrolledPixels(boolean top) {
1648 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1649 }
1650
1651 private void setOverScrolledPixels(float amount, boolean onTop) {
1652 if (onTop) {
1653 mOverScrolledTopPixels = amount;
1654 } else {
1655 mOverScrolledBottomPixels = amount;
1656 }
1657 }
1658
Selim Cinek319bdc42014-05-01 23:01:58 +02001659 private void customScrollTo(int y) {
Selim Cinekef406062016-09-29 17:33:13 -07001660 setOwnScrollY(y);
Selim Cinek3af00cf2014-05-07 17:27:26 +02001661 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001662 }
1663
1664 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001665 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001666 // Treat animating scrolls differently; see #computeScroll() for why.
1667 if (!mScroller.isFinished()) {
1668 final int oldX = mScrollX;
1669 final int oldY = mOwnScrollY;
1670 mScrollX = scrollX;
Selim Cinekef406062016-09-29 17:33:13 -07001671 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01001672 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001673 springBack();
1674 } else {
1675 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1676 invalidateParentIfNeeded();
1677 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001678 float overScrollTop = getCurrentOverScrollAmount(true);
1679 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001680 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001681 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001682 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001683 }
Selim Cinek67b22602014-03-10 15:40:16 +01001684 }
Selim Cinek67b22602014-03-10 15:40:16 +01001685 } else {
1686 customScrollTo(scrollY);
1687 scrollTo(scrollX, mScrollY);
1688 }
1689 }
1690
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001691 private void springBack() {
1692 int scrollRange = getScrollRange();
1693 boolean overScrolledTop = mOwnScrollY <= 0;
1694 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1695 if (overScrolledTop || overScrolledBottom) {
1696 boolean onTop;
1697 float newAmount;
1698 if (overScrolledTop) {
1699 onTop = true;
1700 newAmount = -mOwnScrollY;
Selim Cinekef406062016-09-29 17:33:13 -07001701 setOwnScrollY(0);
Selim Cinek1408eb52014-06-02 14:45:38 +02001702 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001703 } else {
1704 onTop = false;
1705 newAmount = mOwnScrollY - scrollRange;
Selim Cinekef406062016-09-29 17:33:13 -07001706 setOwnScrollY(scrollRange);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001707 }
1708 setOverScrollAmount(newAmount, onTop, false);
1709 setOverScrollAmount(0.0f, onTop, true);
1710 mScroller.forceFinished(true);
1711 }
1712 }
1713
Selim Cinek67b22602014-03-10 15:40:16 +01001714 private int getScrollRange() {
Selim Cineka424c502016-04-05 13:07:54 -07001715 int contentHeight = getContentHeight();
1716 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1717 + mBottomStackSlowDownHeight);
1718 int imeInset = getImeInset();
1719 scrollRange += Math.min(imeInset, Math.max(0,
1720 getContentHeight() - (getHeight() - imeInset)));
1721 return scrollRange;
1722 }
1723
1724 private int getImeInset() {
1725 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
Selim Cinek67b22602014-03-10 15:40:16 +01001726 }
1727
Selim Cinek343e6e22014-04-11 21:23:30 +02001728 /**
1729 * @return the first child which has visibility unequal to GONE
1730 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001731 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001732 int childCount = getChildCount();
1733 for (int i = 0; i < childCount; i++) {
1734 View child = getChildAt(i);
1735 if (child.getVisibility() != View.GONE) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001736 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001737 }
1738 }
1739 return null;
1740 }
1741
Selim Cinek4a1ac842014-05-01 15:51:58 +02001742 /**
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001743 * @return the child before the given view which has visibility unequal to GONE
1744 */
1745 public ExpandableView getViewBeforeView(ExpandableView view) {
1746 ExpandableView previousView = null;
1747 int childCount = getChildCount();
1748 for (int i = 0; i < childCount; i++) {
1749 View child = getChildAt(i);
1750 if (child == view) {
1751 return previousView;
1752 }
1753 if (child.getVisibility() != View.GONE) {
1754 previousView = (ExpandableView) child;
1755 }
1756 }
1757 return null;
1758 }
1759
1760 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001761 * @return The first child which has visibility unequal to GONE which is currently below the
1762 * given translationY or equal to it.
1763 */
1764 private View getFirstChildBelowTranlsationY(float translationY) {
1765 int childCount = getChildCount();
1766 for (int i = 0; i < childCount; i++) {
1767 View child = getChildAt(i);
1768 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1769 return child;
1770 }
1771 }
1772 return null;
1773 }
1774
1775 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001776 * @return the last child which has visibility unequal to GONE
1777 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001778 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001779 int childCount = getChildCount();
1780 for (int i = childCount - 1; i >= 0; i--) {
1781 View child = getChildAt(i);
1782 if (child.getVisibility() != View.GONE) {
1783 return child;
1784 }
1785 }
1786 return null;
1787 }
1788
Jorim Jaggi069cd032014-05-15 03:09:01 +02001789 /**
1790 * @return the number of children which have visibility unequal to GONE
1791 */
1792 public int getNotGoneChildCount() {
1793 int childCount = getChildCount();
1794 int count = 0;
1795 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001796 ExpandableView child = (ExpandableView) getChildAt(i);
1797 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001798 count++;
1799 }
1800 }
1801 return count;
1802 }
1803
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001804 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001805 return mContentHeight;
1806 }
1807
1808 private void updateContentHeight() {
1809 int height = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08001810 float previousIncreasedAmount = 0.0f;
Selim Cinek67b22602014-03-10 15:40:16 +01001811 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08001812 ExpandableView expandableView = (ExpandableView) getChildAt(i);
1813 if (expandableView.getVisibility() != View.GONE) {
Selim Cinek42357e02016-02-24 18:48:01 -08001814 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001815 if (height != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08001816 height += (int) NotificationUtils.interpolate(
1817 mPaddingBetweenElements,
1818 mIncreasedPaddingBetweenElements,
1819 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Jorim Jaggid4a57442014-04-10 02:45:55 +02001820 }
Selim Cinek42357e02016-02-24 18:48:01 -08001821 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08001822 height += expandableView.getIntrinsicHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001823 }
1824 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001825 mContentHeight = height + mTopPadding;
Selim Cinekc22fff62016-05-20 12:44:30 -07001826 updateScrollability();
1827 }
1828
1829 private void updateScrollability() {
1830 boolean scrollable = getScrollRange() > 0;
1831 if (scrollable != mScrollable) {
1832 mScrollable = scrollable;
1833 setFocusable(scrollable);
Selim Cinekef406062016-09-29 17:33:13 -07001834 updateForwardAndBackwardScrollability();
1835 }
1836 }
1837
1838 private void updateForwardAndBackwardScrollability() {
1839 boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
1840 boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
1841 boolean changed = forwardScrollable != mForwardScrollable
1842 || backwardsScrollable != mBackwardScrollable;
1843 mForwardScrollable = forwardScrollable;
1844 mBackwardScrollable = backwardsScrollable;
1845 if (changed) {
1846 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Selim Cinekc22fff62016-05-20 12:44:30 -07001847 }
Selim Cinek67b22602014-03-10 15:40:16 +01001848 }
1849
Selim Cinek6811d722016-01-19 17:53:12 -08001850 private void updateBackground() {
1851 if (mAmbientState.isDark()) {
1852 return;
1853 }
1854 updateBackgroundBounds();
1855 if (!mCurrentBounds.equals(mBackgroundBounds)) {
Selim Cinek54680902016-10-19 16:49:44 -07001856 boolean animate = mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom
1857 || areBoundsAnimating();
1858 if (!isExpanded()) {
1859 abortBackgroundAnimators();
1860 animate = false;
1861 }
1862 if (animate) {
Selim Cinek614576e2016-01-20 10:54:09 -08001863 startBackgroundAnimation();
1864 } else {
1865 mCurrentBounds.set(mBackgroundBounds);
1866 applyCurrentBackgroundBounds();
1867 }
1868 } else {
Selim Cinek54680902016-10-19 16:49:44 -07001869 abortBackgroundAnimators();
Selim Cinek6811d722016-01-19 17:53:12 -08001870 }
Selim Cinek614576e2016-01-20 10:54:09 -08001871 mAnimateNextBackgroundBottom = false;
1872 mAnimateNextBackgroundTop = false;
1873 }
1874
Selim Cinek54680902016-10-19 16:49:44 -07001875 private void abortBackgroundAnimators() {
1876 if (mBottomAnimator != null) {
1877 mBottomAnimator.cancel();
1878 }
1879 if (mTopAnimator != null) {
1880 mTopAnimator.cancel();
1881 }
1882 }
1883
Selim Cinek614576e2016-01-20 10:54:09 -08001884 private boolean areBoundsAnimating() {
1885 return mBottomAnimator != null || mTopAnimator != null;
1886 }
1887
1888 private void startBackgroundAnimation() {
Selim Cinek01e40192016-05-12 14:57:22 -07001889 // left and right are always instantly applied
1890 mCurrentBounds.left = mBackgroundBounds.left;
1891 mCurrentBounds.right = mBackgroundBounds.right;
Selim Cinek614576e2016-01-20 10:54:09 -08001892 startBottomAnimation();
1893 startTopAnimation();
1894 }
1895
1896 private void startTopAnimation() {
1897 int previousEndValue = mEndAnimationRect.top;
1898 int newEndValue = mBackgroundBounds.top;
1899 ObjectAnimator previousAnimator = mTopAnimator;
1900 if (previousAnimator != null && previousEndValue == newEndValue) {
1901 return;
1902 }
1903 if (!mAnimateNextBackgroundTop) {
1904 // just a local update was performed
1905 if (previousAnimator != null) {
1906 // we need to increase all animation keyframes of the previous animator by the
1907 // relative change to the end value
1908 int previousStartValue = mStartAnimationRect.top;
1909 PropertyValuesHolder[] values = previousAnimator.getValues();
1910 values[0].setIntValues(previousStartValue, newEndValue);
1911 mStartAnimationRect.top = previousStartValue;
1912 mEndAnimationRect.top = newEndValue;
1913 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1914 return;
1915 } else {
1916 // no new animation needed, let's just apply the value
1917 setBackgroundTop(newEndValue);
1918 return;
1919 }
1920 }
1921 if (previousAnimator != null) {
1922 previousAnimator.cancel();
1923 }
1924 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
1925 mCurrentBounds.top, newEndValue);
1926 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1927 animator.setInterpolator(interpolator);
1928 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1929 // remove the tag when the animation is finished
1930 animator.addListener(new AnimatorListenerAdapter() {
1931 @Override
1932 public void onAnimationEnd(Animator animation) {
1933 mStartAnimationRect.top = -1;
1934 mEndAnimationRect.top = -1;
1935 mTopAnimator = null;
1936 }
1937 });
1938 animator.start();
1939 mStartAnimationRect.top = mCurrentBounds.top;
1940 mEndAnimationRect.top = newEndValue;
1941 mTopAnimator = animator;
1942 }
1943
1944 private void startBottomAnimation() {
1945 int previousStartValue = mStartAnimationRect.bottom;
1946 int previousEndValue = mEndAnimationRect.bottom;
1947 int newEndValue = mBackgroundBounds.bottom;
1948 ObjectAnimator previousAnimator = mBottomAnimator;
1949 if (previousAnimator != null && previousEndValue == newEndValue) {
1950 return;
1951 }
1952 if (!mAnimateNextBackgroundBottom) {
1953 // just a local update was performed
1954 if (previousAnimator != null) {
1955 // we need to increase all animation keyframes of the previous animator by the
1956 // relative change to the end value
1957 PropertyValuesHolder[] values = previousAnimator.getValues();
1958 values[0].setIntValues(previousStartValue, newEndValue);
1959 mStartAnimationRect.bottom = previousStartValue;
1960 mEndAnimationRect.bottom = newEndValue;
1961 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1962 return;
1963 } else {
1964 // no new animation needed, let's just apply the value
1965 setBackgroundBottom(newEndValue);
1966 return;
1967 }
1968 }
1969 if (previousAnimator != null) {
1970 previousAnimator.cancel();
1971 }
1972 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
1973 mCurrentBounds.bottom, newEndValue);
1974 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1975 animator.setInterpolator(interpolator);
1976 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1977 // remove the tag when the animation is finished
1978 animator.addListener(new AnimatorListenerAdapter() {
1979 @Override
1980 public void onAnimationEnd(Animator animation) {
1981 mStartAnimationRect.bottom = -1;
1982 mEndAnimationRect.bottom = -1;
1983 mBottomAnimator = null;
1984 }
1985 });
1986 animator.start();
1987 mStartAnimationRect.bottom = mCurrentBounds.bottom;
1988 mEndAnimationRect.bottom = newEndValue;
1989 mBottomAnimator = animator;
1990 }
1991
1992 private void setBackgroundTop(int top) {
1993 mCurrentBounds.top = top;
1994 applyCurrentBackgroundBounds();
1995 }
1996
1997 public void setBackgroundBottom(int bottom) {
1998 mCurrentBounds.bottom = bottom;
1999 applyCurrentBackgroundBounds();
2000 }
2001
2002 private void applyCurrentBackgroundBounds() {
Selim Cinek9bfba9c2016-08-08 15:20:06 -07002003 mScrimController.setExcludedBackgroundArea(
2004 mFadingOut || mParentFadingOut || mAmbientState.isDark() ? null
2005 : mCurrentBounds);
Selim Cinek614576e2016-01-20 10:54:09 -08002006 invalidate();
Selim Cinek6811d722016-01-19 17:53:12 -08002007 }
2008
2009 /**
2010 * Update the background bounds to the new desired bounds
2011 */
2012 private void updateBackgroundBounds() {
Selim Cinek614576e2016-01-20 10:54:09 -08002013 mBackgroundBounds.left = (int) getX();
2014 mBackgroundBounds.right = (int) (getX() + getWidth());
2015 if (!mIsExpanded) {
2016 mBackgroundBounds.top = 0;
2017 mBackgroundBounds.bottom = 0;
Selim Cinek1791f502016-10-07 17:38:03 -04002018 return;
Selim Cinek614576e2016-01-20 10:54:09 -08002019 }
2020 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08002021 int top = 0;
2022 if (firstView != null) {
2023 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
Selim Cinek614576e2016-01-20 10:54:09 -08002024 if (mAnimateNextBackgroundTop
2025 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
2026 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
Selim Cinek6811d722016-01-19 17:53:12 -08002027 // we're ending up at the same location as we are now, lets just skip the animation
2028 top = finalTranslationY;
2029 } else {
2030 top = (int) firstView.getTranslationY();
2031 }
2032 }
Selim Cinek614576e2016-01-20 10:54:09 -08002033 ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08002034 int bottom = 0;
2035 if (lastView != null) {
2036 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
2037 int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
2038 int finalBottom = finalTranslationY + finalHeight;
2039 finalBottom = Math.min(finalBottom, getHeight());
Selim Cinek614576e2016-01-20 10:54:09 -08002040 if (mAnimateNextBackgroundBottom
2041 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
2042 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
Selim Cinek6811d722016-01-19 17:53:12 -08002043 // we're ending up at the same location as we are now, lets just skip the animation
2044 bottom = finalBottom;
2045 } else {
Mady Mellor92b82432016-06-09 17:33:56 -07002046 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
Selim Cinek6811d722016-01-19 17:53:12 -08002047 bottom = Math.min(bottom, getHeight());
2048 }
Selim Cinek3776fe02016-02-04 13:32:43 -08002049 } else {
Selim Cinekd393d5c2016-08-03 16:18:12 -07002050 top = mTopPadding;
Selim Cinek7db42982016-02-02 15:21:41 -08002051 bottom = top;
Selim Cinek6811d722016-01-19 17:53:12 -08002052 }
Selim Cinek3776fe02016-02-04 13:32:43 -08002053 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
Selim Cinekd381bc32016-08-15 12:40:57 -07002054 top = (int) Math.max(mTopPadding + mStackTranslation, top);
Selim Cinek3776fe02016-02-04 13:32:43 -08002055 } else {
2056 // otherwise the animation from the shade to the keyguard will jump as it's maxed
Selim Cinekd381bc32016-08-15 12:40:57 -07002057 top = Math.max(0, top);
Selim Cinek3776fe02016-02-04 13:32:43 -08002058 }
Selim Cinekd381bc32016-08-15 12:40:57 -07002059 mBackgroundBounds.top = top;
Selim Cinek3776fe02016-02-04 13:32:43 -08002060 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
Selim Cinek6811d722016-01-19 17:53:12 -08002061 }
2062
Selim Cinek614576e2016-01-20 10:54:09 -08002063 private ActivatableNotificationView getFirstPinnedHeadsUp() {
2064 int childCount = getChildCount();
2065 for (int i = 0; i < childCount; i++) {
2066 View child = getChildAt(i);
2067 if (child.getVisibility() != View.GONE
2068 && child instanceof ExpandableNotificationRow) {
2069 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2070 if (row.isPinned()) {
2071 return row;
2072 }
2073 }
2074 }
2075 return null;
2076 }
2077
2078 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002079 int childCount = getChildCount();
2080 for (int i = childCount - 1; i >= 0; i--) {
2081 View child = getChildAt(i);
2082 if (child.getVisibility() != View.GONE
2083 && child instanceof ActivatableNotificationView) {
2084 return (ActivatableNotificationView) child;
2085 }
2086 }
2087 return null;
2088 }
2089
Selim Cinek614576e2016-01-20 10:54:09 -08002090 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002091 int childCount = getChildCount();
2092 for (int i = 0; i < childCount; i++) {
2093 View child = getChildAt(i);
2094 if (child.getVisibility() != View.GONE
2095 && child instanceof ActivatableNotificationView) {
2096 return (ActivatableNotificationView) child;
2097 }
2098 }
2099 return null;
2100 }
2101
Selim Cinek67b22602014-03-10 15:40:16 +01002102 /**
2103 * Fling the scroll view
2104 *
2105 * @param velocityY The initial velocity in the Y direction. Positive
2106 * numbers mean that the finger/cursor is moving down the screen,
2107 * which means we want to scroll towards the top.
2108 */
2109 private void fling(int velocityY) {
2110 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02002111 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01002112
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002113 float topAmount = getCurrentOverScrollAmount(true);
2114 float bottomAmount = getCurrentOverScrollAmount(false);
2115 if (velocityY < 0 && topAmount > 0) {
Selim Cinekef406062016-09-29 17:33:13 -07002116 setOwnScrollY(mOwnScrollY - (int) topAmount);
Selim Cinek1408eb52014-06-02 14:45:38 +02002117 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002118 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002119 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002120 * mOverflingDistance + topAmount;
2121 } else if (velocityY > 0 && bottomAmount > 0) {
Selim Cinekef406062016-09-29 17:33:13 -07002122 setOwnScrollY((int) (mOwnScrollY + bottomAmount));
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002123 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002124 mMaxOverScroll = Math.abs(velocityY) / 1000f
2125 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
2126 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002127 } else {
2128 // it will be set once we reach the boundary
2129 mMaxOverScroll = 0.0f;
2130 }
Selim Cinek94ab18c2016-02-25 12:35:51 -08002131 int minScrollY = Math.max(0, scrollRange);
2132 if (mExpandedInThisMotion) {
2133 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2134 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002135 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek94ab18c2016-02-25 12:35:51 -08002136 minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01002137
2138 postInvalidateOnAnimation();
2139 }
2140 }
2141
Selim Cinek1408eb52014-06-02 14:45:38 +02002142 /**
2143 * @return Whether a fling performed on the top overscroll edge lead to the expanded
2144 * overScroll view (i.e QS).
2145 */
2146 private boolean shouldOverScrollFling(int initialVelocity) {
2147 float topOverScroll = getCurrentOverScrollAmount(true);
2148 return mScrolledToTopOnFirstDown
2149 && !mExpandedInThisMotion
2150 && topOverScroll > mMinTopOverScrollToEscape
2151 && initialVelocity > 0;
2152 }
2153
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002154 /**
2155 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2156 * account.
2157 *
2158 * @param qsHeight the top padding imposed by the quick settings panel
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002159 * @param animate whether to animate the change
2160 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
2161 * {@code qsHeight} is the final top padding
2162 */
Jason Monk16ac3772016-02-10 15:39:21 -05002163 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002164 boolean ignoreIntrinsicPadding) {
Selim Cinekbc243a92016-09-27 16:35:13 -07002165 int topPadding = (int) qsHeight;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002166 int minStackHeight = getLayoutMinHeight();
Selim Cinekbc243a92016-09-27 16:35:13 -07002167 if (topPadding + minStackHeight > getHeight()) {
2168 mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
Selim Cinek1408eb52014-06-02 14:45:38 +02002169 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002170 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02002171 }
Selim Cinekbc243a92016-09-27 16:35:13 -07002172 setTopPadding(ignoreIntrinsicPadding ? topPadding : clampPadding(topPadding),
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002173 animate);
Selim Cinekbc243a92016-09-27 16:35:13 -07002174 setExpandedHeight(mExpandedHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02002175 }
2176
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002177 public int getLayoutMinHeight() {
Selim Cineka981d082016-08-03 14:45:31 -07002178 int firstChildMinHeight = getFirstChildIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07002179 return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
Selim Cinekbc243a92016-09-27 16:35:13 -07002180 mMaxLayoutHeight - mIntrinsicPadding);
Selim Cinek94c2d822016-07-13 18:50:04 -07002181 }
2182
Selim Cineka981d082016-08-03 14:45:31 -07002183 public int getFirstChildIntrinsicHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002184 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002185 int firstChildMinHeight = firstChild != null
2186 ? firstChild.getIntrinsicHeight()
Selim Cinek90b6a272016-06-03 16:50:52 -07002187 : mEmptyShadeView != null
Selim Cineka981d082016-08-03 14:45:31 -07002188 ? mEmptyShadeView.getIntrinsicHeight()
Selim Cinek90b6a272016-06-03 16:50:52 -07002189 : mCollapsedSize;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002190 if (mOwnScrollY > 0) {
2191 firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
2192 }
Selim Cinek94c2d822016-07-13 18:50:04 -07002193 return firstChildMinHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002194 }
2195
2196 public float getTopPaddingOverflow() {
2197 return mTopPaddingOverflow;
2198 }
2199
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002200 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002201 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek567e8452016-03-24 10:54:56 -07002202 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08002203 : mCollapsedSize;
2204 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002205 + mBottomStackSlowDownHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002206 }
2207
Selim Cinek1408eb52014-06-02 14:45:38 +02002208 private int clampPadding(int desiredPadding) {
2209 return Math.max(desiredPadding, mIntrinsicPadding);
2210 }
2211
Selim Cinekfed1ab62014-06-17 14:10:33 -07002212 private float getRubberBandFactor(boolean onTop) {
2213 if (!onTop) {
2214 return RUBBER_BAND_FACTOR_NORMAL;
2215 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002216 if (mExpandedInThisMotion) {
2217 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002218 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002219 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2220 } else if (mScrolledToTopOnFirstDown) {
2221 return 1.0f;
2222 }
2223 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02002224 }
2225
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002226 /**
2227 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2228 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2229 * overscroll view (e.g. expand QS).
2230 */
2231 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002232 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002233 || !mScrolledToTopOnFirstDown;
2234 }
2235
Selim Cinek67b22602014-03-10 15:40:16 +01002236 private void endDrag() {
2237 setIsBeingDragged(false);
2238
2239 recycleVelocityTracker();
2240
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002241 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
2242 setOverScrollAmount(0, true /* onTop */, true /* animate */);
2243 }
2244 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
2245 setOverScrollAmount(0, false /* onTop */, true /* animate */);
2246 }
Selim Cinek67b22602014-03-10 15:40:16 +01002247 }
2248
Jorim Jaggi56306252014-07-03 00:40:09 +02002249 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
2250 ev.offsetLocation(sourceView.getX(), sourceView.getY());
2251 ev.offsetLocation(-targetView.getX(), -targetView.getY());
2252 }
2253
Selim Cinek67b22602014-03-10 15:40:16 +01002254 @Override
2255 public boolean onInterceptTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002256 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002257 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02002258 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002259 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002260 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
2261 }
Selim Cinek67b22602014-03-10 15:40:16 +01002262 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02002263 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01002264 scrollWantsIt = onInterceptTouchEventScroll(ev);
2265 }
2266 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02002267 if (!mIsBeingDragged
2268 && !mExpandingNotification
2269 && !mExpandedInThisMotion
Adrian Roosfa139752016-04-27 09:59:08 -07002270 && !mOnlyScrollingInThisMotion
2271 && !mDisallowDismissInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01002272 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
2273 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002274 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
2275 }
2276
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002277 private void handleEmptySpaceClick(MotionEvent ev) {
2278 switch (ev.getActionMasked()) {
2279 case MotionEvent.ACTION_MOVE:
2280 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
2281 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
2282 mTouchIsClick = false;
2283 }
2284 break;
2285 case MotionEvent.ACTION_UP:
2286 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
2287 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
2288 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
2289 }
2290 break;
2291 }
2292 }
2293
Selim Cinek1408eb52014-06-02 14:45:38 +02002294 private void initDownStates(MotionEvent ev) {
2295 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
2296 mExpandedInThisMotion = false;
2297 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002298 mDisallowScrollingInThisMotion = false;
Adrian Roosfa139752016-04-27 09:59:08 -07002299 mDisallowDismissInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002300 mTouchIsClick = true;
2301 mInitialTouchX = ev.getX();
2302 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02002303 }
Selim Cinek67b22602014-03-10 15:40:16 +01002304 }
2305
Selim Cinekef5127e2015-12-21 16:55:58 -08002306 public void setChildTransferInProgress(boolean childTransferInProgress) {
2307 mChildTransferInProgress = childTransferInProgress;
2308 }
2309
Christoph Studer068f5922014-04-08 17:43:07 -04002310 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002311 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04002312 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002313 // we only call our internal methods if this is actually a removal and not just a
2314 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08002315 if (!mChildTransferInProgress) {
Selim Cinekd1395642016-04-28 12:22:42 -07002316 onViewRemovedInternal(child, this);
Selim Cinekb5605e52015-02-20 18:21:41 +01002317 }
2318 }
2319
Selim Cinek197823d2016-03-24 13:06:00 -07002320 @Override
2321 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2322 super.requestDisallowInterceptTouchEvent(disallowIntercept);
2323 if (disallowIntercept) {
2324 mSwipeHelper.removeLongPressCallback();
2325 }
2326 }
2327
Selim Cineka5703182016-05-11 21:23:16 -04002328 private void onViewRemovedInternal(View child, ViewGroup container) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002329 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002330 // This is only a position change, don't do anything special
2331 return;
2332 }
Selim Cinekd1395642016-04-28 12:22:42 -07002333 ExpandableView expandableView = (ExpandableView) child;
2334 expandableView.setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04002335 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002336 updateScrollStateForRemovedChild(expandableView);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002337 boolean animationGenerated = generateRemoveAnimation(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002338 if (animationGenerated) {
2339 if (!mSwipedOutViews.contains(child)) {
Selim Cineka5703182016-05-11 21:23:16 -04002340 container.getOverlay().add(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002341 } else if (Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
Selim Cineka5703182016-05-11 21:23:16 -04002342 container.addTransientView(child, 0);
2343 expandableView.setTransientContainer(container);
Selim Cinekd1395642016-04-28 12:22:42 -07002344 }
2345 } else {
2346 mSwipedOutViews.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002347 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002348 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002349
2350 // Make sure the clipRect we might have set is removed
Mady Mellorc128f222016-04-26 11:42:46 -07002351 expandableView.setClipTopAmount(0);
Selim Cineke9bad242016-06-15 11:46:37 -07002352
2353 focusNextViewIfFocused(child);
2354 }
2355
2356 private void focusNextViewIfFocused(View view) {
2357 if (view instanceof ExpandableNotificationRow) {
2358 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2359 if (row.shouldRefocusOnDismiss()) {
2360 View nextView = row.getChildAfterViewWhenDismissed();
2361 if (nextView == null) {
2362 View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
2363 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
2364 ? groupParentWhenDismissed.getTranslationY()
2365 : view.getTranslationY());
2366 }
2367 if (nextView != null) {
2368 nextView.requestAccessibilityFocus();
2369 }
2370 }
2371 }
2372
Selim Cinekc27437b2014-05-14 10:23:33 +02002373 }
2374
Selim Cinekb5605e52015-02-20 18:21:41 +01002375 private boolean isChildInGroup(View child) {
2376 return child instanceof ExpandableNotificationRow
2377 && mGroupManager.isChildInGroupWithSummary(
2378 ((ExpandableNotificationRow) child).getStatusBarNotification());
2379 }
2380
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002381 /**
2382 * Generate a remove animation for a child view.
2383 *
2384 * @param child The view to generate the remove animation for.
2385 * @return Whether an animation was generated.
2386 */
2387 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002388 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002389 mAddedHeadsUpChildren.remove(child);
2390 return false;
2391 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002392 if (isClickedHeadsUp(child)) {
2393 // An animation is already running, add it to the Overlay
2394 mClearOverlayViewsWhenFinished.add(child);
2395 return true;
2396 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002397 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002398 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002399 // Generate Animations
2400 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002401 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002402 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002403 } else {
2404 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002405 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002406 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002407 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002408 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002409 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002410 }
2411
Selim Cinek0fccc722015-07-29 17:04:36 -07002412 private boolean isClickedHeadsUp(View child) {
2413 return HeadsUpManager.isClickedHeadsUpNotification(child);
2414 }
2415
Selim Cineke0890e52015-06-17 11:17:08 -07002416 /**
2417 * Remove a removed child view from the heads up animations if it was just added there
2418 *
2419 * @return whether any child was removed from the list to animate
2420 */
2421 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2422 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002423 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2424 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002425 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002426 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002427 mTmpList.add(eventPair);
2428 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002429 }
2430 }
Selim Cineke0890e52015-06-17 11:17:08 -07002431 if (hasAddEvent) {
2432 // This child was just added lets remove all events.
2433 mHeadsUpChangeAnimations.removeAll(mTmpList);
Selim Cinek8fc78752016-07-13 14:34:56 -07002434 ((ExpandableNotificationRow ) child).setHeadsupDisappearRunning(false);
Selim Cineke0890e52015-06-17 11:17:08 -07002435 }
2436 mTmpList.clear();
2437 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002438 }
2439
Selim Cinek572bbd42014-04-25 16:43:27 +02002440 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002441 * @param child the child to query
2442 * @return whether a view is not a top level child but a child notification and that group is
2443 * not expanded
2444 */
2445 private boolean isChildInInvisibleGroup(View child) {
2446 if (child instanceof ExpandableNotificationRow) {
2447 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2448 ExpandableNotificationRow groupSummary =
2449 mGroupManager.getGroupSummary(row.getStatusBarNotification());
2450 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07002451 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01002452 }
2453 }
2454 return false;
2455 }
2456
2457 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02002458 * Updates the scroll position when a child was removed
2459 *
2460 * @param removedChild the removed child
2461 */
Selim Cinek61633a82016-01-25 15:54:10 -08002462 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002463 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinek42357e02016-02-24 18:48:01 -08002464 int padding = (int) NotificationUtils.interpolate(
2465 mPaddingBetweenElements,
2466 mIncreasedPaddingBetweenElements,
2467 removedChild.getIncreasedPaddingAmount());
Selim Cinek61633a82016-01-25 15:54:10 -08002468 int childHeight = getIntrinsicHeight(removedChild) + padding;
Selim Cinek572bbd42014-04-25 16:43:27 +02002469 int endPosition = startingPosition + childHeight;
2470 if (endPosition <= mOwnScrollY) {
2471 // This child is fully scrolled of the top, so we have to deduct its height from the
2472 // scrollPosition
Selim Cinekef406062016-09-29 17:33:13 -07002473 setOwnScrollY(mOwnScrollY - childHeight);
Selim Cinek572bbd42014-04-25 16:43:27 +02002474 } else if (startingPosition < mOwnScrollY) {
2475 // This child is currently being scrolled into, set the scroll position to the start of
2476 // this child
Selim Cinekef406062016-09-29 17:33:13 -07002477 setOwnScrollY(startingPosition);
Selim Cinek572bbd42014-04-25 16:43:27 +02002478 }
2479 }
2480
Selim Cinekd7c4e002014-07-04 18:36:42 +02002481 private int getIntrinsicHeight(View view) {
2482 if (view instanceof ExpandableView) {
2483 ExpandableView expandableView = (ExpandableView) view;
2484 return expandableView.getIntrinsicHeight();
2485 }
2486 return view.getHeight();
2487 }
2488
Adrian Roos4a579672016-05-24 16:54:37 -07002489 private int getPositionInLinearLayout(View requestedView) {
2490 ExpandableNotificationRow childInGroup = null;
2491 ExpandableNotificationRow requestedRow = null;
2492 if (isChildInGroup(requestedView)) {
2493 // We're asking for a child in a group. Calculate the position of the parent first,
2494 // then within the parent.
2495 childInGroup = (ExpandableNotificationRow) requestedView;
2496 requestedView = requestedRow = childInGroup.getNotificationParent();
2497 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002498 int position = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08002499 float previousIncreasedAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02002500 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002501 ExpandableView child = (ExpandableView) getChildAt(i);
2502 boolean notGone = child.getVisibility() != View.GONE;
2503 if (notGone) {
Selim Cinek42357e02016-02-24 18:48:01 -08002504 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cinek61633a82016-01-25 15:54:10 -08002505 if (position != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08002506 position += (int) NotificationUtils.interpolate(
2507 mPaddingBetweenElements,
2508 mIncreasedPaddingBetweenElements,
2509 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Selim Cinek61633a82016-01-25 15:54:10 -08002510 }
Selim Cinek42357e02016-02-24 18:48:01 -08002511 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002512 }
Adrian Roos4a579672016-05-24 16:54:37 -07002513 if (child == requestedView) {
2514 if (requestedRow != null) {
2515 position += requestedRow.getPositionOfChild(childInGroup);
2516 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002517 return position;
2518 }
Selim Cinek61633a82016-01-25 15:54:10 -08002519 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02002520 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002521 }
2522 }
2523 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02002524 }
2525
2526 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002527 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02002528 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002529 onViewAddedInternal(child);
2530 }
2531
Selim Cinek614576e2016-01-20 10:54:09 -08002532 private void updateFirstAndLastBackgroundViews() {
2533 ActivatableNotificationView firstChild = getFirstChildWithBackground();
2534 ActivatableNotificationView lastChild = getLastChildWithBackground();
2535 if (mAnimationsEnabled && mIsExpanded) {
2536 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2537 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2538 } else {
2539 mAnimateNextBackgroundTop = false;
2540 mAnimateNextBackgroundBottom = false;
2541 }
2542 mFirstVisibleBackgroundChild = firstChild;
2543 mLastVisibleBackgroundChild = lastChild;
2544 }
2545
Selim Cinekb5605e52015-02-20 18:21:41 +01002546 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002547 updateHideSensitiveForChild(child);
Jorim Jaggibe565df2014-04-28 17:51:23 +02002548 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00002549 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002550 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02002551 updateChronometerForChild(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002552 }
2553
Selim Cinekd06c41c2015-07-06 14:51:36 -07002554 private void updateHideSensitiveForChild(View child) {
Selim Cinekcfcd23e2016-05-05 12:31:42 -04002555 if (child instanceof ExpandableView) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002556 ExpandableView expandableView = (ExpandableView) child;
Selim Cinekcfcd23e2016-05-05 12:31:42 -04002557 expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
Selim Cinekd06c41c2015-07-06 14:51:36 -07002558 }
2559 }
2560
Selim Cinekd1395642016-04-28 12:22:42 -07002561 public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
2562 onViewRemovedInternal(row, childrenContainer);
Selim Cinekb5605e52015-02-20 18:21:41 +01002563 }
2564
2565 public void notifyGroupChildAdded(View row) {
2566 onViewAddedInternal(row);
2567 }
2568
Jorim Jaggi75c95042014-05-16 19:09:59 +02002569 public void setAnimationsEnabled(boolean animationsEnabled) {
2570 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002571 updateNotificationAnimationStates();
2572 }
2573
2574 private void updateNotificationAnimationStates() {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002575 boolean running = mAnimationsEnabled || mPulsing;
Selim Cinekcab4a602014-09-03 14:47:57 +02002576 int childCount = getChildCount();
2577 for (int i = 0; i < childCount; i++) {
2578 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07002579 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02002580 updateAnimationState(running, child);
2581 }
2582 }
2583
Selim Cinek51ae05d2014-09-09 15:51:38 +02002584 private void updateAnimationState(View child) {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002585 updateAnimationState((mAnimationsEnabled || mPulsing)
2586 && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002587 }
2588
2589
Selim Cinekcab4a602014-09-03 14:47:57 +02002590 private void updateAnimationState(boolean running, View child) {
2591 if (child instanceof ExpandableNotificationRow) {
2592 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2593 row.setIconAnimationRunning(running);
2594 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02002595 }
2596
2597 public boolean isAddOrRemoveAnimationPending() {
2598 return mNeedsAnimation
2599 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2600 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002601 /**
2602 * Generate an animation for an added child view.
2603 *
2604 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00002605 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002606 */
Jorim Jaggif6411742014-08-05 17:10:43 +00002607 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002608 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002609 // Generate Animations
2610 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00002611 if (fromMoreCard) {
2612 mFromMoreCardAdditions.add(child);
2613 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002614 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02002615 }
Adrian Roos777ef562015-12-01 17:37:14 -08002616 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002617 mAddedHeadsUpChildren.add(child);
2618 mChildrenToAddAnimated.remove(child);
2619 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002620 }
2621
2622 /**
2623 * Change the position of child to a new location
2624 *
2625 * @param child the view to change the position for
2626 * @param newIndex the new index
2627 */
2628 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002629 int currentIndex = indexOfChild(child);
2630 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002631 mChangePositionInProgress = true;
Adrian Roos14503e22016-03-09 14:01:24 -08002632 ((ExpandableView)child).setChangingPosition(true);
Selim Cinekc27437b2014-05-14 10:23:33 +02002633 removeView(child);
2634 addView(child, newIndex);
Adrian Roos14503e22016-03-09 14:01:24 -08002635 ((ExpandableView)child).setChangingPosition(false);
Selim Cinek159ffdb2014-06-04 22:24:18 +02002636 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04002637 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002638 mChildrenChangingPositions.add(child);
2639 mNeedsAnimation = true;
2640 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002641 }
2642 }
2643
Selim Cinekf4c19962014-05-01 21:55:31 +02002644 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002645 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002646 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002647 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002648 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002649 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Selim Cinekea66ca02016-05-24 13:33:47 -07002650 setAnimationRunning(true);
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002651 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2652 mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002653 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08002654 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002655 updateViewShadows();
Selim Cinekf4c19962014-05-01 21:55:31 +02002656 } else {
2657 applyCurrentState();
2658 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002659 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02002660 }
2661
2662 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07002663 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02002664 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002665 generateChildAdditionEvents();
2666 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02002667 generateSnapBackEvents();
2668 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002669 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002670 generateActivateEvent();
2671 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02002672 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04002673 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002674 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02002675 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01002676 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02002677 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002678 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002679 }
2680
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002681 private void generateHeadsUpAnimationEvents() {
2682 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002683 ExpandableNotificationRow row = eventPair.first;
2684 boolean isHeadsUp = eventPair.second;
2685 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2686 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07002687 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07002688 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002689 type = row.wasJustClicked()
2690 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2691 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinek76e813c2016-07-14 11:16:58 -07002692 if (row.isChildInGroup()) {
2693 // We can otherwise get stuck in there if it was just isolated
2694 row.setHeadsupDisappearRunning(false);
2695 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002696 } else {
2697 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
2698 if (viewState == null) {
2699 // A view state was never generated for this view, so we don't need to animate
2700 // this. This may happen with notification children.
2701 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07002702 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002703 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2704 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2705 // Our custom add animation
2706 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2707 } else {
2708 // Normal add animation
2709 type = AnimationEvent.ANIMATION_TYPE_ADD;
2710 }
2711 onBottom = !pinnedAndClosed;
2712 }
Selim Cineka59ecc32015-04-07 10:51:49 -07002713 }
2714 AnimationEvent event = new AnimationEvent(row, type);
2715 event.headsUpFromBottom = onBottom;
2716 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002717 }
2718 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07002719 mAddedHeadsUpChildren.clear();
2720 }
2721
Selim Cinekeaee9c02015-06-25 11:04:20 -04002722 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002723 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2724 return false;
2725 }
2726 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002727 }
2728
Selim Cinekb5605e52015-02-20 18:21:41 +01002729 private void generateGroupExpansionEvent() {
2730 // Generate a group expansion/collapsing event if there is such a group at all
2731 if (mExpandedGroupView != null) {
2732 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2733 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2734 mExpandedGroupView = null;
2735 }
2736 }
2737
Selim Cineka5e211b2014-08-11 17:35:48 +02002738 private void generateViewResizeEvent() {
2739 if (mNeedViewResizeAnimation) {
2740 mAnimationEvents.add(
2741 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2742 }
2743 mNeedViewResizeAnimation = false;
2744 }
2745
Selim Cinekeb973562014-05-02 17:07:49 +02002746 private void generateSnapBackEvents() {
2747 for (View child : mSnappedBackChildren) {
2748 mAnimationEvents.add(new AnimationEvent(child,
2749 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2750 }
2751 mSnappedBackChildren.clear();
2752 }
2753
2754 private void generateDragEvents() {
2755 for (View child : mDragAnimPendingChildren) {
2756 mAnimationEvents.add(new AnimationEvent(child,
2757 AnimationEvent.ANIMATION_TYPE_START_DRAG));
2758 }
2759 mDragAnimPendingChildren.clear();
2760 }
2761
Selim Cinek572bbd42014-04-25 16:43:27 +02002762 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002763 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002764 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2765 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002766 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2767 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002768 AnimationEvent event = new AnimationEvent(child, animationType);
2769
2770 // we need to know the view after this one
2771 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2772 mAnimationEvents.add(event);
Selim Cinekd1395642016-04-28 12:22:42 -07002773 mSwipedOutViews.remove(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002774 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002775 mChildrenToRemoveAnimated.clear();
2776 }
2777
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002778 private void generatePositionChangeEvents() {
2779 for (View child : mChildrenChangingPositions) {
2780 mAnimationEvents.add(new AnimationEvent(child,
2781 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2782 }
2783 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002784 if (mGenerateChildOrderChangedEvent) {
2785 mAnimationEvents.add(new AnimationEvent(null,
2786 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2787 mGenerateChildOrderChangedEvent = false;
2788 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002789 }
2790
Selim Cinek572bbd42014-04-25 16:43:27 +02002791 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002792 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002793 if (mFromMoreCardAdditions.contains(child)) {
2794 mAnimationEvents.add(new AnimationEvent(child,
2795 AnimationEvent.ANIMATION_TYPE_ADD,
2796 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2797 } else {
2798 mAnimationEvents.add(new AnimationEvent(child,
2799 AnimationEvent.ANIMATION_TYPE_ADD));
2800 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002801 }
2802 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002803 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002804 }
2805
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002806 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002807 if (mTopPaddingNeedsAnimation) {
2808 mAnimationEvents.add(
2809 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2810 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002811 mTopPaddingNeedsAnimation = false;
2812 }
2813
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002814 private void generateActivateEvent() {
2815 if (mActivateNeedsAnimation) {
2816 mAnimationEvents.add(
2817 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2818 }
2819 mActivateNeedsAnimation = false;
2820 }
2821
Selim Cinekd9acca52014-09-01 22:33:25 +02002822 private void generateAnimateEverythingEvent() {
2823 if (mEverythingNeedsAnimation) {
2824 mAnimationEvents.add(
2825 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2826 }
2827 mEverythingNeedsAnimation = false;
2828 }
2829
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002830 private void generateDimmedEvent() {
2831 if (mDimmedNeedsAnimation) {
2832 mAnimationEvents.add(
2833 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2834 }
2835 mDimmedNeedsAnimation = false;
2836 }
2837
Jorim Jaggiae441282014-08-01 02:45:18 +02002838 private void generateHideSensitiveEvent() {
2839 if (mHideSensitiveNeedsAnimation) {
2840 mAnimationEvents.add(
2841 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2842 }
2843 mHideSensitiveNeedsAnimation = false;
2844 }
2845
John Spurlockbf370992014-06-17 13:58:31 -04002846 private void generateDarkEvent() {
2847 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002848 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2849 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2850 mAnimationEvents.add(ev);
Selim Cinek972123d2016-05-03 14:25:58 -07002851 startBackgroundFadeIn();
John Spurlockbf370992014-06-17 13:58:31 -04002852 }
2853 mDarkNeedsAnimation = false;
2854 }
2855
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002856 private void generateGoToFullShadeEvent() {
2857 if (mGoToFullShadeNeedsAnimation) {
2858 mAnimationEvents.add(
2859 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2860 }
2861 mGoToFullShadeNeedsAnimation = false;
2862 }
2863
Selim Cinek67b22602014-03-10 15:40:16 +01002864 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002865 if (!isScrollingEnabled()) {
2866 return false;
2867 }
Selim Cinek67b22602014-03-10 15:40:16 +01002868 /*
2869 * This method JUST determines whether we want to intercept the motion.
2870 * If we return true, onMotionEvent will be called and we do the actual
2871 * scrolling there.
2872 */
2873
2874 /*
2875 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002876 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002877 * motion.
2878 */
2879 final int action = ev.getAction();
2880 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2881 return true;
2882 }
2883
Selim Cinek67b22602014-03-10 15:40:16 +01002884 switch (action & MotionEvent.ACTION_MASK) {
2885 case MotionEvent.ACTION_MOVE: {
2886 /*
2887 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04002888 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01002889 */
2890
2891 /*
2892 * Locally do absolute value. mLastMotionY is set to the y value
2893 * of the down event.
2894 */
2895 final int activePointerId = mActivePointerId;
2896 if (activePointerId == INVALID_POINTER) {
2897 // If we don't have a valid id, the touch down wasn't on content.
2898 break;
2899 }
2900
2901 final int pointerIndex = ev.findPointerIndex(activePointerId);
2902 if (pointerIndex == -1) {
2903 Log.e(TAG, "Invalid pointerId=" + activePointerId
2904 + " in onInterceptTouchEvent");
2905 break;
2906 }
2907
2908 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002909 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002910 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002911 final int xDiff = Math.abs(x - mDownX);
2912 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002913 setIsBeingDragged(true);
2914 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002915 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002916 initVelocityTrackerIfNotExists();
2917 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002918 }
2919 break;
2920 }
2921
2922 case MotionEvent.ACTION_DOWN: {
2923 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05302924 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002925 if (getChildAtPosition(ev.getX(), y) == null) {
2926 setIsBeingDragged(false);
2927 recycleVelocityTracker();
2928 break;
2929 }
2930
2931 /*
2932 * Remember location of down touch.
2933 * ACTION_DOWN always refers to pointer index 0.
2934 */
2935 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002936 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002937 mActivePointerId = ev.getPointerId(0);
2938
2939 initOrResetVelocityTracker();
2940 mVelocityTracker.addMovement(ev);
2941 /*
2942 * If being flinged and user touches the screen, initiate drag;
2943 * otherwise don't. mScroller.isFinished should be false when
2944 * being flinged.
2945 */
2946 boolean isBeingDragged = !mScroller.isFinished();
2947 setIsBeingDragged(isBeingDragged);
2948 break;
2949 }
2950
2951 case MotionEvent.ACTION_CANCEL:
2952 case MotionEvent.ACTION_UP:
2953 /* Release the drag */
2954 setIsBeingDragged(false);
2955 mActivePointerId = INVALID_POINTER;
2956 recycleVelocityTracker();
2957 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2958 postInvalidateOnAnimation();
2959 }
2960 break;
2961 case MotionEvent.ACTION_POINTER_UP:
2962 onSecondaryPointerUp(ev);
2963 break;
2964 }
2965
2966 /*
2967 * The only time we want to intercept motion events is if we are in the
2968 * drag mode.
2969 */
2970 return mIsBeingDragged;
2971 }
2972
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002973 /**
2974 * @return Whether the specified motion event is actually happening over the content.
2975 */
2976 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002977 return isInContentBounds(event.getY());
2978 }
2979
2980 /**
2981 * @return Whether a y coordinate is inside the content.
2982 */
2983 public boolean isInContentBounds(float y) {
2984 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002985 }
2986
Selim Cinek67b22602014-03-10 15:40:16 +01002987 private void setIsBeingDragged(boolean isDragged) {
2988 mIsBeingDragged = isDragged;
2989 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002990 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002991 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002992 }
2993 }
2994
2995 @Override
2996 public void onWindowFocusChanged(boolean hasWindowFocus) {
2997 super.onWindowFocusChanged(hasWindowFocus);
2998 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002999 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01003000 }
3001 }
Selim Cinekfab078b2014-03-27 22:45:58 +01003002
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07003003 @Override
Adrian Roos181385c2016-05-05 17:45:44 -04003004 public void clearChildFocus(View child) {
3005 super.clearChildFocus(child);
3006 if (mForcedScroll == child) {
3007 mForcedScroll = null;
3008 }
3009 }
3010
3011 @Override
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07003012 public void requestDisallowLongPress() {
3013 removeLongPressCallback();
3014 }
3015
Adrian Roosfa139752016-04-27 09:59:08 -07003016 @Override
3017 public void requestDisallowDismiss() {
3018 mDisallowDismissInThisMotion = true;
3019 }
3020
Selim Cinek1408eb52014-06-02 14:45:38 +02003021 public void removeLongPressCallback() {
3022 mSwipeHelper.removeLongPressCallback();
3023 }
3024
Selim Cinekfab078b2014-03-27 22:45:58 +01003025 @Override
3026 public boolean isScrolledToTop() {
3027 return mOwnScrollY == 0;
3028 }
3029
3030 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01003031 public boolean isScrolledToBottom() {
3032 return mOwnScrollY >= getScrollRange();
3033 }
3034
3035 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01003036 public View getHostView() {
3037 return this;
3038 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003039
Selim Cinekb6d85eb2014-03-28 20:21:01 +01003040 public int getEmptyBottomMargin() {
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08003041 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize
3042 - mBottomStackSlowDownHeight;
Selim Cinek4a1ac842014-05-01 15:51:58 +02003043 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01003044 }
3045
Selim Cinek5f71bee2015-11-18 10:25:23 -08003046 public float getKeyguardBottomStackSize() {
3047 return mBottomStackPeekSize + getResources().getDimensionPixelSize(
3048 R.dimen.bottom_stack_slow_down_length);
3049 }
3050
Selim Cinek1685e632014-04-08 02:27:49 +02003051 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02003052 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02003053 }
3054
3055 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02003056 mIsExpansionChanging = false;
Selim Cinek4fe3e472014-07-03 16:32:54 +02003057 if (!mIsExpanded) {
Selim Cinekef406062016-09-29 17:33:13 -07003058 setOwnScrollY(0);
Selim Cinek78f40082016-03-10 18:06:27 -08003059 mPhoneStatusBar.resetUserExpandedStates();
Selim Cinekf336f4c2014-11-12 16:58:16 +01003060
Selim Cinekd1395642016-04-28 12:22:42 -07003061 // lets make sure nothing is in the overlay / transient anymore
Selim Cineka5703182016-05-11 21:23:16 -04003062 clearTemporaryViews(this);
Selim Cinekd1395642016-04-28 12:22:42 -07003063 for (int i = 0; i < getChildCount(); i++) {
3064 ExpandableView child = (ExpandableView) getChildAt(i);
3065 if (child instanceof ExpandableNotificationRow) {
3066 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Selim Cineka5703182016-05-11 21:23:16 -04003067 clearTemporaryViews(row.getChildrenContainer());
Selim Cinekd1395642016-04-28 12:22:42 -07003068 }
3069 }
Selim Cinek4fe3e472014-07-03 16:32:54 +02003070 }
Selim Cinek1685e632014-04-08 02:27:49 +02003071 }
3072
Selim Cineka5703182016-05-11 21:23:16 -04003073 private void clearTemporaryViews(ViewGroup viewGroup) {
Selim Cinekd1395642016-04-28 12:22:42 -07003074 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
Selim Cinek81f26d32016-05-09 18:54:10 -04003075 viewGroup.removeTransientView(viewGroup.getTransientView(0));
Selim Cinekd1395642016-04-28 12:22:42 -07003076 }
Selim Cineka5703182016-05-11 21:23:16 -04003077 if (viewGroup != null) {
3078 viewGroup.getOverlay().clear();
3079 }
Selim Cinekd1395642016-04-28 12:22:42 -07003080 }
3081
Jorim Jaggie4b840d2015-06-30 16:19:17 -07003082 public void onPanelTrackingStarted() {
3083 mPanelTracking = true;
3084 }
3085 public void onPanelTrackingStopped() {
3086 mPanelTracking = false;
3087 }
3088
Selim Cinekb24e0a92015-06-09 20:17:30 -07003089 public void resetScrollPosition() {
3090 mScroller.abortAnimation();
Selim Cinekef406062016-09-29 17:33:13 -07003091 setOwnScrollY(0);
Selim Cinekb24e0a92015-06-09 20:17:30 -07003092 }
3093
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02003094 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02003095 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02003096 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02003097 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02003098 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08003099 if (!mIsExpanded) {
3100 mGroupManager.collapseAllGroups();
3101 }
Selim Cinekcab4a602014-09-03 14:47:57 +02003102 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02003103 updateChronometers();
3104 }
3105 }
3106
3107 private void updateChronometers() {
3108 int childCount = getChildCount();
3109 for (int i = 0; i < childCount; i++) {
3110 updateChronometerForChild(getChildAt(i));
3111 }
3112 }
3113
3114 private void updateChronometerForChild(View child) {
3115 if (child instanceof ExpandableNotificationRow) {
3116 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3117 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02003118 }
Selim Cinek1685e632014-04-08 02:27:49 +02003119 }
3120
Jorim Jaggibe565df2014-04-28 17:51:23 +02003121 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003122 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003123 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02003124 updateScrollPositionOnExpandInBottom(view);
3125 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02003126 notifyHeightChangeListener(view);
Selim Cinekbc243a92016-09-27 16:35:13 -07003127 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
3128 ? (ExpandableNotificationRow) view
3129 : null;
3130 if (row != null && (row == mFirstVisibleBackgroundChild
3131 || row.getNotificationParent() == mFirstVisibleBackgroundChild)) {
3132 updateAlgorithmLayoutMinHeight();
3133 }
Selim Cinekb5605e52015-02-20 18:21:41 +01003134 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08003135 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01003136 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003137 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02003138 }
3139
Selim Cineka5e211b2014-08-11 17:35:48 +02003140 @Override
3141 public void onReset(ExpandableView view) {
Selim Cinek51ae05d2014-09-09 15:51:38 +02003142 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02003143 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02003144 }
3145
Selim Cinekf7a14c02014-07-07 14:01:46 +02003146 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
3147 if (view instanceof ExpandableNotificationRow) {
3148 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02003149 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08003150 if (row.isSummaryWithChildren()) {
3151 return;
3152 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02003153 // We are actually expanding this view
Selim Cinek4e4cac32016-03-11 16:45:52 -08003154 float endPosition = row.getTranslationY() + row.getActualHeight();
Selim Cinek388df6d2015-10-22 13:25:11 -07003155 if (row.isChildInGroup()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08003156 endPosition += row.getNotificationParent().getTranslationY();
Selim Cinek388df6d2015-10-22 13:25:11 -07003157 }
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003158 int stackEnd = getStackEndPosition();
Selim Cinekf7a14c02014-07-07 14:01:46 +02003159 if (endPosition > stackEnd) {
Selim Cinekef406062016-09-29 17:33:13 -07003160 setOwnScrollY((int) (mOwnScrollY + endPosition - stackEnd));
Selim Cinekf7a14c02014-07-07 14:01:46 +02003161 mDisallowScrollingInThisMotion = true;
3162 }
3163 }
3164 }
3165 }
3166
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003167 private int getStackEndPosition() {
3168 return mMaxLayoutHeight - mBottomStackPeekSize - mBottomStackSlowDownHeight
3169 + mPaddingBetweenElements + (int) mStackTranslation;
3170 }
3171
Jorim Jaggibe565df2014-04-28 17:51:23 +02003172 public void setOnHeightChangedListener(
3173 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
3174 this.mOnHeightChangedListener = mOnHeightChangedListener;
3175 }
3176
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003177 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
3178 mOnEmptySpaceClickListener = listener;
3179 }
3180
Selim Cinek572bbd42014-04-25 16:43:27 +02003181 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08003182 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02003183 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07003184 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07003185 clearViewOverlays();
Selim Cinek8fc78752016-07-13 14:34:56 -07003186 clearHeadsUpDisappearRunning();
3187 }
3188
3189 private void clearHeadsUpDisappearRunning() {
3190 for (int i = 0; i < getChildCount(); i++) {
3191 View view = getChildAt(i);
3192 if (view instanceof ExpandableNotificationRow) {
Selim Cinek76e813c2016-07-14 11:16:58 -07003193 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
3194 row.setHeadsupDisappearRunning(false);
3195 if (row.isSummaryWithChildren()) {
3196 for (ExpandableNotificationRow child : row.getNotificationChildren()) {
3197 child.setHeadsupDisappearRunning(false);
3198 }
3199 }
Selim Cinek8fc78752016-07-13 14:34:56 -07003200 }
3201 }
Selim Cinek0fccc722015-07-29 17:04:36 -07003202 }
3203
3204 private void clearViewOverlays() {
3205 for (View view : mClearOverlayViewsWhenFinished) {
Selim Cineka5703182016-05-11 21:23:16 -04003206 StackStateAnimator.removeFromOverlay(view);
Selim Cinek0fccc722015-07-29 17:04:36 -07003207 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07003208 }
3209
3210 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003211 for (Runnable runnable : mAnimationFinishedRunnables) {
3212 runnable.run();
3213 }
3214 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02003215 }
3216
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003217 /**
3218 * See {@link AmbientState#setDimmed}.
3219 */
3220 public void setDimmed(boolean dimmed, boolean animate) {
3221 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02003222 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003223 mDimmedNeedsAnimation = true;
3224 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08003225 animateDimmed(dimmed);
3226 } else {
3227 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003228 }
3229 requestChildrenUpdate();
3230 }
3231
Selim Cinekd35c2792016-01-21 13:20:57 -08003232 private void setDimAmount(float dimAmount) {
3233 mDimAmount = dimAmount;
3234 updateBackgroundDimming();
3235 }
3236
3237 private void animateDimmed(boolean dimmed) {
3238 if (mDimAnimator != null) {
3239 mDimAnimator.cancel();
3240 }
3241 float target = dimmed ? 1.0f : 0.0f;
3242 if (target == mDimAmount) {
3243 return;
3244 }
3245 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
3246 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
3247 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
3248 mDimAnimator.addListener(mDimEndListener);
3249 mDimAnimator.addUpdateListener(mDimUpdateListener);
3250 mDimAnimator.start();
3251 }
3252
Jorim Jaggiae441282014-08-01 02:45:18 +02003253 public void setHideSensitive(boolean hideSensitive, boolean animate) {
3254 if (hideSensitive != mAmbientState.isHideSensitive()) {
3255 int childCount = getChildCount();
3256 for (int i = 0; i < childCount; i++) {
3257 ExpandableView v = (ExpandableView) getChildAt(i);
3258 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
3259 }
3260 mAmbientState.setHideSensitive(hideSensitive);
3261 if (animate && mAnimationsEnabled) {
3262 mHideSensitiveNeedsAnimation = true;
3263 mNeedsAnimation = true;
3264 }
3265 requestChildrenUpdate();
3266 }
3267 }
3268
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003269 /**
3270 * See {@link AmbientState#setActivatedChild}.
3271 */
Selim Cineka32ab602014-06-11 15:06:01 +02003272 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003273 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02003274 if (mAnimationsEnabled) {
3275 mActivateNeedsAnimation = true;
3276 mNeedsAnimation = true;
3277 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003278 requestChildrenUpdate();
3279 }
3280
Selim Cineka32ab602014-06-11 15:06:01 +02003281 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003282 return mAmbientState.getActivatedChild();
3283 }
3284
Selim Cinek572bbd42014-04-25 16:43:27 +02003285 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02003286 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02003287 if (mListener != null) {
3288 mListener.onChildLocationsChanged(this);
3289 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07003290 runAnimationFinishedRunnables();
Selim Cinekea66ca02016-05-24 13:33:47 -07003291 setAnimationRunning(false);
Selim Cinek6811d722016-01-19 17:53:12 -08003292 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08003293 updateViewShadows();
3294 }
3295
3296 private void updateViewShadows() {
3297 // we need to work around an issue where the shadow would not cast between siblings when
3298 // their z difference is between 0 and 0.1
3299
3300 // Lefts first sort by Z difference
3301 for (int i = 0; i < getChildCount(); i++) {
3302 ExpandableView child = (ExpandableView) getChildAt(i);
3303 if (child.getVisibility() != GONE) {
3304 mTmpSortedChildren.add(child);
3305 }
3306 }
3307 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
3308
3309 // Now lets update the shadow for the views
3310 ExpandableView previous = null;
3311 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
3312 ExpandableView expandableView = mTmpSortedChildren.get(i);
3313 float translationZ = expandableView.getTranslationZ();
3314 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
3315 float diff = otherZ - translationZ;
3316 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
3317 // There is no fake shadow to be drawn
3318 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
3319 } else {
3320 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
Mady Mellorb0a82462016-04-30 17:31:02 -07003321 expandableView.getTranslationY() - previous.getExtraBottomPadding();
3322 expandableView.setFakeShadowIntensity(
3323 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
Selim Cinek33223572016-02-19 19:32:22 -08003324 previous.getOutlineAlpha(), (int) yLocation,
3325 previous.getOutlineTranslation());
3326 }
3327 previous = expandableView;
3328 }
3329
3330 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02003331 }
3332
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003333 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003334 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003335 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003336 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003337 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003338 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003339 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02003340 }
3341
Selim Cinek1408eb52014-06-02 14:45:38 +02003342 public void cancelExpandHelper() {
3343 mExpandHelper.cancel();
3344 }
3345
3346 public void setIntrinsicPadding(int intrinsicPadding) {
3347 mIntrinsicPadding = intrinsicPadding;
3348 }
3349
Jorim Jaggi30c305c2014-07-01 23:34:41 +02003350 public int getIntrinsicPadding() {
3351 return mIntrinsicPadding;
3352 }
3353
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003354 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02003355 * @return the y position of the first notification
3356 */
3357 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07003358 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02003359 }
3360
Selim Cinekc0ce82d2014-06-10 13:21:15 +02003361 @Override
3362 public boolean shouldDelayChildPressedState() {
3363 return true;
3364 }
3365
Jorim Jaggi457cc352014-06-02 22:47:42 +02003366 /**
John Spurlockbf370992014-06-17 13:58:31 -04003367 * See {@link AmbientState#setDark}.
3368 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003369 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04003370 mAmbientState.setDark(dark);
3371 if (animate && mAnimationsEnabled) {
3372 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003373 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04003374 mNeedsAnimation = true;
Selim Cinek972123d2016-05-03 14:25:58 -07003375 setBackgroundFadeAmount(0.0f);
3376 } else if (!dark) {
3377 setBackgroundFadeAmount(1.0f);
John Spurlockbf370992014-06-17 13:58:31 -04003378 }
3379 requestChildrenUpdate();
Selim Cinek6811d722016-01-19 17:53:12 -08003380 if (dark) {
3381 setWillNotDraw(!DEBUG);
3382 mScrimController.setExcludedBackgroundArea(null);
3383 } else {
3384 updateBackground();
3385 setWillNotDraw(false);
Selim Cinek6811d722016-01-19 17:53:12 -08003386 }
John Spurlockbf370992014-06-17 13:58:31 -04003387 }
3388
Selim Cinek972123d2016-05-03 14:25:58 -07003389 private void setBackgroundFadeAmount(float fadeAmount) {
3390 mBackgroundFadeAmount = fadeAmount;
3391 updateBackgroundDimming();
3392 }
3393
3394 public float getBackgroundFadeAmount() {
3395 return mBackgroundFadeAmount;
3396 }
3397
3398 private void startBackgroundFadeIn() {
3399 ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
3400 int maxLength;
3401 if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE
3402 || mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
3403 maxLength = getNotGoneChildCount() - 1;
3404 } else {
3405 maxLength = Math.max(mDarkAnimationOriginIndex,
3406 getNotGoneChildCount() - mDarkAnimationOriginIndex - 1);
3407 }
Selim Cinekea66ca02016-05-24 13:33:47 -07003408 maxLength = Math.max(0, maxLength);
Selim Cinek972123d2016-05-03 14:25:58 -07003409 long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK;
3410 fadeAnimator.setStartDelay(delay);
3411 fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
3412 fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
3413 fadeAnimator.start();
3414 }
3415
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003416 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
Selim Cinekbc243a92016-09-27 16:35:13 -07003417 if (screenLocation == null || screenLocation.y < mTopPadding) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003418 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3419 }
3420 if (screenLocation.y > getBottomMostNotificationBottom()) {
3421 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
3422 }
3423 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
3424 if (child != null) {
3425 return getNotGoneIndex(child);
3426 } else {
3427 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3428 }
3429 }
3430
3431 private int getNotGoneIndex(View child) {
3432 int count = getChildCount();
3433 int notGoneIndex = 0;
3434 for (int i = 0; i < count; i++) {
3435 View v = getChildAt(i);
3436 if (child == v) {
3437 return notGoneIndex;
3438 }
3439 if (v.getVisibility() != View.GONE) {
3440 notGoneIndex++;
3441 }
3442 }
3443 return -1;
3444 }
3445
Dan Sandlereceda3d2014-07-21 15:35:01 -04003446 public void setDismissView(DismissView dismissView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003447 int index = -1;
3448 if (mDismissView != null) {
3449 index = indexOfChild(mDismissView);
3450 removeView(mDismissView);
3451 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003452 mDismissView = dismissView;
Selim Cinek01af3342016-02-09 19:25:31 -08003453 addView(mDismissView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003454 }
3455
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003456 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003457 int index = -1;
3458 if (mEmptyShadeView != null) {
3459 index = indexOfChild(mEmptyShadeView);
3460 removeView(mEmptyShadeView);
3461 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003462 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08003463 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003464 }
3465
3466 public void updateEmptyShadeView(boolean visible) {
3467 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
3468 int newVisibility = visible ? VISIBLE : GONE;
3469 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003470 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003471 if (mEmptyShadeView.willBeGone()) {
3472 mEmptyShadeView.cancelAnimation();
3473 } else {
3474 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003475 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003476 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003477 mEmptyShadeView.setWillBeGone(false);
3478 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003479 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003480 } else {
Selim Cinek20867102014-12-10 17:09:17 +01003481 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003482 @Override
3483 public void run() {
3484 mEmptyShadeView.setVisibility(GONE);
3485 mEmptyShadeView.setWillBeGone(false);
3486 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003487 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003488 }
Selim Cinek20867102014-12-10 17:09:17 +01003489 };
Selim Cineka9d3efd2016-08-16 14:16:59 -07003490 if (mAnimationsEnabled && mIsExpanded) {
Selim Cinek20867102014-12-10 17:09:17 +01003491 mEmptyShadeView.setWillBeGone(true);
3492 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
3493 } else {
3494 mEmptyShadeView.setInvisible();
3495 onFinishedRunnable.run();
3496 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003497 }
3498 }
3499 }
3500
Selim Cinek2cd45df2015-06-09 18:00:07 -07003501 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
Selim Cinek3e7592d2016-04-11 09:35:54 +08003502 int index = -1;
3503 if (mOverflowContainer != null) {
3504 index = indexOfChild(mOverflowContainer);
3505 removeView(mOverflowContainer);
3506 }
Selim Cinek2cd45df2015-06-09 18:00:07 -07003507 mOverflowContainer = overFlowContainer;
Selim Cinek3e7592d2016-04-11 09:35:54 +08003508 addView(mOverflowContainer, index);
Selim Cinek2cd45df2015-06-09 18:00:07 -07003509 }
3510
3511 public void updateOverflowContainerVisibility(boolean visible) {
3512 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
3513 : mOverflowContainer.getVisibility();
3514 final int newVisibility = visible ? VISIBLE : GONE;
3515 if (oldVisibility != newVisibility) {
3516 Runnable onFinishedRunnable = new Runnable() {
3517 @Override
3518 public void run() {
3519 mOverflowContainer.setVisibility(newVisibility);
3520 mOverflowContainer.setWillBeGone(false);
3521 updateContentHeight();
3522 notifyHeightChangeListener(mOverflowContainer);
3523 }
3524 };
3525 if (!mAnimationsEnabled || !mIsExpanded) {
3526 mOverflowContainer.cancelAppearDrawing();
3527 onFinishedRunnable.run();
3528 } else if (newVisibility != GONE) {
3529 mOverflowContainer.performAddAnimation(0,
3530 StackStateAnimator.ANIMATION_DURATION_STANDARD);
3531 mOverflowContainer.setVisibility(newVisibility);
3532 mOverflowContainer.setWillBeGone(false);
3533 updateContentHeight();
3534 notifyHeightChangeListener(mOverflowContainer);
3535 } else {
3536 mOverflowContainer.performRemoveAnimation(
3537 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3538 0.0f,
3539 onFinishedRunnable);
3540 mOverflowContainer.setWillBeGone(true);
3541 }
3542 }
3543 }
3544
Dan Sandlereceda3d2014-07-21 15:35:01 -04003545 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003546 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003547 int newVisibility = visible ? VISIBLE : GONE;
3548 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003549 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003550 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003551 mDismissView.cancelAnimation();
3552 } else {
3553 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003554 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003555 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003556 mDismissView.setWillBeGone(false);
3557 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003558 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003559 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003560 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003561 @Override
3562 public void run() {
3563 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003564 mDismissView.setWillBeGone(false);
3565 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003566 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003567 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01003568 };
Selim Cinek20867102014-12-10 17:09:17 +01003569 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003570 mDismissView.setWillBeGone(true);
3571 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3572 } else {
3573 dimissHideFinishRunnable.run();
Selim Cinek7d5f3742014-11-07 18:07:49 +01003574 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003575 }
3576 }
3577 }
3578
3579 public void setDismissAllInProgress(boolean dismissAllInProgress) {
3580 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07003581 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07003582 handleDismissAllClipping();
3583 }
3584
3585 private void handleDismissAllClipping() {
3586 final int count = getChildCount();
3587 boolean previousChildWillBeDismissed = false;
3588 for (int i = 0; i < count; i++) {
3589 ExpandableView child = (ExpandableView) getChildAt(i);
3590 if (child.getVisibility() == GONE) {
3591 continue;
3592 }
3593 if (mDismissAllInProgress && previousChildWillBeDismissed) {
3594 child.setMinClipTopAmount(child.getClipTopAmount());
3595 } else {
3596 child.setMinClipTopAmount(0);
3597 }
3598 previousChildWillBeDismissed = canChildBeDismissed(child);
3599 }
Selim Cineka272dfe2015-02-20 18:12:28 +01003600 }
3601
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003602 public boolean isDismissViewNotGone() {
3603 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3604 }
3605
3606 public boolean isDismissViewVisible() {
3607 return mDismissView.isVisible();
3608 }
3609
3610 public int getDismissViewHeight() {
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08003611 return mDismissView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003612 }
3613
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01003614 public int getEmptyShadeViewHeight() {
3615 return mEmptyShadeView.getHeight();
3616 }
3617
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003618 public float getBottomMostNotificationBottom() {
3619 final int count = getChildCount();
3620 float max = 0;
3621 for (int childIdx = 0; childIdx < count; childIdx++) {
3622 ExpandableView child = (ExpandableView) getChildAt(childIdx);
3623 if (child.getVisibility() == GONE) {
3624 continue;
3625 }
3626 float bottom = child.getTranslationY() + child.getActualHeight();
3627 if (bottom > max) {
3628 max = bottom;
3629 }
3630 }
Selim Cinekd2281152015-04-10 14:37:46 -07003631 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003632 }
3633
Selim Cinek19c8c702014-08-25 22:09:19 +02003634 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3635 this.mPhoneStatusBar = phoneStatusBar;
3636 }
3637
Selim Cinekb5605e52015-02-20 18:21:41 +01003638 public void setGroupManager(NotificationGroupManager groupManager) {
3639 this.mGroupManager = groupManager;
3640 }
3641
Selim Cinekd9acca52014-09-01 22:33:25 +02003642 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01003643 requestAnimateEverything();
3644 }
3645
3646 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02003647 if (mIsExpanded && mAnimationsEnabled) {
3648 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01003649 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02003650 requestChildrenUpdate();
3651 }
3652 }
3653
Selim Cinek04fb2582015-06-02 19:58:09 +02003654 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01003655 int childCount = getChildCount();
3656 for (int i = childCount - 1; i >= 0; i--) {
3657 ExpandableView child = (ExpandableView) getChildAt(i);
3658 if (child.getVisibility() != View.GONE) {
3659 float childTop = child.getY();
3660 if (childTop > touchY) {
3661 // we are above a notification entirely let's abort
3662 return false;
3663 }
3664 boolean belowChild = touchY > childTop + child.getActualHeight();
3665 if (child == mDismissView) {
3666 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3667 touchY - childTop)) {
3668 // We clicked on the dismiss button
3669 return false;
3670 }
3671 } else if (child == mEmptyShadeView) {
3672 // We arrived at the empty shade view, for which we accept all clicks
3673 return true;
3674 } else if (!belowChild){
3675 // We are on a child
3676 return false;
3677 }
3678 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003679 }
Selim Cinek04fb2582015-06-02 19:58:09 +02003680 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003681 }
3682
Selim Cinekb5605e52015-02-20 18:21:41 +01003683 @Override
3684 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003685 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
3686 && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01003687 if (animated) {
3688 mExpandedGroupView = changedRow;
3689 mNeedsAnimation = true;
3690 }
3691 changedRow.setChildrenExpanded(expanded, animated);
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003692 if (!mGroupExpandedForMeasure) {
3693 onHeightChanged(changedRow, false /* needsAnimation */);
3694 }
Mady Mellorb0a82462016-04-30 17:31:02 -07003695 runAfterAnimationFinished(new Runnable() {
3696 @Override
3697 public void run() {
3698 changedRow.onFinishedExpansionChange();
3699 }
3700 });
Selim Cinekb5605e52015-02-20 18:21:41 +01003701 }
3702
3703 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003704 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08003705 mPhoneStatusBar.requestNotificationUpdate();
3706 }
3707
Selim Cinekc22fff62016-05-20 12:44:30 -07003708 /** @hide */
3709 @Override
3710 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
3711 super.onInitializeAccessibilityEventInternal(event);
3712 event.setScrollable(mScrollable);
3713 event.setScrollX(mScrollX);
3714 event.setScrollY(mOwnScrollY);
3715 event.setMaxScrollX(mScrollX);
3716 event.setMaxScrollY(getScrollRange());
3717 }
3718
3719 @Override
3720 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3721 super.onInitializeAccessibilityNodeInfoInternal(info);
Selim Cinekef406062016-09-29 17:33:13 -07003722 if (mScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07003723 info.setScrollable(true);
Selim Cinekef406062016-09-29 17:33:13 -07003724 if (mBackwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07003725 info.addAction(
3726 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
3727 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
3728 }
Selim Cinekef406062016-09-29 17:33:13 -07003729 if (mForwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07003730 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
3731 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
3732 }
3733 }
Selim Cinek41fe89a2016-06-02 15:27:56 -07003734 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
3735 info.setClassName(ScrollView.class.getName());
Selim Cinekc22fff62016-05-20 12:44:30 -07003736 }
3737
3738 /** @hide */
3739 @Override
3740 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
3741 if (super.performAccessibilityActionInternal(action, arguments)) {
3742 return true;
3743 }
3744 if (!isEnabled()) {
3745 return false;
3746 }
3747 int direction = -1;
3748 switch (action) {
3749 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
3750 // fall through
3751 case android.R.id.accessibilityActionScrollDown:
3752 direction = 1;
3753 // fall through
3754 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
3755 // fall through
3756 case android.R.id.accessibilityActionScrollUp:
3757 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
3758 - mBottomStackPeekSize - mBottomStackSlowDownHeight;
3759 final int targetScrollY = Math.max(0,
3760 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
3761 if (targetScrollY != mOwnScrollY) {
3762 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
3763 postInvalidateOnAnimation();
3764 return true;
3765 }
3766 break;
3767 }
3768 return false;
3769 }
3770
Selim Cinekef5127e2015-12-21 16:55:58 -08003771 @Override
Selim Cinek2a739342016-03-17 10:28:55 -07003772 public void onGroupsChanged() {
Selim Cinekef5127e2015-12-21 16:55:58 -08003773 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01003774 }
3775
3776 public void generateChildOrderChangedEvent() {
3777 if (mIsExpanded && mAnimationsEnabled) {
3778 mGenerateChildOrderChangedEvent = true;
3779 mNeedsAnimation = true;
3780 requestChildrenUpdate();
3781 }
3782 }
3783
Selim Cinek684a4422015-04-15 16:18:39 -07003784 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003785 mAnimationFinishedRunnables.add(runnable);
3786 }
3787
3788 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3789 mHeadsUpManager = headsUpManager;
3790 mAmbientState.setHeadsUpManager(headsUpManager);
3791 }
3792
3793 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3794 if (mAnimationsEnabled) {
3795 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3796 mNeedsAnimation = true;
Selim Cinek73cf02a2016-06-17 13:08:00 -07003797 if (!mIsExpanded && !isHeadsUp) {
3798 row.setHeadsupDisappearRunning(true);
3799 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003800 requestChildrenUpdate();
3801 }
3802 }
3803
3804 public void setShadeExpanded(boolean shadeExpanded) {
3805 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07003806 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003807 }
3808
Selim Cineka59ecc32015-04-07 10:51:49 -07003809 /**
3810 * Set the boundary for the bottom heads up position. The heads up will always be above this
3811 * position.
3812 *
3813 * @param height the height of the screen
3814 * @param bottomBarHeight the height of the bar on the bottom
3815 */
3816 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3817 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3818 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003819 requestChildrenUpdate();
3820 }
3821
3822 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3823 mTrackingHeadsUp = trackingHeadsUp;
3824 }
3825
Selim Cinekaac93252015-04-14 20:04:12 -07003826 public void setScrimController(ScrimController scrimController) {
3827 mScrimController = scrimController;
Selim Cinekd35c2792016-01-21 13:20:57 -08003828 mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3829 @Override
3830 public void run() {
3831 updateBackgroundDimming();
3832 }
3833 });
Selim Cinekaac93252015-04-14 20:04:12 -07003834 }
3835
Selim Cinekbbc580b2015-06-03 14:11:03 +02003836 public void forceNoOverlappingRendering(boolean force) {
3837 mForceNoOverlappingRendering = force;
3838 }
3839
3840 @Override
3841 public boolean hasOverlappingRendering() {
3842 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3843 }
3844
Selim Cinek6811d722016-01-19 17:53:12 -08003845 public void setAnimationRunning(boolean animationRunning) {
3846 if (animationRunning != mAnimationRunning) {
3847 if (animationRunning) {
3848 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
3849 } else {
3850 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
3851 }
3852 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08003853 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08003854 }
3855 }
3856
Selim Cinek3776fe02016-02-04 13:32:43 -08003857 public boolean isExpanded() {
3858 return mIsExpanded;
3859 }
3860
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003861 public void setPulsing(boolean pulsing) {
3862 mPulsing = pulsing;
3863 updateNotificationAnimationStates();
3864 }
3865
Selim Cinek07304f5222016-05-19 18:31:36 -07003866 public void setFadingOut(boolean fadingOut) {
3867 if (fadingOut != mFadingOut) {
3868 mFadingOut = fadingOut;
3869 updateFadingState();
Selim Cinek31d37b92016-04-26 09:56:42 -07003870 }
3871 }
3872
Selim Cinek07304f5222016-05-19 18:31:36 -07003873 public void setParentFadingOut(boolean fadingOut) {
3874 if (fadingOut != mParentFadingOut) {
3875 mParentFadingOut = fadingOut;
3876 updateFadingState();
3877 }
3878 }
3879
3880 private void updateFadingState() {
Selim Cinek9bfba9c2016-08-08 15:20:06 -07003881 applyCurrentBackgroundBounds();
Selim Cinek07304f5222016-05-19 18:31:36 -07003882 updateSrcDrawing();
3883 }
3884
Selim Cinek31d37b92016-04-26 09:56:42 -07003885 @Override
3886 public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) {
3887 super.setAlpha(alpha);
Selim Cinek07304f5222016-05-19 18:31:36 -07003888 setFadingOut(alpha != 1.0f);
Selim Cinek31d37b92016-04-26 09:56:42 -07003889 }
3890
Selim Cinek3afd00e2014-08-11 22:32:57 +02003891 /**
Selim Cinek7103fd42016-05-09 22:22:33 -04003892 * Remove the a given view from the viewstate. This is currently used when the children are
3893 * kept in the parent artificially to have a nicer animation.
3894 * @param view the view to remove
3895 */
3896 public void removeViewStateForView(View view) {
3897 mCurrentStackScrollState.removeViewStateForView(view);
3898 }
3899
Selim Cinekbc243a92016-09-27 16:35:13 -07003900 public void setQsExpanded(boolean qsExpanded) {
3901 mQsExpanded = qsExpanded;
3902 updateAlgorithmLayoutMinHeight();
3903 }
3904
Selim Cinekef406062016-09-29 17:33:13 -07003905 public void setOwnScrollY(int ownScrollY) {
3906 if (ownScrollY != mOwnScrollY) {
3907 mOwnScrollY = ownScrollY;
3908 updateForwardAndBackwardScrollability();
3909 }
3910 }
3911
Selim Cinek7103fd42016-05-09 22:22:33 -04003912 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003913 * A listener that is notified when some child locations might have changed.
3914 */
3915 public interface OnChildLocationsChangedListener {
3916 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
3917 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003918
Jorim Jaggi290600a2014-05-30 17:02:20 +02003919 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003920 * A listener that is notified when the empty space below the notifications is clicked on
3921 */
3922 public interface OnEmptySpaceClickListener {
3923 public void onEmptySpaceClicked(float x, float y);
3924 }
3925
3926 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02003927 * A listener that gets notified when the overscroll at the top has changed.
3928 */
3929 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02003930
3931 /**
3932 * Notifies a listener that the overscroll has changed.
3933 *
3934 * @param amount the amount of overscroll, in pixels
3935 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
3936 * unrubberbanded motion to directly expand overscroll view (e.g expand
3937 * QS)
3938 */
3939 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02003940
3941 /**
3942 * Notify a listener that the scroller wants to escape from the scrolling motion and
3943 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
3944 *
3945 * @param velocity The velocity that the Scroller had when over flinging
3946 * @param open Should the fling open or close the overscroll view.
3947 */
3948 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02003949 }
3950
Mady Mellor4b80b102016-01-22 08:03:58 -08003951 private class NotificationSwipeHelper extends SwipeHelper {
Mady Mellor97c8df42016-03-22 18:09:39 -07003952 private static final long SHOW_GEAR_DELAY = 60;
3953 private static final long COVER_GEAR_DELAY = 4000;
Mady Mellor5b3cc112016-09-12 15:53:35 -07003954 private static final long SWIPE_GEAR_TIMING = 200;
Mady Mellor4b80b102016-01-22 08:03:58 -08003955 private CheckForDrag mCheckForDrag;
Mady Mellor97c8df42016-03-22 18:09:39 -07003956 private Runnable mFalsingCheck;
Mady Mellor4b80b102016-01-22 08:03:58 -08003957 private Handler mHandler;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003958 private boolean mGearSnappedTo;
3959 private boolean mGearSnappedOnLeft;
Mady Mellor4b80b102016-01-22 08:03:58 -08003960
3961 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
3962 super(swipeDirection, callback, context);
3963 mHandler = new Handler();
Mady Mellor97c8df42016-03-22 18:09:39 -07003964 mFalsingCheck = new Runnable() {
3965 @Override
3966 public void run() {
3967 resetExposedGearView(true /* animate */, true /* force */);
3968 }
3969 };
Mady Mellor4b80b102016-01-22 08:03:58 -08003970 }
3971
3972 @Override
3973 public void onDownUpdate(View currView) {
3974 // Set the active view
3975 mTranslatingParentView = currView;
3976
3977 // Reset check for drag gesture
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003978 cancelCheckForDrag();
3979 if (mCurrIconRow != null) {
3980 mCurrIconRow.setSnapping(false);
3981 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003982 mCheckForDrag = null;
Mady Mellor34958fa2016-02-23 09:52:17 -08003983 mCurrIconRow = null;
Mady Mellor97c8df42016-03-22 18:09:39 -07003984 mHandler.removeCallbacks(mFalsingCheck);
Mady Mellor4b80b102016-01-22 08:03:58 -08003985
3986 // Slide back any notifications that might be showing a gear
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003987 resetExposedGearView(true /* animate */, false /* force */);
Mady Mellor4b80b102016-01-22 08:03:58 -08003988
3989 if (currView instanceof ExpandableNotificationRow) {
3990 // Set the listener for the current row's gear
3991 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
3992 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
Mady Mellor4b80b102016-01-22 08:03:58 -08003993 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003994 }
3995
3996 @Override
3997 public void onMoveUpdate(View view, float translation, float delta) {
Mady Mellor97c8df42016-03-22 18:09:39 -07003998 mHandler.removeCallbacks(mFalsingCheck);
3999
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004000 if (mCurrIconRow != null) {
4001 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
4002
4003 // If the gear is visible and the movement is towards it it's not a location change.
4004 boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
4005 boolean locationChange = isTowardsGear(translation, onLeft)
4006 ? false : mCurrIconRow.isIconLocationChange(translation);
4007 if (locationChange) {
4008 // Don't consider it "snapped" if location has changed.
4009 setSnappedToGear(false);
4010
4011 // Changed directions, make sure we check to fade in icon again.
4012 if (!mHandler.hasCallbacks(mCheckForDrag)) {
4013 // No check scheduled, set null to schedule a new one.
4014 mCheckForDrag = null;
4015 } else {
4016 // Check scheduled, reset alpha and update location; check will fade it in
4017 mCurrIconRow.setGearAlpha(0f);
Mady Mellorcdd57182016-04-12 19:00:17 -07004018 mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004019 }
4020 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004021 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004022
Mady Mellor34958fa2016-02-23 09:52:17 -08004023 final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
4024 && ((ExpandableNotificationRow) view).areGutsExposed();
4025
4026 if (!isPinnedHeadsUp(view) && !gutsExposed) {
4027 // Only show the gear if we're not a heads up view and guts aren't exposed.
4028 checkForDrag();
Mady Mellor4b80b102016-01-22 08:03:58 -08004029 }
4030 }
4031
4032 @Override
Mady Mellordc6c97d2016-03-31 14:18:35 -07004033 public void dismissChild(final View view, float velocity,
4034 boolean useAccelerateInterpolator) {
4035 super.dismissChild(view, velocity, useAccelerateInterpolator);
Selim Cinekd1395642016-04-28 12:22:42 -07004036 if (mIsExpanded) {
4037 // We don't want to quick-dismiss when it's a heads up as this might lead to closing
4038 // of the panel early.
4039 handleChildDismissed(view);
4040 }
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004041 handleGearCoveredOrDismissed();
Mady Mellor4b80b102016-01-22 08:03:58 -08004042 }
4043
4044 @Override
4045 public void snapChild(final View animView, final float targetLeft, float velocity) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004046 super.snapChild(animView, targetLeft, velocity);
4047 onDragCancelled(animView);
4048 if (targetLeft == 0) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004049 handleGearCoveredOrDismissed();
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004050 }
4051 }
4052
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004053 private void handleGearCoveredOrDismissed() {
4054 cancelCheckForDrag();
4055 setSnappedToGear(false);
4056 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
4057 mGearExposedView = null;
4058 }
4059 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004060
4061 @Override
4062 public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
4063 float translation) {
4064 if (mCurrIconRow == null) {
4065 cancelCheckForDrag();
4066 return false; // Let SwipeHelper handle it.
4067 }
4068
4069 boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
4070 boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
Mady Mellor5b3cc112016-09-12 15:53:35 -07004071 final double timeForGesture = ev.getEventTime() - ev.getDownTime();
4072 final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
4073 && timeForGesture >= SWIPE_GEAR_TIMING;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004074
4075 if (mGearSnappedTo && mCurrIconRow.isVisible()) {
4076 if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
4077 boolean coveringGear =
4078 Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
4079 if (gestureTowardsGear || coveringGear) {
4080 // Gesture is towards or covering the gear
4081 snapChild(animView, 0 /* leftTarget */, velocity);
4082 } else if (isDismissGesture(ev)) {
4083 // Gesture is a dismiss that's not towards the gear
Mady Mellordc6c97d2016-03-31 14:18:35 -07004084 dismissChild(animView, velocity,
4085 !swipedFastEnough() /* useAccelerateInterpolator */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004086 } else {
4087 // Didn't move enough to dismiss or cover, snap to the gear
4088 snapToGear(animView, velocity);
4089 }
4090 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
4091 || (gestureTowardsGear && !swipedFarEnough())) {
4092 // The gear has been snapped to previously, however, the gear is now on the
4093 // other side. If gesture is towards gear and not too far snap to the gear.
4094 snapToGear(animView, velocity);
4095 } else {
4096 dismissOrSnapBack(animView, velocity, ev);
4097 }
Mady Mellor5b3cc112016-09-12 15:53:35 -07004098 } else if (((!gestureFastEnough || showGearForSlowOnGoing)
4099 && swipedEnoughToShowGear(animView))
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004100 || gestureTowardsGear) {
4101 // Gear has not been snapped to previously and this is gear revealing gesture
4102 snapToGear(animView, velocity);
4103 } else {
4104 dismissOrSnapBack(animView, velocity, ev);
4105 }
4106 return true;
4107 }
4108
4109 private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
4110 if (isDismissGesture(ev)) {
Mady Mellordc6c97d2016-03-31 14:18:35 -07004111 dismissChild(animView, velocity,
4112 !swipedFastEnough() /* useAccelerateInterpolator */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004113 } else {
4114 snapChild(animView, 0 /* leftTarget */, velocity);
4115 }
4116 }
4117
4118 private void snapToGear(View animView, float velocity) {
4119 final float snapBackThreshold = getSpaceForGear(animView);
4120 final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
4121 : -snapBackThreshold;
4122 mGearExposedView = mTranslatingParentView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004123 if (animView instanceof ExpandableNotificationRow) {
4124 MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
4125 ((ExpandableNotificationRow) animView).getStatusBarNotification()
4126 .getPackageName());
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004127 }
4128 if (mCurrIconRow != null) {
4129 mCurrIconRow.setSnapping(true);
4130 setSnappedToGear(true);
4131 }
4132 onDragCancelled(animView);
Mady Mellor97c8df42016-03-22 18:09:39 -07004133
4134 // If we're on the lockscreen we want to false this.
Selim Cinekbc243a92016-09-27 16:35:13 -07004135 if (isAntiFalsingNeeded()) {
Mady Mellor97c8df42016-03-22 18:09:39 -07004136 mHandler.removeCallbacks(mFalsingCheck);
4137 mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
4138 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004139 super.snapChild(animView, target, velocity);
4140 }
4141
4142 private boolean swipedEnoughToShowGear(View animView) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004143 if (mTranslatingParentView == null) {
4144 return false;
4145 }
Mady Mellorfcba9592016-03-23 11:55:53 -07004146 // If the notification can't be dismissed then how far it can move is
4147 // restricted -- reduce the distance it needs to move in this case.
4148 final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
4149 final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
Mady Mellor4b80b102016-01-22 08:03:58 -08004150 final float translation = getTranslation(animView);
Mady Mellor5b3cc112016-09-12 15:53:35 -07004151 return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
4152 ? translation > snapBackThreshold
4153 : translation < -snapBackThreshold);
Mady Mellor4b80b102016-01-22 08:03:58 -08004154 }
4155
4156 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -08004157 public Animator getViewTranslationAnimator(View v, float target,
4158 AnimatorUpdateListener listener) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07004159 if (v instanceof ExpandableNotificationRow) {
Mady Mellor34958fa2016-02-23 09:52:17 -08004160 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
4161 } else {
4162 return super.getViewTranslationAnimator(v, target, listener);
Mady Mellor4b80b102016-01-22 08:03:58 -08004163 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004164 }
4165
4166 @Override
4167 public void setTranslation(View v, float translate) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07004168 ((ExpandableView) v).setTranslation(translate);
Mady Mellor4b80b102016-01-22 08:03:58 -08004169 }
4170
4171 @Override
4172 public float getTranslation(View v) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07004173 return ((ExpandableView) v).getTranslation();
Mady Mellor4b80b102016-01-22 08:03:58 -08004174 }
4175
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004176 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4177 NotificationGuts guts = mPhoneStatusBar.getExposedGuts();
4178 View view = null;
4179 int height = 0;
4180 if (guts != null) {
4181 // Checking guts
4182 view = guts;
4183 height = guts.getActualHeight();
4184 } else if (mCurrIconRow != null && mCurrIconRow.isVisible()
4185 && mTranslatingParentView != null) {
4186 // Checking gear
4187 view = mTranslatingParentView;
4188 height = ((ExpandableView) mTranslatingParentView).getActualHeight();
4189 }
4190 if (view != null) {
4191 final int rx = (int) ev.getRawX();
4192 final int ry = (int) ev.getRawY();
4193
Selim Cinekfc7086e2016-09-22 14:03:41 -07004194 view.getLocationOnScreen(mTempInt2);
4195 final int x = mTempInt2[0];
4196 final int y = mTempInt2[1];
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004197 Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
Selim Cinekfc7086e2016-09-22 14:03:41 -07004198 if (!rect.contains(rx, ry)) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004199 // Touch was outside visible guts / gear notification, close what's visible
4200 mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
4201 }
4202 }
4203 }
4204
Mady Mellor4b80b102016-01-22 08:03:58 -08004205 /**
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004206 * Returns whether the gesture is towards the gear location or not.
4207 */
4208 private boolean isTowardsGear(float velocity, boolean onLeft) {
4209 if (mCurrIconRow == null) {
4210 return false;
4211 }
4212 return mCurrIconRow.isVisible()
4213 && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
4214 }
4215
4216 /**
4217 * Indicates the the gear has been snapped to.
4218 */
4219 private void setSnappedToGear(boolean snapped) {
4220 mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
4221 mGearSnappedTo = snapped && mCurrIconRow != null;
4222 }
4223
4224 /**
Mady Mellor4b80b102016-01-22 08:03:58 -08004225 * Returns the horizontal space in pixels required to display the gear behind a
4226 * notification.
4227 */
4228 private float getSpaceForGear(View view) {
4229 if (view instanceof ExpandableNotificationRow) {
4230 return ((ExpandableNotificationRow) view).getSpaceForGear();
4231 }
4232 return 0;
4233 }
4234
4235 private void checkForDrag() {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004236 if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
Mady Mellor4b80b102016-01-22 08:03:58 -08004237 mCheckForDrag = new CheckForDrag();
Mady Mellor97c8df42016-03-22 18:09:39 -07004238 mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
Mady Mellor4b80b102016-01-22 08:03:58 -08004239 }
4240 }
4241
4242 private void cancelCheckForDrag() {
4243 if (mCurrIconRow != null) {
4244 mCurrIconRow.cancelFadeAnimator();
4245 }
4246 mHandler.removeCallbacks(mCheckForDrag);
Mady Mellor4b80b102016-01-22 08:03:58 -08004247 }
4248
4249 private final class CheckForDrag implements Runnable {
4250 @Override
4251 public void run() {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004252 if (mTranslatingParentView == null) {
4253 return;
4254 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004255 final float translation = getTranslation(mTranslatingParentView);
4256 final float absTransX = Math.abs(translation);
4257 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
4258 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004259 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
4260 || mCurrIconRow.isIconLocationChange(translation)))
4261 && absTransX >= bounceBackToGearWidth * 0.4
Mady Mellor4b80b102016-01-22 08:03:58 -08004262 && absTransX < notiThreshold) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004263 // Fade in the gear
Mady Mellor4b80b102016-01-22 08:03:58 -08004264 mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
4265 notiThreshold);
Mady Mellor4b80b102016-01-22 08:03:58 -08004266 }
4267 }
4268 }
4269
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004270 public void resetExposedGearView(boolean animate, boolean force) {
4271 if (mGearExposedView == null
4272 || (!force && mGearExposedView == mTranslatingParentView)) {
Mady Mellor4b80b102016-01-22 08:03:58 -08004273 // If no gear is showing or it's showing for this view we do nothing.
4274 return;
4275 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004276 final View prevGearExposedView = mGearExposedView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004277 if (animate) {
4278 Animator anim = getViewTranslationAnimator(prevGearExposedView,
4279 0 /* leftTarget */, null /* updateListener */);
4280 if (anim != null) {
4281 anim.start();
4282 }
4283 } else if (mGearExposedView instanceof ExpandableNotificationRow) {
4284 ((ExpandableNotificationRow) mGearExposedView).resetTranslation();
4285 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004286 mGearExposedView = null;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004287 mGearSnappedTo = false;
Mady Mellor4b80b102016-01-22 08:03:58 -08004288 }
4289 }
4290
Selim Cinek33223572016-02-19 19:32:22 -08004291 private void updateContinuousShadowDrawing() {
4292 boolean continuousShadowUpdate = mAnimationRunning
4293 || !mAmbientState.getDraggedViews().isEmpty();
4294 if (continuousShadowUpdate != mContinuousShadowUpdate) {
4295 if (continuousShadowUpdate) {
4296 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
4297 } else {
4298 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
4299 }
Jorim Jaggi38b5ec92016-04-12 01:39:49 -07004300 mContinuousShadowUpdate = continuousShadowUpdate;
Selim Cinek33223572016-02-19 19:32:22 -08004301 }
4302 }
4303
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004304 public void resetExposedGearView(boolean animate, boolean force) {
4305 mSwipeHelper.resetExposedGearView(animate, force);
4306 }
4307
4308 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4309 mSwipeHelper.closeControlsIfOutsideTouch(ev);
4310 }
4311
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004312 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02004313
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004314 static AnimationFilter[] FILTERS = new AnimationFilter[] {
4315
4316 // ANIMATION_TYPE_ADD
4317 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004318 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004319 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004320 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004321 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004322 .animateZ()
4323 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004324
4325 // ANIMATION_TYPE_REMOVE
4326 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004327 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004328 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004329 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004330 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004331 .animateZ()
4332 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004333
4334 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4335 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004336 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004337 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004338 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004339 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004340 .animateZ()
4341 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004342
4343 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4344 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004345 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004346 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004347 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004348 .animateY()
4349 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004350 .animateZ(),
4351
4352 // ANIMATION_TYPE_START_DRAG
4353 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004354 .animateShadowAlpha(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004355
4356 // ANIMATION_TYPE_SNAP_BACK
4357 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004358 .animateShadowAlpha()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004359 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004360
4361 // ANIMATION_TYPE_ACTIVATED_CHILD
4362 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004363 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004364
4365 // ANIMATION_TYPE_DIMMED
4366 new AnimationFilter()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004367 .animateDimmed(),
4368
4369 // ANIMATION_TYPE_CHANGE_POSITION
4370 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004371 .animateAlpha() // maybe the children change positions
4372 .animateShadowAlpha()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004373 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004374 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004375 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04004376 .animateZ(),
4377
4378 // ANIMATION_TYPE_DARK
4379 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01004380 .animateDark()
4381 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004382
4383 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4384 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004385 .animateShadowAlpha()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004386 .animateHeight()
4387 .animateTopInset()
4388 .animateY()
4389 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02004390 .animateZ()
4391 .hasDelays(),
4392
4393 // ANIMATION_TYPE_HIDE_SENSITIVE
4394 new AnimationFilter()
4395 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02004396
4397 // ANIMATION_TYPE_VIEW_RESIZE
4398 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004399 .animateShadowAlpha()
Selim Cineka5e211b2014-08-11 17:35:48 +02004400 .animateHeight()
4401 .animateTopInset()
4402 .animateY()
4403 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02004404
Selim Cinekb5605e52015-02-20 18:21:41 +01004405 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
4406 new AnimationFilter()
4407 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004408 .animateShadowAlpha()
Selim Cinekb5605e52015-02-20 18:21:41 +01004409 .animateHeight()
4410 .animateTopInset()
4411 .animateY()
4412 .animateZ(),
4413
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004414 // ANIMATION_TYPE_HEADS_UP_APPEAR
4415 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004416 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004417 .animateHeight()
4418 .animateTopInset()
4419 .animateY()
4420 .animateZ(),
4421
4422 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4423 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004424 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004425 .animateHeight()
4426 .animateTopInset()
4427 .animateY()
4428 .animateZ(),
4429
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004430 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4431 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004432 .animateShadowAlpha()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004433 .animateHeight()
4434 .animateTopInset()
4435 .animateY()
4436 .animateZ()
4437 .hasDelays(),
4438
Selim Cineka59ecc32015-04-07 10:51:49 -07004439 // ANIMATION_TYPE_HEADS_UP_OTHER
4440 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004441 .animateShadowAlpha()
Selim Cineka59ecc32015-04-07 10:51:49 -07004442 .animateHeight()
4443 .animateTopInset()
4444 .animateY()
4445 .animateZ(),
4446
Selim Cinekd9acca52014-09-01 22:33:25 +02004447 // ANIMATION_TYPE_EVERYTHING
4448 new AnimationFilter()
4449 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004450 .animateShadowAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02004451 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02004452 .animateDimmed()
4453 .animateHideSensitive()
4454 .animateHeight()
4455 .animateTopInset()
4456 .animateY()
4457 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004458 };
4459
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004460 static int[] LENGTHS = new int[] {
4461
4462 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004463 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004464
4465 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004466 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004467
4468 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4469 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4470
4471 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4472 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4473
4474 // ANIMATION_TYPE_START_DRAG
4475 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4476
4477 // ANIMATION_TYPE_SNAP_BACK
4478 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4479
4480 // ANIMATION_TYPE_ACTIVATED_CHILD
4481 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
4482
4483 // ANIMATION_TYPE_DIMMED
4484 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004485
4486 // ANIMATION_TYPE_CHANGE_POSITION
4487 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04004488
4489 // ANIMATION_TYPE_DARK
4490 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004491
4492 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4493 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02004494
4495 // ANIMATION_TYPE_HIDE_SENSITIVE
4496 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02004497
4498 // ANIMATION_TYPE_VIEW_RESIZE
4499 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02004500
Selim Cinekb5605e52015-02-20 18:21:41 +01004501 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08004502 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01004503
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004504 // ANIMATION_TYPE_HEADS_UP_APPEAR
4505 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
4506
4507 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4508 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4509
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004510 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4511 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4512
Selim Cineka59ecc32015-04-07 10:51:49 -07004513 // ANIMATION_TYPE_HEADS_UP_OTHER
4514 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4515
Selim Cinekd9acca52014-09-01 22:33:25 +02004516 // ANIMATION_TYPE_EVERYTHING
4517 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004518 };
4519
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004520 static final int ANIMATION_TYPE_ADD = 0;
4521 static final int ANIMATION_TYPE_REMOVE = 1;
4522 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
4523 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
4524 static final int ANIMATION_TYPE_START_DRAG = 4;
4525 static final int ANIMATION_TYPE_SNAP_BACK = 5;
4526 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
4527 static final int ANIMATION_TYPE_DIMMED = 7;
4528 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04004529 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004530 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02004531 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02004532 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01004533 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004534 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
4535 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004536 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
4537 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
4538 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004539
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004540 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
4541 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
4542
Selim Cinek572bbd42014-04-25 16:43:27 +02004543 final long eventStartTime;
4544 final View changingView;
4545 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004546 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004547 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004548 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004549 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07004550 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02004551
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004552 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02004553 this(view, type, LENGTHS[type]);
4554 }
4555
4556 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02004557 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
4558 changingView = view;
4559 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004560 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02004561 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004562 }
4563
4564 /**
4565 * Combines the length of several animation events into a single value.
4566 *
4567 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004568 * @return The combined length. Depending on the event types, this might be the maximum of
4569 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004570 */
4571 static long combineLength(ArrayList<AnimationEvent> events) {
4572 long length = 0;
4573 int size = events.size();
4574 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004575 AnimationEvent event = events.get(i);
4576 length = Math.max(length, event.length);
4577 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
4578 return event.length;
4579 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004580 }
4581 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02004582 }
4583 }
4584
Selim Cinek67b22602014-03-10 15:40:16 +01004585}