blob: b5ec398ffdd7523016f110da7cbe8054f9e959d1 [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 Cinek11e33232016-08-05 15:30:53 -070044import android.view.InputDevice;
Selim Cinek67b22602014-03-10 15:40:16 +010045import android.view.MotionEvent;
46import android.view.VelocityTracker;
47import android.view.View;
48import android.view.ViewConfiguration;
49import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020050import android.view.ViewTreeObserver;
Adrian Roos5153d4a2016-03-22 10:01:56 -070051import android.view.WindowInsets;
Selim Cinekc22fff62016-05-20 12:44:30 -070052import android.view.accessibility.AccessibilityEvent;
53import android.view.accessibility.AccessibilityNodeInfo;
Selim Cinek572bbd42014-04-25 16:43:27 +020054import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080055import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010056import android.widget.OverScroller;
Selim Cinek41fe89a2016-06-02 15:27:56 -070057import android.widget.ScrollView;
Jorim Jaggi56306252014-07-03 00:40:09 +020058
Mady Mellora41587b2016-02-11 18:43:06 -080059import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010060import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Selim Cinek67b22602014-03-10 15:40:16 +010061import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080062import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010063import com.android.systemui.R;
64import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070065import com.android.systemui.classifier.FalsingManager;
Mady Mellor43c2cd12016-12-12 21:05:13 -080066import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
67import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
Selim Cineka32ab602014-06-11 15:06:01 +020068import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040069import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020070import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010071import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020072import com.android.systemui.statusbar.ExpandableView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -070073import com.android.systemui.statusbar.NotificationGuts;
Mady Mellor761cde12017-01-10 11:36:39 -080074import com.android.systemui.statusbar.NotificationMenuRow;
Selim Cinek0cfbef42016-11-09 19:06:36 -080075import com.android.systemui.statusbar.NotificationShelf;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010076import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020077import com.android.systemui.statusbar.StatusBarState;
Selim Cinek33223572016-02-19 19:32:22 -080078import com.android.systemui.statusbar.notification.FakeShadowView;
Selim Cinek42357e02016-02-24 18:48:01 -080079import com.android.systemui.statusbar.notification.NotificationUtils;
Selim Cineka7d4f822016-12-06 14:34:47 -080080import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
Selim Cinekb5605e52015-02-20 18:21:41 +010081import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020082import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070083import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070084import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010085import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010086
Selim Cinek572bbd42014-04-25 16:43:27 +020087import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -080088import java.util.Collections;
89import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020090import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020091
Selim Cinek67b22602014-03-10 15:40:16 +010092/**
93 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
94 */
95public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020096 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Mady Mellor4b80b102016-01-22 08:03:58 -080097 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
Mady Mellor43c2cd12016-12-12 21:05:13 -080098 NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer,
99 VisibilityLocationProvider {
Selim Cinek67b22602014-03-10 15:40:16 +0100100
Selim Cinekd35c2792016-01-21 13:20:57 -0800101 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -0800102 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +0100103 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200104 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
105 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200106 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +0100107 /**
108 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
109 */
110 private static final int INVALID_POINTER = -1;
111
Selim Cinek1408eb52014-06-02 14:45:38 +0200112 private ExpandHelper mExpandHelper;
Selim Cinek33223572016-02-19 19:32:22 -0800113 private NotificationSwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +0200114 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +0100115 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800116 private final Paint mBackgroundPaint = new Paint();
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100117
Selim Cinekbc243a92016-09-27 16:35:13 -0700118 private float mExpandedHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100119 private int mOwnScrollY;
120 private int mMaxLayoutHeight;
121
122 private VelocityTracker mVelocityTracker;
123 private OverScroller mScroller;
Ricky Waicd35def2016-05-03 11:07:07 +0100124 private Runnable mFinishScrollingCallback;
Selim Cinek67b22602014-03-10 15:40:16 +0100125 private int mTouchSlop;
126 private int mMinimumVelocity;
127 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100128 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200129 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100130 private boolean mIsBeingDragged;
131 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200132 private int mDownX;
Dong-wan Kimb9266662016-09-21 13:08:30 -0700133 private int mActivePointerId = INVALID_POINTER;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100134 private boolean mTouchIsClick;
135 private float mInitialTouchX;
136 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100137
Selim Cinek67b22602014-03-10 15:40:16 +0100138 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100139 private int mContentHeight;
140 private int mCollapsedSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100141 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800142 private int mIncreasedPaddingBetweenElements;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200143 private int mTopPadding;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700144 private int mBottomInset = 0;
Selim Cinek67b22602014-03-10 15:40:16 +0100145
146 /**
147 * The algorithm which calculates the properties for our children
148 */
Muyuan Li87798022016-04-07 17:51:25 -0700149 protected final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100150
151 /**
152 * The current State this Layout is in
153 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200154 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Selim Cinek281c2022016-10-13 19:14:43 -0700155 private final AmbientState mAmbientState;
Selim Cinekb5605e52015-02-20 18:21:41 +0100156 private NotificationGroupManager mGroupManager;
Selim Cinek3776fe02016-02-04 13:32:43 -0800157 private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700158 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
159 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
160 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
161 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
162 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200163 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700164 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
165 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200166 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200167 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200168 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800169 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200170
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200171 /**
172 * The raw amount of the overScroll on the top, which is not rubber-banded.
173 */
174 private float mOverScrolledTopPixels;
175
176 /**
177 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
178 */
179 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200180 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200181 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200182 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100183 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200184 private boolean mNeedsAnimation;
185 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200186 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200187 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400188 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100189 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200190 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200191 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200192 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200193 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200194 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700195 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200196 private boolean mExpandingNotification;
197 private boolean mExpandedInThisMotion;
Muyuan Li84b45612016-06-01 11:05:08 -0700198 protected boolean mScrollingEnabled;
Muyuan Li8cf63dc2016-05-18 17:40:58 -0700199 protected DismissView mDismissView;
Muyuan Lidd9ae752016-05-13 16:45:43 -0700200 protected EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400201 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200202
203 /**
204 * Was the scroller scrolled to the top when the down motion was observed?
205 */
206 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200207 /**
208 * The minimal amount of over scroll which is needed in order to switch to the quick settings
209 * when over scrolling on a expanded card.
210 */
211 private float mMinTopOverScrollToEscape;
212 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700213 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200214 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200215 private boolean mDontReportNextOverScroll;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700216 private boolean mDontClampNextScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200217 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100218 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700219 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700220
Selim Cinek1408eb52014-06-02 14:45:38 +0200221 /**
222 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
223 * This is needed to avoid scrolling too far after the notification was collapsed in the same
224 * motion.
225 */
226 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400227 private SwipeHelper.LongPressListener mLongPressListener;
Mady Mellor4b80b102016-01-22 08:03:58 -0800228
Mady Mellor761cde12017-01-10 11:36:39 -0800229 private NotificationMenuRow mCurrIconRow;
Mady Mellor4b80b102016-01-22 08:03:58 -0800230 private View mTranslatingParentView;
231 private View mGearExposedView;
Selim Cinek1408eb52014-06-02 14:45:38 +0200232
233 /**
234 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
235 * animating.
236 */
237 private boolean mOnlyScrollingInThisMotion;
Adrian Roosfa139752016-04-27 09:59:08 -0700238 private boolean mDisallowDismissInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200239 private boolean mInterceptDelegateEnabled;
240 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700241 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700242 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200243 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200244 = new ViewTreeObserver.OnPreDrawListener() {
245 @Override
246 public boolean onPreDraw() {
Adrian Roos181385c2016-05-05 17:45:44 -0400247 updateForcedScroll();
Selim Cinek1f553cf2014-05-02 12:01:36 +0200248 updateChildren();
249 mChildrenUpdateRequested = false;
250 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200251 return true;
252 }
253 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200254 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100255 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100256 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700257 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700258 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700259 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
260 = new HashSet<>();
261 private HeadsUpManager mHeadsUpManager;
262 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700263 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200264 private boolean mForceNoOverlappingRendering;
Selim Cineke0890e52015-06-17 11:17:08 -0700265 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700266 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800267 private boolean mAnimationRunning;
Selim Cinekc383fd02016-10-21 15:31:26 -0700268 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
Selim Cinek6811d722016-01-19 17:53:12 -0800269 = new ViewTreeObserver.OnPreDrawListener() {
270 @Override
271 public boolean onPreDraw() {
Selim Cinekc383fd02016-10-21 15:31:26 -0700272 onPreDrawDuringAnimation();
Selim Cinek6811d722016-01-19 17:53:12 -0800273 return true;
274 }
275 };
276 private Rect mBackgroundBounds = new Rect();
Selim Cinek614576e2016-01-20 10:54:09 -0800277 private Rect mStartAnimationRect = new Rect();
278 private Rect mEndAnimationRect = new Rect();
Selim Cinekd35c2792016-01-21 13:20:57 -0800279 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
Selim Cinek614576e2016-01-20 10:54:09 -0800280 private boolean mAnimateNextBackgroundBottom;
281 private boolean mAnimateNextBackgroundTop;
282 private ObjectAnimator mBottomAnimator = null;
283 private ObjectAnimator mTopAnimator = null;
284 private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
285 private ActivatableNotificationView mLastVisibleBackgroundChild = null;
Selim Cinekd35c2792016-01-21 13:20:57 -0800286 private int mBgColor;
287 private float mDimAmount;
288 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800289 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Selim Cinekd35c2792016-01-21 13:20:57 -0800290 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
291 @Override
292 public void onAnimationEnd(Animator animation) {
293 mDimAnimator = null;
294 }
295 };
296 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
297 = new ValueAnimator.AnimatorUpdateListener() {
298
299 @Override
300 public void onAnimationUpdate(ValueAnimator animation) {
301 setDimAmount((Float) animation.getAnimatedValue());
302 }
303 };
Muyuan Li4fe4a402016-03-30 16:50:11 -0700304 protected ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800305 private boolean mContinuousShadowUpdate;
306 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
307 = new ViewTreeObserver.OnPreDrawListener() {
308
309 @Override
310 public boolean onPreDraw() {
311 updateViewShadows();
312 return true;
313 }
314 };
315 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
316 @Override
317 public int compare(ExpandableView view, ExpandableView otherView) {
318 float endY = view.getTranslationY() + view.getActualHeight();
319 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
320 if (endY < otherEndY) {
321 return -1;
322 } else if (endY > otherEndY) {
323 return 1;
324 } else {
325 // The two notifications end at the same location
326 return 0;
327 }
328 }
329 };
Selim Cinek25503252016-03-03 15:31:43 -0800330 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
Selim Cinekcd5b22f2016-03-08 16:15:41 -0800331 private boolean mPulsing;
Selim Cinek31d37b92016-04-26 09:56:42 -0700332 private boolean mDrawBackgroundAsSrc;
Selim Cinek07304f5222016-05-19 18:31:36 -0700333 private boolean mFadingOut;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800334 private boolean mParentNotFullyVisible;
Selim Cinek1b2a05e2016-04-28 14:20:39 -0700335 private boolean mGroupExpandedForMeasure;
Selim Cinekc22fff62016-05-20 12:44:30 -0700336 private boolean mScrollable;
Adrian Roos181385c2016-05-05 17:45:44 -0400337 private View mForcedScroll;
Selim Cinek972123d2016-05-03 14:25:58 -0700338 private float mBackgroundFadeAmount = 1.0f;
339 private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
340 new FloatProperty<NotificationStackScrollLayout>("backgroundFade") {
341 @Override
342 public void setValue(NotificationStackScrollLayout object, float value) {
343 object.setBackgroundFadeAmount(value);
344 }
345
346 @Override
347 public Float get(NotificationStackScrollLayout object) {
348 return object.getBackgroundFadeAmount();
349 }
350 };
Selim Cinekbc243a92016-09-27 16:35:13 -0700351 private boolean mQsExpanded;
Selim Cinekef406062016-09-29 17:33:13 -0700352 private boolean mForwardScrollable;
353 private boolean mBackwardScrollable;
Selim Cinek281c2022016-10-13 19:14:43 -0700354 private NotificationShelf mShelf;
Selim Cinekad7fac02016-10-18 17:09:15 -0700355 private int mMaxDisplayedNotifications = -1;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800356 private int mStatusBarHeight;
357 private boolean mNoAmbient;
358 private final Rect mClipRect = new Rect();
359 private boolean mIsClipped;
Selim Cinekcafa87f2016-10-26 17:00:17 -0700360 private Rect mRequestedClipBounds;
361 private boolean mInHeadsUpPinnedMode;
362 private boolean mHeadsUpAnimatingAway;
Selim Cinek355652a2016-12-07 13:32:12 -0800363 private int mStatusBarState;
Selim Cinekfb6ee6d2016-12-29 16:49:26 +0100364 private int mCachedBackgroundColor;
Selim Cinek67b22602014-03-10 15:40:16 +0100365
366 public NotificationStackScrollLayout(Context context) {
367 this(context, null);
368 }
369
370 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
371 this(context, attrs, 0);
372 }
373
374 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
375 this(context, attrs, defStyleAttr, 0);
376 }
377
378 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
379 int defStyleRes) {
380 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinek281c2022016-10-13 19:14:43 -0700381 mAmbientState = new AmbientState(context);
Selim Cinekd35c2792016-01-21 13:20:57 -0800382 mBgColor = context.getColor(R.color.notification_shade_background_color);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200383 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
384 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
385 mExpandHelper = new ExpandHelper(getContext(), this,
386 minHeight, maxHeight);
387 mExpandHelper.setEventSource(this);
388 mExpandHelper.setScrollAdapter(this);
Mady Mellor4b80b102016-01-22 08:03:58 -0800389 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
Selim Cinek1cf41c12014-08-12 20:06:19 +0200390 mSwipeHelper.setLongPressListener(mLongPressListener);
Muyuan Li333a4fc2016-04-16 17:13:46 -0700391 mStackScrollAlgorithm = createStackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100392 initView(context);
Selim Cinek6811d722016-01-19 17:53:12 -0800393 setWillNotDraw(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100394 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100395 mDebugPaint = new Paint();
396 mDebugPaint.setColor(0xffff0000);
397 mDebugPaint.setStrokeWidth(2);
398 mDebugPaint.setStyle(Paint.Style.STROKE);
399 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700400 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100401 }
402
403 @Override
Mady Mellor43c2cd12016-12-12 21:05:13 -0800404 public void onMenuClicked(View view, int x, int y, MenuItem item) {
405 if (mLongPressListener == null) {
406 return;
407 }
408 if (view instanceof ExpandableNotificationRow) {
409 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Mady Mellora41587b2016-02-11 18:43:06 -0800410 MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
411 row.getStatusBarNotification().getPackageName());
Mady Mellor4b80b102016-01-22 08:03:58 -0800412 }
Mady Mellor43c2cd12016-12-12 21:05:13 -0800413 mLongPressListener.onLongPress(view, x, y, item);
Mady Mellor4b80b102016-01-22 08:03:58 -0800414 }
415
416 @Override
Mady Mellor43c2cd12016-12-12 21:05:13 -0800417 public void onMenuReset(View row) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -0700418 if (mTranslatingParentView != null && row == mTranslatingParentView) {
419 mSwipeHelper.setSnappedToGear(false);
420 mGearExposedView = null;
421 mTranslatingParentView = null;
422 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000423 }
424
425 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100426 protected void onDraw(Canvas canvas) {
Selim Cinekd381bc32016-08-15 12:40:57 -0700427 if (mCurrentBounds.top < mCurrentBounds.bottom) {
428 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
429 mBackgroundPaint);
430 }
Selim Cinek67b22602014-03-10 15:40:16 +0100431 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800432 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100433 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Mady Mellor43c2cd12016-12-12 21:05:13 -0800434 y = getLayoutHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100435 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200436 y = getHeight() - getEmptyBottomMargin();
437 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100438 }
439 }
440
Selim Cinekd35c2792016-01-21 13:20:57 -0800441 private void updateBackgroundDimming() {
442 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
Selim Cinek972123d2016-05-03 14:25:58 -0700443 alpha *= mBackgroundFadeAmount;
Selim Cinekd35c2792016-01-21 13:20:57 -0800444 // We need to manually blend in the background color
445 int scrimColor = mScrimController.getScrimBehindColor();
446 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
447 float alphaInv = 1 - alpha;
448 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
Selim Cinek972123d2016-05-03 14:25:58 -0700449 (int) (mBackgroundFadeAmount * Color.red(mBgColor)
450 + alphaInv * Color.red(scrimColor)),
451 (int) (mBackgroundFadeAmount * Color.green(mBgColor)
452 + alphaInv * Color.green(scrimColor)),
453 (int) (mBackgroundFadeAmount * Color.blue(mBgColor)
454 + alphaInv * Color.blue(scrimColor)));
Selim Cinekfb6ee6d2016-12-29 16:49:26 +0100455 if (mCachedBackgroundColor != color) {
456 mCachedBackgroundColor = color;
457 mBackgroundPaint.setColor(color);
458 invalidate();
459 }
Selim Cinekd35c2792016-01-21 13:20:57 -0800460 }
461
Selim Cinek67b22602014-03-10 15:40:16 +0100462 private void initView(Context context) {
463 mScroller = new OverScroller(getContext());
Selim Cinek67b22602014-03-10 15:40:16 +0100464 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200465 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100466 final ViewConfiguration configuration = ViewConfiguration.get(context);
467 mTouchSlop = configuration.getScaledTouchSlop();
468 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
469 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100470 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100471 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200472 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800473 mStackScrollAlgorithm.initView(context);
Selim Cinek281c2022016-10-13 19:14:43 -0700474 mAmbientState.reload(context);
Selim Cinek61633a82016-01-25 15:54:10 -0800475 mPaddingBetweenElements = Math.max(1, context.getResources()
Selim Cinekcacc6042016-01-21 16:16:41 -0800476 .getDimensionPixelSize(R.dimen.notification_divider_height));
Selim Cinek61633a82016-01-25 15:54:10 -0800477 mIncreasedPaddingBetweenElements = context.getResources()
478 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
Selim Cinek1408eb52014-06-02 14:45:38 +0200479 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
480 R.dimen.min_top_overscroll_to_qs);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800481 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
Selim Cineka5eaa602014-05-12 21:27:47 +0200482 }
483
Selim Cinek25503252016-03-03 15:31:43 -0800484 public void setDrawBackgroundAsSrc(boolean asSrc) {
Selim Cinek31d37b92016-04-26 09:56:42 -0700485 mDrawBackgroundAsSrc = asSrc;
486 updateSrcDrawing();
487 }
488
489 private void updateSrcDrawing() {
Selim Cinek48ff9b42016-11-09 19:31:51 -0800490 mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible
Selim Cinek07304f5222016-05-19 18:31:36 -0700491 ? mSrcMode : null);
Selim Cinek25503252016-03-03 15:31:43 -0800492 invalidate();
493 }
494
Selim Cinekaef92ef2014-06-06 18:06:04 +0200495 private void notifyHeightChangeListener(ExpandableView view) {
496 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100497 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200498 }
Selim Cinek67b22602014-03-10 15:40:16 +0100499 }
500
501 @Override
502 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
503 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -0400504 // We need to measure all children even the GONE ones, such that the heights are calculated
505 // correctly as they are used to calculate how many we can fit on the screen.
506 final int size = getChildCount();
507 for (int i = 0; i < size; i++) {
508 measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
509 }
Selim Cinek67b22602014-03-10 15:40:16 +0100510 }
511
512 @Override
513 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinek67b22602014-03-10 15:40:16 +0100514 // we layout all our children centered on the top
515 float centerX = getWidth() / 2.0f;
516 for (int i = 0; i < getChildCount(); i++) {
517 View child = getChildAt(i);
Selim Cinekfa760d42016-05-10 15:50:53 -0400518 // We need to layout all children even the GONE ones, such that the heights are
519 // calculated correctly as they are used to calculate how many we can fit on the screen
Selim Cinek67b22602014-03-10 15:40:16 +0100520 float width = child.getMeasuredWidth();
521 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100522 child.layout((int) (centerX - width / 2.0f),
523 0,
524 (int) (centerX + width / 2.0f),
525 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100526 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200527 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100528 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200529 clampScrollPosition();
Selim Cinek319bdc42014-05-01 23:01:58 +0200530 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -0800531 updateFirstAndLastBackgroundViews();
Selim Cinekbc243a92016-09-27 16:35:13 -0700532 updateAlgorithmLayoutMinHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100533 }
534
Selim Cinek5bc852a2015-12-21 12:19:09 -0800535 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
536 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200537 mNeedViewResizeAnimation = true;
538 mNeedsAnimation = true;
539 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200540 }
541
Selim Cinekdb167372016-11-17 15:41:17 -0800542 public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) {
543 mAmbientState.setSpeedBumpIndex(newIndex);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800544 mNoAmbient = noAmbient;
Selim Cinekc27437b2014-05-14 10:23:33 +0200545 }
546
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200547 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
548 mListener = listener;
549 }
550
Selim Cineka7d4f822016-12-06 14:34:47 -0800551 @Override
552 public boolean isInVisibleLocation(ExpandableNotificationRow row) {
553 ExpandableViewState childViewState = mCurrentStackScrollState.getViewStateForView(row);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200554 if (childViewState == null) {
Selim Cineka7d4f822016-12-06 14:34:47 -0800555 return false;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200556 }
Selim Cineka7d4f822016-12-06 14:34:47 -0800557 if ((childViewState.location &= ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
558 return false;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100559 }
Selim Cineka7d4f822016-12-06 14:34:47 -0800560 if (row.getVisibility() != View.VISIBLE) {
561 return false;
562 }
563 return true;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200564 }
565
Selim Cinek67b22602014-03-10 15:40:16 +0100566 private void setMaxLayoutHeight(int maxLayoutHeight) {
567 mMaxLayoutHeight = maxLayoutHeight;
Selim Cinek9458b192016-10-25 19:02:42 -0700568 mShelf.setMaxLayoutHeight(maxLayoutHeight);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200569 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100570 }
571
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200572 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700573 mAmbientState.setLayoutHeight(getLayoutHeight());
Selim Cinekbc243a92016-09-27 16:35:13 -0700574 updateAlgorithmLayoutMinHeight();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700575 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100576 }
577
Selim Cinekbc243a92016-09-27 16:35:13 -0700578 private void updateAlgorithmLayoutMinHeight() {
579 mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() ? getLayoutMinHeight() : 0);
580 }
581
Selim Cinek67b22602014-03-10 15:40:16 +0100582 /**
583 * Updates the children views according to the stack scroll algorithm. Call this whenever
584 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
585 */
586 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -0800587 updateScrollStateForAddedChildren();
Selim Cinek727903c2016-12-06 17:28:10 -0800588 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
589 ? 0
590 : mScroller.getCurrVelocity());
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200591 mAmbientState.setScrollY(mOwnScrollY);
592 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200593 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200594 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100595 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200596 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100597 }
598 }
599
Selim Cinekc383fd02016-10-21 15:31:26 -0700600 private void onPreDrawDuringAnimation() {
Selim Cineka686b2c2016-10-26 13:58:27 -0700601 mShelf.updateAppearance();
Selim Cinekc383fd02016-10-21 15:31:26 -0700602 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
603 updateBackground();
604 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700605 }
606
Selim Cinek3776fe02016-02-04 13:32:43 -0800607 private void updateScrollStateForAddedChildren() {
608 if (mChildrenToAddAnimated.isEmpty()) {
609 return;
610 }
611 for (int i = 0; i < getChildCount(); i++) {
612 ExpandableView child = (ExpandableView) getChildAt(i);
613 if (mChildrenToAddAnimated.contains(child)) {
614 int startingPosition = getPositionInLinearLayout(child);
Selim Cinek42357e02016-02-24 18:48:01 -0800615 int padding = child.getIncreasedPaddingAmount() == 1.0f
Selim Cinek3776fe02016-02-04 13:32:43 -0800616 ? mIncreasedPaddingBetweenElements :
617 mPaddingBetweenElements;
618 int childHeight = getIntrinsicHeight(child) + padding;
619 if (startingPosition < mOwnScrollY) {
620 // This child starts off screen, so let's keep it offscreen to keep the others visible
621
Selim Cinekef406062016-09-29 17:33:13 -0700622 setOwnScrollY(mOwnScrollY + childHeight);
Selim Cinek3776fe02016-02-04 13:32:43 -0800623 }
624 }
625 }
626 clampScrollPosition();
627 }
628
Adrian Roos181385c2016-05-05 17:45:44 -0400629 private void updateForcedScroll() {
630 if (mForcedScroll != null && (!mForcedScroll.hasFocus()
631 || !mForcedScroll.isAttachedToWindow())) {
632 mForcedScroll = null;
633 }
634 if (mForcedScroll != null) {
635 ExpandableView expandableView = (ExpandableView) mForcedScroll;
636 int positionInLinearLayout = getPositionInLinearLayout(expandableView);
637 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
Adrian Roos4a579672016-05-24 16:54:37 -0700638 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Adrian Roos181385c2016-05-05 17:45:44 -0400639
640 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
Adrian Roos4a579672016-05-24 16:54:37 -0700641
642 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
643 // that it is not visible anymore.
644 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
Selim Cinekef406062016-09-29 17:33:13 -0700645 setOwnScrollY(targetScroll);
Adrian Roos181385c2016-05-05 17:45:44 -0400646 }
647 }
648 }
649
Selim Cinek319bdc42014-05-01 23:01:58 +0200650 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200651 if (!mChildrenUpdateRequested) {
652 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
653 mChildrenUpdateRequested = true;
654 invalidate();
655 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200656 }
657
Selim Cinek67b22602014-03-10 15:40:16 +0100658 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200659 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100660 }
661
Selim Cinekf7a14c02014-07-07 14:01:46 +0200662 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100663 int scrollRange = getScrollRange();
664 if (scrollRange < mOwnScrollY) {
Selim Cinekef406062016-09-29 17:33:13 -0700665 setOwnScrollY(scrollRange);
Selim Cinek67b22602014-03-10 15:40:16 +0100666 }
667 }
668
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200669 public int getTopPadding() {
670 return mTopPadding;
671 }
672
Selim Cinek1408eb52014-06-02 14:45:38 +0200673 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200674 if (mTopPadding != topPadding) {
675 mTopPadding = topPadding;
676 updateAlgorithmHeightAndPadding();
677 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200678 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200679 mTopPaddingNeedsAnimation = true;
680 mNeedsAnimation = true;
681 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200682 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200683 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200684 }
685 }
686
687 /**
Selim Cinekbc243a92016-09-27 16:35:13 -0700688 * Update the height of the panel.
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200689 *
Selim Cinekbc243a92016-09-27 16:35:13 -0700690 * @param height the expanded height of the panel
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200691 */
Selim Cinekbc243a92016-09-27 16:35:13 -0700692 public void setExpandedHeight(float height) {
693 mExpandedHeight = height;
Selim Cinekcafa87f2016-10-26 17:00:17 -0700694 setIsExpanded(height > 0);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800695 int minExpansionHeight = getMinExpansionHeight();
696 if (height < minExpansionHeight) {
697 mClipRect.left = 0;
698 mClipRect.right = getWidth();
699 mClipRect.top = 0;
700 mClipRect.bottom = (int) height;
701 height = minExpansionHeight;
Selim Cinekcafa87f2016-10-26 17:00:17 -0700702 setRequestedClipBounds(mClipRect);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800703 } else {
Selim Cinekcafa87f2016-10-26 17:00:17 -0700704 setRequestedClipBounds(null);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800705 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200706 int stackHeight;
Selim Cinek94c2d822016-07-13 18:50:04 -0700707 float translationY;
708 float appearEndPosition = getAppearEndPosition();
709 float appearStartPosition = getAppearStartPosition();
710 if (height >= appearEndPosition) {
Selim Cinekbc243a92016-09-27 16:35:13 -0700711 translationY = 0;
Selim Cinek94c2d822016-07-13 18:50:04 -0700712 stackHeight = (int) height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200713 } else {
Selim Cinek94c2d822016-07-13 18:50:04 -0700714 float appearFraction = getAppearFraction(height);
715 if (appearFraction >= 0) {
716 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
717 appearFraction);
718 } else {
719 // This may happen when pushing up a heads up. We linearly push it up from the
720 // start
721 translationY = height - appearStartPosition + getExpandTranslationStart();
722 }
723 stackHeight = (int) (height - translationY);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200724 }
725 if (stackHeight != mCurrentStackHeight) {
726 mCurrentStackHeight = stackHeight;
727 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200728 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200729 }
Selim Cinek94c2d822016-07-13 18:50:04 -0700730 setStackTranslation(translationY);
Selim Cinekcafa87f2016-10-26 17:00:17 -0700731 }
732
733 private void setRequestedClipBounds(Rect clipRect) {
734 mRequestedClipBounds = clipRect;
735 updateClipping();
736 }
737
738 public void updateClipping() {
739 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
740 && !mHeadsUpAnimatingAway;
741 if (mIsClipped != clipped) {
742 mIsClipped = clipped;
743 updateFadingState();
744 }
745 if (clipped) {
746 setClipBounds(mRequestedClipBounds);
747 } else {
748 setClipBounds(null);
749 }
Selim Cinek94c2d822016-07-13 18:50:04 -0700750 }
751
752 /**
753 * @return The translation at the beginning when expanding.
754 * Measured relative to the resting position.
755 */
756 private float getExpandTranslationStart() {
Selim Cinek48ff9b42016-11-09 19:31:51 -0800757 return - mTopPadding;
Selim Cinek94c2d822016-07-13 18:50:04 -0700758 }
759
760 /**
761 * @return the position from where the appear transition starts when expanding.
762 * Measured in absolute height.
763 */
764 private float getAppearStartPosition() {
Selim Cinekd127d792016-11-01 19:11:41 -0700765 if (mTrackingHeadsUp && mFirstVisibleBackgroundChild != null) {
766 if (mFirstVisibleBackgroundChild.isAboveShelf()) {
767 // If we ever expanded beyond the first notification, it's allowed to merge into
768 // the shelf
769 return mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight();
770 }
771 }
Selim Cinek48ff9b42016-11-09 19:31:51 -0800772 return getMinExpansionHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -0700773 }
774
775 /**
776 * @return the position from where the appear transition ends when expanding.
777 * Measured in absolute height.
778 */
779 private float getAppearEndPosition() {
Selim Cinekaa417da2016-10-27 18:17:08 -0700780 int appearPosition;
Selim Cinekcde90e52016-12-22 21:01:49 +0100781 if (mEmptyShadeView.getVisibility() == GONE) {
782 int minNotificationsForShelf = 1;
783 if (mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) {
784 appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
785 minNotificationsForShelf = 2;
786 } else {
787 appearPosition = 0;
788 }
789 if (getNotGoneChildCount() >= minNotificationsForShelf) {
790 appearPosition += mShelf.getIntrinsicHeight();
791 }
Selim Cinekaa417da2016-10-27 18:17:08 -0700792 } else {
Selim Cinekcde90e52016-12-22 21:01:49 +0100793 appearPosition = mEmptyShadeView.getHeight();
Selim Cinekaa417da2016-10-27 18:17:08 -0700794 }
795 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
Selim Cinek94c2d822016-07-13 18:50:04 -0700796 }
797
798 /**
799 * @param height the height of the panel
800 * @return the fraction of the appear animation that has been performed
801 */
802 public float getAppearFraction(float height) {
803 float appearEndPosition = getAppearEndPosition();
804 float appearStartPosition = getAppearStartPosition();
805 return (height - appearStartPosition)
806 / (appearEndPosition - appearStartPosition);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700807 }
808
Selim Cinekd2281152015-04-10 14:37:46 -0700809 public float getStackTranslation() {
810 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700811 }
812
Selim Cinekd2281152015-04-10 14:37:46 -0700813 private void setStackTranslation(float stackTranslation) {
814 if (stackTranslation != mStackTranslation) {
815 mStackTranslation = stackTranslation;
816 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700817 requestChildrenUpdate();
818 }
Selim Cinek67b22602014-03-10 15:40:16 +0100819 }
820
821 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100822 * 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 +0100823 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
824 *
825 * @return either the layout height or the externally defined height, whichever is smaller
826 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200827 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100828 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
829 }
830
Selim Cinek816c8e42015-11-19 12:00:45 -0800831 public int getFirstItemMinHeight() {
832 final ExpandableView firstChild = getFirstChildNotGone();
833 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100834 }
835
Dan Sandler4247a5c2014-07-23 15:58:08 -0400836 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100837 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400838 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100839 }
840
Jason Monk16ac3772016-02-10 15:39:21 -0500841 public void setQsContainer(ViewGroup qsContainer) {
842 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +0200843 }
844
Mady Mellorb0a82462016-04-30 17:31:02 -0700845 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100846 public void onChildDismissed(View v) {
Selim Cinekd1395642016-04-28 12:22:42 -0700847 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
848 if (!row.isDismissed()) {
849 handleChildDismissed(v);
850 }
851 ViewGroup transientContainer = row.getTransientContainer();
852 if (transientContainer != null) {
853 transientContainer.removeTransientView(v);
854 }
855 }
856
857 private void handleChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400858 if (mDismissAllInProgress) {
859 return;
860 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100861 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200862 if (mDragAnimPendingChildren.contains(v)) {
863 // We start the swipe and finish it in the same frame, we don't want any animation
864 // for the drag
865 mDragAnimPendingChildren.remove(v);
866 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200867 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200868 mAmbientState.onDragFinished(v);
Selim Cinek33223572016-02-19 19:32:22 -0800869 updateContinuousShadowDrawing();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700870 if (v instanceof ExpandableNotificationRow) {
871 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
872 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700873 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700874 }
875 }
Selim Cineke9bad242016-06-15 11:46:37 -0700876 performDismiss(v, mGroupManager, false /* fromAccessibility */);
Blazej Magnowski72323322015-07-24 11:49:40 -0700877
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700878 mFalsingManager.onNotificationDismissed();
879 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700880 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
Felipe Lemeee5d6302016-04-22 16:11:19 -0700881 false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
Blazej Magnowski72323322015-07-24 11:49:40 -0700882 }
Selim Cinekeb973562014-05-02 17:07:49 +0200883 }
884
Selim Cineke9bad242016-06-15 11:46:37 -0700885 public static void performDismiss(View v, NotificationGroupManager groupManager,
886 boolean fromAccessibility) {
Selim Cinek9e624e72016-07-20 13:46:49 -0700887 if (!(v instanceof ExpandableNotificationRow)) {
888 return;
Selim Cinek2a739342016-03-17 10:28:55 -0700889 }
Selim Cinek9e624e72016-07-20 13:46:49 -0700890 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
891 if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
892 ExpandableNotificationRow groupSummary =
893 groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
894 if (groupSummary.isClearable()) {
895 performDismiss(groupSummary, groupManager, fromAccessibility);
896 }
897 }
898 row.setDismissed(true, fromAccessibility);
899 if (row.isClearable()) {
900 row.performDismiss();
Selim Cinek2a739342016-03-17 10:28:55 -0700901 }
902 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
903 }
904
Selim Cinekeb973562014-05-02 17:07:49 +0200905 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800906 public void onChildSnappedBack(View animView, float targetLeft) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200907 mAmbientState.onDragFinished(animView);
Selim Cinek33223572016-02-19 19:32:22 -0800908 updateContinuousShadowDrawing();
Selim Cinekeb973562014-05-02 17:07:49 +0200909 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200910 if (mAnimationsEnabled) {
911 mSnappedBackChildren.add(animView);
912 mNeedsAnimation = true;
913 }
Selim Cinekeb973562014-05-02 17:07:49 +0200914 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200915 } else {
916 // We start the swipe and snap back in the same frame, we don't want any animation
917 mDragAnimPendingChildren.remove(animView);
918 }
Mady Mellor7a9b2a62016-03-23 07:41:47 -0700919 if (mCurrIconRow != null && targetLeft == 0) {
Mady Mellor43c2cd12016-12-12 21:05:13 -0800920 mCurrIconRow.resetState(true /* notify */);
Mady Mellor7a9b2a62016-03-23 07:41:47 -0700921 mCurrIconRow = null;
Mady Mellor4b80b102016-01-22 08:03:58 -0800922 }
Selim Cinek67b22602014-03-10 15:40:16 +0100923 }
924
Adrian Roos5d9cc662014-05-28 17:08:13 +0200925 @Override
926 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700927 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700928 mScrimController.setTopHeadsUpDragAmount(animView,
Winsonbde852d2016-04-15 19:06:54 -0700929 Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f));
Selim Cinekaac93252015-04-14 20:04:12 -0700930 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800931 return true; // Don't fade out the notification
Adrian Roos5d9cc662014-05-28 17:08:13 +0200932 }
933
Mady Mellorb0a82462016-04-30 17:31:02 -0700934 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100935 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700936 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100937 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200938 mAmbientState.onBeginDrag(v);
Selim Cinek33223572016-02-19 19:32:22 -0800939 updateContinuousShadowDrawing();
Selim Cinek131c1e22015-05-11 19:04:49 -0700940 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200941 mDragAnimPendingChildren.add(v);
942 mNeedsAnimation = true;
943 }
Selim Cinekeb973562014-05-02 17:07:49 +0200944 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100945 }
946
Selim Cinek684a4422015-04-15 16:18:39 -0700947 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700948 if (v instanceof ExpandableNotificationRow) {
949 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700950 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700951 }
952 return false;
953 }
954
955 private boolean isHeadsUp(View v) {
956 if (v instanceof ExpandableNotificationRow) {
957 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
958 return row.isHeadsUp();
959 }
960 return false;
961 }
962
Mady Mellorb0a82462016-04-30 17:31:02 -0700963 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100964 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700965 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100966 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100967 }
968
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700969 @Override
970 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700971 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700972 }
973
Mady Mellor4b80b102016-01-22 08:03:58 -0800974 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100975 public View getChildAtPosition(MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800976 View child = getChildAtPosition(ev.getX(), ev.getY());
977 if (child instanceof ExpandableNotificationRow) {
978 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
979 ExpandableNotificationRow parent = row.getNotificationParent();
Selim Cinek43d30f02016-03-04 10:51:32 -0800980 if (parent != null && parent.areChildrenExpanded()
Selim Cinek38d429f2016-06-03 11:46:56 -0700981 && (parent.areGutsExposed()
982 || mGearExposedView == parent
Selim Cinek43d30f02016-03-04 10:51:32 -0800983 || (parent.getNotificationChildren().size() == 1
984 && parent.isClearable()))) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800985 // In this case the group is expanded and showing the gear for the
986 // group, further interaction should apply to the group, not any
Selim Cinek43d30f02016-03-04 10:51:32 -0800987 // child notifications so we use the parent of the child. We also do the same
988 // if we only have a single child.
989 child = parent;
Mady Mellor4b80b102016-01-22 08:03:58 -0800990 }
991 }
992 return child;
Selim Cinek67b22602014-03-10 15:40:16 +0100993 }
994
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100995 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
996 getLocationOnScreen(mTempInt2);
997 float localTouchY = touchY - mTempInt2[1];
998
999 ExpandableView closestChild = null;
1000 float minDist = Float.MAX_VALUE;
1001
1002 // find the view closest to the location, accounting for GONE views
1003 final int count = getChildCount();
1004 for (int childIdx = 0; childIdx < count; childIdx++) {
1005 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1006 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -08001007 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001008 continue;
1009 }
1010 float childTop = slidingChild.getTranslationY();
1011 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001012 float bottom = childTop + slidingChild.getActualHeight()
1013 - slidingChild.getClipBottomAmount();
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001014
1015 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
1016 if (dist < minDist) {
1017 closestChild = slidingChild;
1018 minDist = dist;
1019 }
1020 }
1021 return closestChild;
1022 }
1023
Mady Mellorb0a82462016-04-30 17:31:02 -07001024 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +02001025 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001026 getLocationOnScreen(mTempInt2);
1027 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +01001028 }
1029
Mady Mellorb0a82462016-04-30 17:31:02 -07001030 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +02001031 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001032 // find the view under the pointer, accounting for GONE views
1033 final int count = getChildCount();
1034 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001035 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001036 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -08001037 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +01001038 continue;
1039 }
Selim Cinek89faff12014-06-19 16:29:04 -07001040 float childTop = slidingChild.getTranslationY();
1041 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001042 float bottom = childTop + slidingChild.getActualHeight()
1043 - slidingChild.getClipBottomAmount();
Jorim Jaggi28f0e592014-08-05 22:03:07 +02001044
1045 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
1046 // camera affordance).
1047 int left = 0;
1048 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +01001049
1050 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001051 if (slidingChild instanceof ExpandableNotificationRow) {
1052 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -07001053 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -08001054 && mHeadsUpManager.getTopEntry().entry.row != row
1055 && mGroupManager.getGroupSummary(
1056 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
1057 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001058 continue;
1059 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001060 return row.getViewAtPosition(touchY - childTop);
1061 }
Selim Cinek67b22602014-03-10 15:40:16 +01001062 return slidingChild;
1063 }
1064 }
1065 return null;
1066 }
1067
Mady Mellorb0a82462016-04-30 17:31:02 -07001068 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001069 public boolean canChildBeExpanded(View v) {
1070 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -07001071 && ((ExpandableNotificationRow) v).isExpandable()
Selim Cinek38d429f2016-06-03 11:46:56 -07001072 && !((ExpandableNotificationRow) v).areGutsExposed()
Selim Cinek166f7c42016-03-03 16:21:37 -08001073 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
Selim Cinek67b22602014-03-10 15:40:16 +01001074 }
1075
Chris Wren6abeeb92016-05-26 14:44:38 -04001076 /* Only ever called as a consequence of an expansion gesture in the shade. */
Mady Mellorb0a82462016-04-30 17:31:02 -07001077 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001078 public void setUserExpandedChild(View v, boolean userExpanded) {
1079 if (v instanceof ExpandableNotificationRow) {
Chris Wren6abeeb92016-05-26 14:44:38 -04001080 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek740c1112016-12-22 16:39:54 +01001081 if (userExpanded && onKeyguard()) {
1082 // Due to a race when locking the screen while touching, a notification may be
1083 // expanded even after we went back to keyguard. An example of this happens if
1084 // you click in the empty space while expanding a group.
1085
1086 // We also need to un-user lock it here, since otherwise the content height
1087 // calculated might be wrong. We also can't invert the two calls since
1088 // un-userlocking it will trigger a layout switch in the content view.
1089 row.setUserLocked(false);
1090 updateContentHeight();
1091 notifyHeightChangeListener(row);
1092 return;
1093 }
Chris Wren6abeeb92016-05-26 14:44:38 -04001094 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
1095 row.onExpandedByGesture(userExpanded);
Selim Cinek67b22602014-03-10 15:40:16 +01001096 }
1097 }
1098
Mady Mellorb0a82462016-04-30 17:31:02 -07001099 @Override
1100 public void setExpansionCancelled(View v) {
1101 if (v instanceof ExpandableNotificationRow) {
1102 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
1103 }
1104 }
1105
1106 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001107 public void setUserLockedChild(View v, boolean userLocked) {
1108 if (v instanceof ExpandableNotificationRow) {
1109 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
1110 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001111 removeLongPressCallback();
1112 requestDisallowInterceptTouchEvent(true);
1113 }
1114
1115 @Override
1116 public void expansionStateChanged(boolean isExpanding) {
1117 mExpandingNotification = isExpanding;
1118 if (!mExpandedInThisMotion) {
1119 mMaxScrollAfterExpand = mOwnScrollY;
1120 mExpandedInThisMotion = true;
1121 }
1122 }
1123
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001124 @Override
1125 public int getMaxExpandHeight(ExpandableView view) {
Selim Cinekb3dadcc2016-11-21 17:21:13 -08001126 return view.getMaxContentHeight();
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001127 }
1128
Selim Cinek1408eb52014-06-02 14:45:38 +02001129 public void setScrollingEnabled(boolean enable) {
1130 mScrollingEnabled = enable;
1131 }
1132
Adrian Roos181385c2016-05-05 17:45:44 -04001133 @Override
1134 public void lockScrollTo(View v) {
1135 if (mForcedScroll == v) {
1136 return;
1137 }
1138 mForcedScroll = v;
1139 scrollTo(v);
1140 }
1141
1142 @Override
Ricky Waicd35def2016-05-03 11:07:07 +01001143 public boolean scrollTo(View v) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001144 ExpandableView expandableView = (ExpandableView) v;
Adrian Roos4a579672016-05-24 16:54:37 -07001145 int positionInLinearLayout = getPositionInLinearLayout(v);
1146 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1147 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Ricky Waicd35def2016-05-03 11:07:07 +01001148
Adrian Roos4a579672016-05-24 16:54:37 -07001149 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1150 // that it is not visible anymore.
1151 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001152 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1153 mDontReportNextOverScroll = true;
1154 postInvalidateOnAnimation();
Ricky Waicd35def2016-05-03 11:07:07 +01001155 return true;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001156 }
Ricky Waicd35def2016-05-03 11:07:07 +01001157 return false;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001158 }
1159
Adrian Roos181385c2016-05-05 17:45:44 -04001160 /**
1161 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
1162 * the IME.
1163 */
1164 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1165 return positionInLinearLayout + v.getIntrinsicHeight() +
1166 getImeInset() - getHeight() + getTopPadding();
1167 }
1168
Adrian Roos5153d4a2016-03-22 10:01:56 -07001169 @Override
1170 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Selim Cineka424c502016-04-05 13:07:54 -07001171 mBottomInset = insets.getSystemWindowInsetBottom();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001172
1173 int range = getScrollRange();
1174 if (mOwnScrollY > range) {
1175 // HACK: We're repeatedly getting staggered insets here while the IME is
1176 // animating away. To work around that we'll wait until things have settled.
1177 removeCallbacks(mReclamp);
1178 postDelayed(mReclamp, 50);
Adrian Roos4a579672016-05-24 16:54:37 -07001179 } else if (mForcedScroll != null) {
1180 // The scroll was requested before we got the actual inset - in case we need
1181 // to scroll up some more do so now.
1182 scrollTo(mForcedScroll);
Adrian Roos5153d4a2016-03-22 10:01:56 -07001183 }
1184 return insets;
1185 }
1186
1187 private Runnable mReclamp = new Runnable() {
1188 @Override
1189 public void run() {
1190 int range = getScrollRange();
1191 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1192 mDontReportNextOverScroll = true;
1193 mDontClampNextScroll = true;
1194 postInvalidateOnAnimation();
1195 }
1196 };
1197
Selim Cinek1408eb52014-06-02 14:45:38 +02001198 public void setExpandingEnabled(boolean enable) {
1199 mExpandHelper.setEnabled(enable);
1200 }
1201
1202 private boolean isScrollingEnabled() {
1203 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +01001204 }
1205
Mady Mellorb0a82462016-04-30 17:31:02 -07001206 @Override
Selim Cinek67b22602014-03-10 15:40:16 +01001207 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -07001208 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +01001209 }
1210
Selim Cinek19c8c702014-08-25 22:09:19 +02001211 @Override
1212 public boolean isAntiFalsingNeeded() {
Selim Cinekbc243a92016-09-27 16:35:13 -07001213 return onKeyguard();
1214 }
1215
1216 private boolean onKeyguard() {
Selim Cinek355652a2016-12-07 13:32:12 -08001217 return mStatusBarState == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +02001218 }
1219
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001220 private void setSwipingInProgress(boolean isSwiped) {
1221 mSwipingInProgress = isSwiped;
1222 if(isSwiped) {
1223 requestDisallowInterceptTouchEvent(true);
1224 }
Selim Cinek67b22602014-03-10 15:40:16 +01001225 }
1226
1227 @Override
1228 protected void onConfigurationChanged(Configuration newConfig) {
1229 super.onConfigurationChanged(newConfig);
1230 float densityScale = getResources().getDisplayMetrics().density;
1231 mSwipeHelper.setDensityScale(densityScale);
1232 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1233 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1234 initView(getContext());
1235 }
1236
Dan Sandlereceda3d2014-07-21 15:35:01 -04001237 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07001238 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1239 true /* isDismissAll */);
Selim Cinek67b22602014-03-10 15:40:16 +01001240 }
1241
Mady Mellor86889c22016-04-18 16:37:06 -07001242 public void snapViewIfNeeded(ExpandableNotificationRow child) {
dongwan0605.kim30637e42016-03-02 17:16:47 +09001243 boolean animate = mIsExpanded || isPinnedHeadsUp(child);
Mady Mellor86889c22016-04-18 16:37:06 -07001244 // If the child is showing the gear to go to settings, snap to that
1245 float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
1246 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
dongwan0605.kim30637e42016-03-02 17:16:47 +09001247 }
1248
Selim Cinek67b22602014-03-10 15:40:16 +01001249 @Override
1250 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001251 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1252 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001253 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001254 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001255 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001256 if (isCancelOrUp) {
1257 mExpandHelper.onlyObserveMovements(false);
1258 }
1259 boolean wasExpandingBefore = mExpandingNotification;
1260 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +02001261 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
1262 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001263 dispatchDownEventToScroller(ev);
1264 }
1265 }
Selim Cinek67b22602014-03-10 15:40:16 +01001266 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -07001267 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
1268 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001269 scrollerWantsIt = onScrollTouch(ev);
1270 }
1271 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001272 if (!mIsBeingDragged
1273 && !mExpandingNotification
1274 && !mExpandedInThisMotion
Adrian Roosfa139752016-04-27 09:59:08 -07001275 && !mOnlyScrollingInThisMotion
1276 && !mDisallowDismissInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001277 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
1278 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001279 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
1280 }
1281
1282 private void dispatchDownEventToScroller(MotionEvent ev) {
1283 MotionEvent downEvent = MotionEvent.obtain(ev);
1284 downEvent.setAction(MotionEvent.ACTION_DOWN);
1285 onScrollTouch(downEvent);
1286 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +01001287 }
1288
Selim Cinek11e33232016-08-05 15:30:53 -07001289 @Override
1290 public boolean onGenericMotionEvent(MotionEvent event) {
1291 if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
1292 || mDisallowScrollingInThisMotion) {
1293 return false;
1294 }
1295 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1296 switch (event.getAction()) {
1297 case MotionEvent.ACTION_SCROLL: {
1298 if (!mIsBeingDragged) {
1299 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1300 if (vscroll != 0) {
1301 final int delta = (int) (vscroll * getVerticalScrollFactor());
1302 final int range = getScrollRange();
1303 int oldScrollY = mOwnScrollY;
1304 int newScrollY = oldScrollY - delta;
1305 if (newScrollY < 0) {
1306 newScrollY = 0;
1307 } else if (newScrollY > range) {
1308 newScrollY = range;
1309 }
1310 if (newScrollY != oldScrollY) {
1311 customScrollTo(newScrollY);
1312 return true;
1313 }
1314 }
1315 }
1316 }
1317 }
1318 }
1319 return super.onGenericMotionEvent(event);
1320 }
1321
Selim Cinek67b22602014-03-10 15:40:16 +01001322 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001323 if (!isScrollingEnabled()) {
1324 return false;
1325 }
Adrian Roosfeb97252016-09-29 17:05:58 -07001326 if (isInsideQsContainer(ev) && !mIsBeingDragged) {
Jason Monk16ac3772016-02-10 15:39:21 -05001327 return false;
1328 }
Adrian Roos181385c2016-05-05 17:45:44 -04001329 mForcedScroll = null;
Selim Cinek67b22602014-03-10 15:40:16 +01001330 initVelocityTrackerIfNotExists();
1331 mVelocityTracker.addMovement(ev);
1332
1333 final int action = ev.getAction();
1334
1335 switch (action & MotionEvent.ACTION_MASK) {
1336 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001337 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +01001338 return false;
1339 }
1340 boolean isBeingDragged = !mScroller.isFinished();
1341 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +01001342 /*
1343 * If being flinged and user touches, stop the fling. isFinished
1344 * will be false if being flinged.
1345 */
1346 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001347 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +01001348 }
1349
1350 // Remember where the motion event started
1351 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001352 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001353 mActivePointerId = ev.getPointerId(0);
1354 break;
1355 }
1356 case MotionEvent.ACTION_MOVE:
1357 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1358 if (activePointerIndex == -1) {
1359 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1360 break;
1361 }
1362
1363 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001364 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001365 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001366 final int xDiff = Math.abs(x - mDownX);
1367 final int yDiff = Math.abs(deltaY);
1368 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001369 setIsBeingDragged(true);
1370 if (deltaY > 0) {
1371 deltaY -= mTouchSlop;
1372 } else {
1373 deltaY += mTouchSlop;
1374 }
1375 }
1376 if (mIsBeingDragged) {
1377 // Scroll to follow the motion event
1378 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001379 int range = getScrollRange();
1380 if (mExpandedInThisMotion) {
1381 range = Math.min(range, mMaxScrollAfterExpand);
1382 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001383
1384 float scrollAmount;
1385 if (deltaY < 0) {
1386 scrollAmount = overScrollDown(deltaY);
1387 } else {
1388 scrollAmount = overScrollUp(deltaY, range);
1389 }
Selim Cinek67b22602014-03-10 15:40:16 +01001390
1391 // Calling overScrollBy will call onOverScrolled, which
1392 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001393 if (scrollAmount != 0.0f) {
1394 // The scrolling motion could not be compensated with the
1395 // existing overScroll, we have to scroll the view
1396 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1397 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +01001398 }
Selim Cinek67b22602014-03-10 15:40:16 +01001399 }
1400 break;
1401 case MotionEvent.ACTION_UP:
1402 if (mIsBeingDragged) {
1403 final VelocityTracker velocityTracker = mVelocityTracker;
1404 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1405 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1406
Selim Cinek1408eb52014-06-02 14:45:38 +02001407 if (shouldOverScrollFling(initialVelocity)) {
1408 onOverScrollFling(true, initialVelocity);
1409 } else {
1410 if (getChildCount() > 0) {
1411 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1412 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1413 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1414 fling(-initialVelocity);
1415 } else {
1416 onOverScrollFling(false, initialVelocity);
1417 }
1418 } else {
1419 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1420 getScrollRange())) {
1421 postInvalidateOnAnimation();
1422 }
Selim Cinek67b22602014-03-10 15:40:16 +01001423 }
1424 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001425 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001426 mActivePointerId = INVALID_POINTER;
1427 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +01001428 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001429
Selim Cinek67b22602014-03-10 15:40:16 +01001430 break;
1431 case MotionEvent.ACTION_CANCEL:
1432 if (mIsBeingDragged && getChildCount() > 0) {
1433 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1434 postInvalidateOnAnimation();
1435 }
1436 mActivePointerId = INVALID_POINTER;
1437 endDrag();
1438 }
1439 break;
1440 case MotionEvent.ACTION_POINTER_DOWN: {
1441 final int index = ev.getActionIndex();
1442 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +02001443 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +01001444 mActivePointerId = ev.getPointerId(index);
1445 break;
1446 }
1447 case MotionEvent.ACTION_POINTER_UP:
1448 onSecondaryPointerUp(ev);
1449 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +02001450 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +01001451 break;
1452 }
1453 return true;
1454 }
1455
Muyuan Li84b45612016-06-01 11:05:08 -07001456 protected boolean isInsideQsContainer(MotionEvent ev) {
1457 return ev.getY() < mQsContainer.getBottom();
1458 }
1459
Selim Cinek1408eb52014-06-02 14:45:38 +02001460 private void onOverScrollFling(boolean open, int initialVelocity) {
1461 if (mOverscrollTopChangedListener != null) {
1462 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1463 }
1464 mDontReportNextOverScroll = true;
1465 setOverScrollAmount(0.0f, true, false);
1466 }
1467
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001468 /**
1469 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1470 *
1471 * @param deltaY The amount to scroll upwards, has to be positive.
1472 * @return The amount of scrolling to be performed by the scroller,
1473 * not handled by the overScroll amount.
1474 */
1475 private float overScrollUp(int deltaY, int range) {
1476 deltaY = Math.max(deltaY, 0);
1477 float currentTopAmount = getCurrentOverScrollAmount(true);
1478 float newTopAmount = currentTopAmount - deltaY;
1479 if (currentTopAmount > 0) {
1480 setOverScrollAmount(newTopAmount, true /* onTop */,
1481 false /* animate */);
1482 }
1483 // Top overScroll might not grab all scrolling motion,
1484 // we have to scroll as well.
1485 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1486 float newScrollY = mOwnScrollY + scrollAmount;
1487 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001488 if (!mExpandedInThisMotion) {
1489 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1490 // We overScroll on the top
1491 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1492 false /* onTop */,
1493 false /* animate */);
1494 }
Selim Cinekef406062016-09-29 17:33:13 -07001495 setOwnScrollY(range);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001496 scrollAmount = 0.0f;
1497 }
1498 return scrollAmount;
1499 }
1500
1501 /**
1502 * Perform a scroll downward and adapt the overscroll amounts accordingly
1503 *
1504 * @param deltaY The amount to scroll downwards, has to be negative.
1505 * @return The amount of scrolling to be performed by the scroller,
1506 * not handled by the overScroll amount.
1507 */
1508 private float overScrollDown(int deltaY) {
1509 deltaY = Math.min(deltaY, 0);
1510 float currentBottomAmount = getCurrentOverScrollAmount(false);
1511 float newBottomAmount = currentBottomAmount + deltaY;
1512 if (currentBottomAmount > 0) {
1513 setOverScrollAmount(newBottomAmount, false /* onTop */,
1514 false /* animate */);
1515 }
1516 // Bottom overScroll might not grab all scrolling motion,
1517 // we have to scroll as well.
1518 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1519 float newScrollY = mOwnScrollY + scrollAmount;
1520 if (newScrollY < 0) {
1521 float currentTopPixels = getCurrentOverScrolledPixels(true);
1522 // We overScroll on the top
1523 setOverScrolledPixels(currentTopPixels - newScrollY,
1524 true /* onTop */,
1525 false /* animate */);
Selim Cinekef406062016-09-29 17:33:13 -07001526 setOwnScrollY(0);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001527 scrollAmount = 0.0f;
1528 }
1529 return scrollAmount;
1530 }
1531
Selim Cinek67b22602014-03-10 15:40:16 +01001532 private void onSecondaryPointerUp(MotionEvent ev) {
1533 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1534 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1535 final int pointerId = ev.getPointerId(pointerIndex);
1536 if (pointerId == mActivePointerId) {
1537 // This was our active pointer going up. Choose a new
1538 // active pointer and adjust accordingly.
1539 // TODO: Make this decision more intelligent.
1540 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1541 mLastMotionY = (int) ev.getY(newPointerIndex);
1542 mActivePointerId = ev.getPointerId(newPointerIndex);
1543 if (mVelocityTracker != null) {
1544 mVelocityTracker.clear();
1545 }
1546 }
1547 }
1548
1549 private void initVelocityTrackerIfNotExists() {
1550 if (mVelocityTracker == null) {
1551 mVelocityTracker = VelocityTracker.obtain();
1552 }
1553 }
1554
1555 private void recycleVelocityTracker() {
1556 if (mVelocityTracker != null) {
1557 mVelocityTracker.recycle();
1558 mVelocityTracker = null;
1559 }
1560 }
1561
1562 private void initOrResetVelocityTracker() {
1563 if (mVelocityTracker == null) {
1564 mVelocityTracker = VelocityTracker.obtain();
1565 } else {
1566 mVelocityTracker.clear();
1567 }
1568 }
1569
Ricky Waicd35def2016-05-03 11:07:07 +01001570 public void setFinishScrollingCallback(Runnable runnable) {
1571 mFinishScrollingCallback = runnable;
1572 }
1573
Selim Cinek67b22602014-03-10 15:40:16 +01001574 @Override
1575 public void computeScroll() {
1576 if (mScroller.computeScrollOffset()) {
1577 // This is called at drawing time by ViewGroup.
1578 int oldX = mScrollX;
1579 int oldY = mOwnScrollY;
1580 int x = mScroller.getCurrX();
1581 int y = mScroller.getCurrY();
1582
1583 if (oldX != x || oldY != y) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001584 int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001585 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1586 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001587 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001588 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1589 }
1590 }
Selim Cinek67b22602014-03-10 15:40:16 +01001591
Adrian Roos5153d4a2016-03-22 10:01:56 -07001592 if (mDontClampNextScroll) {
1593 range = Math.max(range, oldY);
1594 }
Selim Cinek67b22602014-03-10 15:40:16 +01001595 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001596 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001597 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001598 }
1599
1600 // Keep on drawing until the animation has finished.
1601 postInvalidateOnAnimation();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001602 } else {
1603 mDontClampNextScroll = false;
Ricky Waicd35def2016-05-03 11:07:07 +01001604 if (mFinishScrollingCallback != null) {
1605 mFinishScrollingCallback.run();
1606 }
Selim Cinek67b22602014-03-10 15:40:16 +01001607 }
1608 }
1609
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001610 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001611 protected boolean overScrollBy(int deltaX, int deltaY,
1612 int scrollX, int scrollY,
1613 int scrollRangeX, int scrollRangeY,
1614 int maxOverScrollX, int maxOverScrollY,
1615 boolean isTouchEvent) {
1616
1617 int newScrollY = scrollY + deltaY;
Selim Cinek4195dd02014-05-19 18:16:14 +02001618 final int top = -maxOverScrollY;
1619 final int bottom = maxOverScrollY + scrollRangeY;
1620
1621 boolean clampedY = false;
1622 if (newScrollY > bottom) {
1623 newScrollY = bottom;
1624 clampedY = true;
1625 } else if (newScrollY < top) {
1626 newScrollY = top;
1627 clampedY = true;
1628 }
1629
1630 onOverScrolled(0, newScrollY, false, clampedY);
1631
1632 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001633 }
1634
1635 /**
1636 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1637 * overscroll effect based on numPixels. By default this will also cancel animations on the
1638 * same overScroll edge.
1639 *
1640 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1641 * the rubber-banding logic.
1642 * @param onTop Should the effect be applied on top of the scroller.
1643 * @param animate Should an animation be performed.
1644 */
1645 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001646 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001647 }
1648
1649 /**
1650 * Set the effective overScroll amount which will be directly reflected in the layout.
1651 * By default this will also cancel animations on the same overScroll edge.
1652 *
1653 * @param amount The amount to overScroll by.
1654 * @param onTop Should the effect be applied on top of the scroller.
1655 * @param animate Should an animation be performed.
1656 */
1657 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1658 setOverScrollAmount(amount, onTop, animate, true);
1659 }
1660
1661 /**
1662 * Set the effective overScroll amount which will be directly reflected in the layout.
1663 *
1664 * @param amount The amount to overScroll by.
1665 * @param onTop Should the effect be applied on top of the scroller.
1666 * @param animate Should an animation be performed.
1667 * @param cancelAnimators Should running animations be cancelled.
1668 */
1669 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1670 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001671 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1672 }
1673
1674 /**
1675 * Set the effective overScroll amount which will be directly reflected in the layout.
1676 *
1677 * @param amount The amount to overScroll by.
1678 * @param onTop Should the effect be applied on top of the scroller.
1679 * @param animate Should an animation be performed.
1680 * @param cancelAnimators Should running animations be cancelled.
1681 * @param isRubberbanded The value which will be passed to
1682 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1683 */
1684 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1685 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001686 if (cancelAnimators) {
1687 mStateAnimator.cancelOverScrollAnimators(onTop);
1688 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001689 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001690 }
1691
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001692 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1693 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001694 amount = Math.max(0, amount);
1695 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001696 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001697 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001698 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001699 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001700 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001701 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001702 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001703 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001704 }
1705 }
1706
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001707 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001708 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1709 if (mDontReportNextOverScroll) {
1710 mDontReportNextOverScroll = false;
1711 return;
1712 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001713 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001714 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001715 }
1716 }
1717
1718 public void setOverscrollTopChangedListener(
1719 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1720 mOverscrollTopChangedListener = overscrollTopChangedListener;
1721 }
1722
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001723 public float getCurrentOverScrollAmount(boolean top) {
1724 return mAmbientState.getOverScrollAmount(top);
1725 }
1726
1727 public float getCurrentOverScrolledPixels(boolean top) {
1728 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1729 }
1730
1731 private void setOverScrolledPixels(float amount, boolean onTop) {
1732 if (onTop) {
1733 mOverScrolledTopPixels = amount;
1734 } else {
1735 mOverScrolledBottomPixels = amount;
1736 }
1737 }
1738
Selim Cinek319bdc42014-05-01 23:01:58 +02001739 private void customScrollTo(int y) {
Selim Cinekef406062016-09-29 17:33:13 -07001740 setOwnScrollY(y);
Selim Cinek3af00cf2014-05-07 17:27:26 +02001741 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001742 }
1743
1744 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001745 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001746 // Treat animating scrolls differently; see #computeScroll() for why.
1747 if (!mScroller.isFinished()) {
1748 final int oldX = mScrollX;
1749 final int oldY = mOwnScrollY;
1750 mScrollX = scrollX;
Selim Cinekef406062016-09-29 17:33:13 -07001751 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01001752 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001753 springBack();
1754 } else {
1755 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1756 invalidateParentIfNeeded();
1757 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001758 float overScrollTop = getCurrentOverScrollAmount(true);
1759 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001760 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001761 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001762 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001763 }
Selim Cinek67b22602014-03-10 15:40:16 +01001764 }
Selim Cinek67b22602014-03-10 15:40:16 +01001765 } else {
1766 customScrollTo(scrollY);
1767 scrollTo(scrollX, mScrollY);
1768 }
1769 }
1770
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001771 private void springBack() {
1772 int scrollRange = getScrollRange();
1773 boolean overScrolledTop = mOwnScrollY <= 0;
1774 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1775 if (overScrolledTop || overScrolledBottom) {
1776 boolean onTop;
1777 float newAmount;
1778 if (overScrolledTop) {
1779 onTop = true;
1780 newAmount = -mOwnScrollY;
Selim Cinekef406062016-09-29 17:33:13 -07001781 setOwnScrollY(0);
Selim Cinek1408eb52014-06-02 14:45:38 +02001782 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001783 } else {
1784 onTop = false;
1785 newAmount = mOwnScrollY - scrollRange;
Selim Cinekef406062016-09-29 17:33:13 -07001786 setOwnScrollY(scrollRange);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001787 }
1788 setOverScrollAmount(newAmount, onTop, false);
1789 setOverScrollAmount(0.0f, onTop, true);
1790 mScroller.forceFinished(true);
1791 }
1792 }
1793
Selim Cinek67b22602014-03-10 15:40:16 +01001794 private int getScrollRange() {
Selim Cineka424c502016-04-05 13:07:54 -07001795 int contentHeight = getContentHeight();
Selim Cinekdb167372016-11-17 15:41:17 -08001796 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
Selim Cineka424c502016-04-05 13:07:54 -07001797 int imeInset = getImeInset();
1798 scrollRange += Math.min(imeInset, Math.max(0,
1799 getContentHeight() - (getHeight() - imeInset)));
1800 return scrollRange;
1801 }
1802
1803 private int getImeInset() {
1804 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
Selim Cinek67b22602014-03-10 15:40:16 +01001805 }
1806
Selim Cinek343e6e22014-04-11 21:23:30 +02001807 /**
1808 * @return the first child which has visibility unequal to GONE
1809 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001810 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001811 int childCount = getChildCount();
1812 for (int i = 0; i < childCount; i++) {
1813 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08001814 if (child.getVisibility() != View.GONE && child != mShelf) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001815 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001816 }
1817 }
1818 return null;
1819 }
1820
Selim Cinek4a1ac842014-05-01 15:51:58 +02001821 /**
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001822 * @return the child before the given view which has visibility unequal to GONE
1823 */
1824 public ExpandableView getViewBeforeView(ExpandableView view) {
1825 ExpandableView previousView = null;
1826 int childCount = getChildCount();
1827 for (int i = 0; i < childCount; i++) {
1828 View child = getChildAt(i);
1829 if (child == view) {
1830 return previousView;
1831 }
1832 if (child.getVisibility() != View.GONE) {
1833 previousView = (ExpandableView) child;
1834 }
1835 }
1836 return null;
1837 }
1838
1839 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001840 * @return The first child which has visibility unequal to GONE which is currently below the
1841 * given translationY or equal to it.
1842 */
1843 private View getFirstChildBelowTranlsationY(float translationY) {
1844 int childCount = getChildCount();
1845 for (int i = 0; i < childCount; i++) {
1846 View child = getChildAt(i);
1847 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1848 return child;
1849 }
1850 }
1851 return null;
1852 }
1853
1854 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001855 * @return the last child which has visibility unequal to GONE
1856 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001857 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001858 int childCount = getChildCount();
1859 for (int i = childCount - 1; i >= 0; i--) {
1860 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08001861 if (child.getVisibility() != View.GONE && child != mShelf) {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001862 return child;
1863 }
1864 }
1865 return null;
1866 }
1867
Jorim Jaggi069cd032014-05-15 03:09:01 +02001868 /**
1869 * @return the number of children which have visibility unequal to GONE
1870 */
1871 public int getNotGoneChildCount() {
1872 int childCount = getChildCount();
1873 int count = 0;
1874 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001875 ExpandableView child = (ExpandableView) getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08001876 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001877 count++;
1878 }
1879 }
1880 return count;
1881 }
1882
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001883 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001884 return mContentHeight;
1885 }
1886
1887 private void updateContentHeight() {
1888 int height = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08001889 float previousIncreasedAmount = 0.0f;
Selim Cinekad7fac02016-10-18 17:09:15 -07001890 int numShownItems = 0;
1891 boolean finish = false;
Adrian Roos7a9551a2017-01-11 12:27:49 -08001892 int maxDisplayedNotifications = mAmbientState.isDark()
1893 ? (mPulsing ? 1 : 0)
1894 : mMaxDisplayedNotifications;
1895
Selim Cinek67b22602014-03-10 15:40:16 +01001896 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08001897 ExpandableView expandableView = (ExpandableView) getChildAt(i);
Selim Cinek281c2022016-10-13 19:14:43 -07001898 if (expandableView.getVisibility() != View.GONE
1899 && !expandableView.hasNoContentHeight()) {
Adrian Roos7a9551a2017-01-11 12:27:49 -08001900 if (maxDisplayedNotifications != -1
1901 && numShownItems >= maxDisplayedNotifications) {
Selim Cinekad7fac02016-10-18 17:09:15 -07001902 expandableView = mShelf;
1903 finish = true;
1904 }
Selim Cinek42357e02016-02-24 18:48:01 -08001905 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001906 if (height != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08001907 height += (int) NotificationUtils.interpolate(
1908 mPaddingBetweenElements,
1909 mIncreasedPaddingBetweenElements,
1910 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Jorim Jaggid4a57442014-04-10 02:45:55 +02001911 }
Selim Cinek42357e02016-02-24 18:48:01 -08001912 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08001913 height += expandableView.getIntrinsicHeight();
Selim Cinekad7fac02016-10-18 17:09:15 -07001914 numShownItems++;
1915 if (finish) {
1916 break;
1917 }
Selim Cinek67b22602014-03-10 15:40:16 +01001918 }
1919 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001920 mContentHeight = height + mTopPadding;
Selim Cinekc22fff62016-05-20 12:44:30 -07001921 updateScrollability();
Selim Cinek91d4cba2016-11-10 19:59:48 -08001922 mAmbientState.setLayoutMaxHeight(mContentHeight);
Selim Cinekc22fff62016-05-20 12:44:30 -07001923 }
1924
1925 private void updateScrollability() {
1926 boolean scrollable = getScrollRange() > 0;
1927 if (scrollable != mScrollable) {
1928 mScrollable = scrollable;
1929 setFocusable(scrollable);
Selim Cinekef406062016-09-29 17:33:13 -07001930 updateForwardAndBackwardScrollability();
1931 }
1932 }
1933
1934 private void updateForwardAndBackwardScrollability() {
1935 boolean forwardScrollable = mScrollable && mOwnScrollY < getScrollRange();
1936 boolean backwardsScrollable = mScrollable && mOwnScrollY > 0;
1937 boolean changed = forwardScrollable != mForwardScrollable
1938 || backwardsScrollable != mBackwardScrollable;
1939 mForwardScrollable = forwardScrollable;
1940 mBackwardScrollable = backwardsScrollable;
1941 if (changed) {
1942 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Selim Cinekc22fff62016-05-20 12:44:30 -07001943 }
Selim Cinek67b22602014-03-10 15:40:16 +01001944 }
1945
Selim Cinek6811d722016-01-19 17:53:12 -08001946 private void updateBackground() {
1947 if (mAmbientState.isDark()) {
1948 return;
1949 }
1950 updateBackgroundBounds();
1951 if (!mCurrentBounds.equals(mBackgroundBounds)) {
Selim Cinek54680902016-10-19 16:49:44 -07001952 boolean animate = mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom
1953 || areBoundsAnimating();
1954 if (!isExpanded()) {
1955 abortBackgroundAnimators();
1956 animate = false;
1957 }
1958 if (animate) {
Selim Cinek614576e2016-01-20 10:54:09 -08001959 startBackgroundAnimation();
1960 } else {
1961 mCurrentBounds.set(mBackgroundBounds);
1962 applyCurrentBackgroundBounds();
1963 }
1964 } else {
Selim Cinek54680902016-10-19 16:49:44 -07001965 abortBackgroundAnimators();
Selim Cinek6811d722016-01-19 17:53:12 -08001966 }
Selim Cinek614576e2016-01-20 10:54:09 -08001967 mAnimateNextBackgroundBottom = false;
1968 mAnimateNextBackgroundTop = false;
1969 }
1970
Selim Cinek54680902016-10-19 16:49:44 -07001971 private void abortBackgroundAnimators() {
1972 if (mBottomAnimator != null) {
1973 mBottomAnimator.cancel();
1974 }
1975 if (mTopAnimator != null) {
1976 mTopAnimator.cancel();
1977 }
1978 }
1979
Selim Cinek614576e2016-01-20 10:54:09 -08001980 private boolean areBoundsAnimating() {
1981 return mBottomAnimator != null || mTopAnimator != null;
1982 }
1983
1984 private void startBackgroundAnimation() {
Selim Cinek01e40192016-05-12 14:57:22 -07001985 // left and right are always instantly applied
1986 mCurrentBounds.left = mBackgroundBounds.left;
1987 mCurrentBounds.right = mBackgroundBounds.right;
Selim Cinek614576e2016-01-20 10:54:09 -08001988 startBottomAnimation();
1989 startTopAnimation();
1990 }
1991
1992 private void startTopAnimation() {
1993 int previousEndValue = mEndAnimationRect.top;
1994 int newEndValue = mBackgroundBounds.top;
1995 ObjectAnimator previousAnimator = mTopAnimator;
1996 if (previousAnimator != null && previousEndValue == newEndValue) {
1997 return;
1998 }
1999 if (!mAnimateNextBackgroundTop) {
2000 // just a local update was performed
2001 if (previousAnimator != null) {
2002 // we need to increase all animation keyframes of the previous animator by the
2003 // relative change to the end value
2004 int previousStartValue = mStartAnimationRect.top;
2005 PropertyValuesHolder[] values = previousAnimator.getValues();
2006 values[0].setIntValues(previousStartValue, newEndValue);
2007 mStartAnimationRect.top = previousStartValue;
2008 mEndAnimationRect.top = newEndValue;
2009 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
2010 return;
2011 } else {
2012 // no new animation needed, let's just apply the value
2013 setBackgroundTop(newEndValue);
2014 return;
2015 }
2016 }
2017 if (previousAnimator != null) {
2018 previousAnimator.cancel();
2019 }
2020 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
2021 mCurrentBounds.top, newEndValue);
2022 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
2023 animator.setInterpolator(interpolator);
2024 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
2025 // remove the tag when the animation is finished
2026 animator.addListener(new AnimatorListenerAdapter() {
2027 @Override
2028 public void onAnimationEnd(Animator animation) {
2029 mStartAnimationRect.top = -1;
2030 mEndAnimationRect.top = -1;
2031 mTopAnimator = null;
2032 }
2033 });
2034 animator.start();
2035 mStartAnimationRect.top = mCurrentBounds.top;
2036 mEndAnimationRect.top = newEndValue;
2037 mTopAnimator = animator;
2038 }
2039
2040 private void startBottomAnimation() {
2041 int previousStartValue = mStartAnimationRect.bottom;
2042 int previousEndValue = mEndAnimationRect.bottom;
2043 int newEndValue = mBackgroundBounds.bottom;
2044 ObjectAnimator previousAnimator = mBottomAnimator;
2045 if (previousAnimator != null && previousEndValue == newEndValue) {
2046 return;
2047 }
2048 if (!mAnimateNextBackgroundBottom) {
2049 // just a local update was performed
2050 if (previousAnimator != null) {
2051 // we need to increase all animation keyframes of the previous animator by the
2052 // relative change to the end value
2053 PropertyValuesHolder[] values = previousAnimator.getValues();
2054 values[0].setIntValues(previousStartValue, newEndValue);
2055 mStartAnimationRect.bottom = previousStartValue;
2056 mEndAnimationRect.bottom = newEndValue;
2057 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
2058 return;
2059 } else {
2060 // no new animation needed, let's just apply the value
2061 setBackgroundBottom(newEndValue);
2062 return;
2063 }
2064 }
2065 if (previousAnimator != null) {
2066 previousAnimator.cancel();
2067 }
2068 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
2069 mCurrentBounds.bottom, newEndValue);
2070 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
2071 animator.setInterpolator(interpolator);
2072 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
2073 // remove the tag when the animation is finished
2074 animator.addListener(new AnimatorListenerAdapter() {
2075 @Override
2076 public void onAnimationEnd(Animator animation) {
2077 mStartAnimationRect.bottom = -1;
2078 mEndAnimationRect.bottom = -1;
2079 mBottomAnimator = null;
2080 }
2081 });
2082 animator.start();
2083 mStartAnimationRect.bottom = mCurrentBounds.bottom;
2084 mEndAnimationRect.bottom = newEndValue;
2085 mBottomAnimator = animator;
2086 }
2087
2088 private void setBackgroundTop(int top) {
2089 mCurrentBounds.top = top;
2090 applyCurrentBackgroundBounds();
2091 }
2092
2093 public void setBackgroundBottom(int bottom) {
2094 mCurrentBounds.bottom = bottom;
2095 applyCurrentBackgroundBounds();
2096 }
2097
2098 private void applyCurrentBackgroundBounds() {
Selim Cinek9bfba9c2016-08-08 15:20:06 -07002099 mScrimController.setExcludedBackgroundArea(
Selim Cinek48ff9b42016-11-09 19:31:51 -08002100 mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
Selim Cinek9bfba9c2016-08-08 15:20:06 -07002101 : mCurrentBounds);
Selim Cinek614576e2016-01-20 10:54:09 -08002102 invalidate();
Selim Cinek6811d722016-01-19 17:53:12 -08002103 }
2104
2105 /**
2106 * Update the background bounds to the new desired bounds
2107 */
2108 private void updateBackgroundBounds() {
Selim Cinekfb6ee6d2016-12-29 16:49:26 +01002109 if (mAmbientState.isPanelFullWidth()) {
2110 mBackgroundBounds.left = 0;
2111 mBackgroundBounds.right = getWidth();
2112 } else {
2113 getLocationInWindow(mTempInt2);
2114 mBackgroundBounds.left = mTempInt2[0];
2115 mBackgroundBounds.right = mTempInt2[0] + getWidth();
2116 }
Selim Cinek614576e2016-01-20 10:54:09 -08002117 if (!mIsExpanded) {
2118 mBackgroundBounds.top = 0;
2119 mBackgroundBounds.bottom = 0;
Selim Cinek1791f502016-10-07 17:38:03 -04002120 return;
Selim Cinek614576e2016-01-20 10:54:09 -08002121 }
2122 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08002123 int top = 0;
2124 if (firstView != null) {
Selim Cinek0cfbef42016-11-09 19:06:36 -08002125 int finalTranslationY = (int) ViewState.getFinalTranslationY(firstView);
Selim Cinek614576e2016-01-20 10:54:09 -08002126 if (mAnimateNextBackgroundTop
2127 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
2128 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
Selim Cinek6811d722016-01-19 17:53:12 -08002129 // we're ending up at the same location as we are now, lets just skip the animation
2130 top = finalTranslationY;
2131 } else {
2132 top = (int) firstView.getTranslationY();
2133 }
2134 }
Selim Cinekeccb5de2016-10-28 15:04:05 -07002135 ActivatableNotificationView lastView = mShelf.hasItemsInStableShelf()
2136 ? mShelf
2137 : mLastVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08002138 int bottom = 0;
2139 if (lastView != null) {
Selim Cinekeccb5de2016-10-28 15:04:05 -07002140 int finalTranslationY;
2141 if (lastView == mShelf) {
2142 finalTranslationY = (int) mShelf.getTranslationY();
2143 } else {
2144 finalTranslationY = (int) ViewState.getFinalTranslationY(lastView);
2145 }
Selim Cinek0cfbef42016-11-09 19:06:36 -08002146 int finalHeight = ExpandableViewState.getFinalActualHeight(lastView);
Selim Cinekc519dcf2016-11-22 13:45:39 -08002147 int finalBottom = finalTranslationY + finalHeight - lastView.getClipBottomAmount();
Selim Cinek6811d722016-01-19 17:53:12 -08002148 finalBottom = Math.min(finalBottom, getHeight());
Selim Cinek614576e2016-01-20 10:54:09 -08002149 if (mAnimateNextBackgroundBottom
2150 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
2151 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
Selim Cinek6811d722016-01-19 17:53:12 -08002152 // we're ending up at the same location as we are now, lets just skip the animation
2153 bottom = finalBottom;
2154 } else {
Selim Cinekc519dcf2016-11-22 13:45:39 -08002155 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight()
2156 - lastView.getClipBottomAmount());
Selim Cinek6811d722016-01-19 17:53:12 -08002157 bottom = Math.min(bottom, getHeight());
2158 }
Selim Cinek3776fe02016-02-04 13:32:43 -08002159 } else {
Selim Cinekd393d5c2016-08-03 16:18:12 -07002160 top = mTopPadding;
Selim Cinek7db42982016-02-02 15:21:41 -08002161 bottom = top;
Selim Cinek6811d722016-01-19 17:53:12 -08002162 }
Selim Cinek355652a2016-12-07 13:32:12 -08002163 if (mStatusBarState != StatusBarState.KEYGUARD) {
Selim Cinekd381bc32016-08-15 12:40:57 -07002164 top = (int) Math.max(mTopPadding + mStackTranslation, top);
Selim Cinek3776fe02016-02-04 13:32:43 -08002165 } else {
2166 // otherwise the animation from the shade to the keyguard will jump as it's maxed
Selim Cinekd381bc32016-08-15 12:40:57 -07002167 top = Math.max(0, top);
Selim Cinek3776fe02016-02-04 13:32:43 -08002168 }
Selim Cinekd381bc32016-08-15 12:40:57 -07002169 mBackgroundBounds.top = top;
Selim Cinek3776fe02016-02-04 13:32:43 -08002170 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
Selim Cinek6811d722016-01-19 17:53:12 -08002171 }
2172
Selim Cinek614576e2016-01-20 10:54:09 -08002173 private ActivatableNotificationView getFirstPinnedHeadsUp() {
2174 int childCount = getChildCount();
2175 for (int i = 0; i < childCount; i++) {
2176 View child = getChildAt(i);
2177 if (child.getVisibility() != View.GONE
2178 && child instanceof ExpandableNotificationRow) {
2179 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2180 if (row.isPinned()) {
2181 return row;
2182 }
2183 }
2184 }
2185 return null;
2186 }
2187
2188 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002189 int childCount = getChildCount();
2190 for (int i = childCount - 1; i >= 0; i--) {
2191 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002192 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2193 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002194 return (ActivatableNotificationView) child;
2195 }
2196 }
2197 return null;
2198 }
2199
Selim Cinek614576e2016-01-20 10:54:09 -08002200 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002201 int childCount = getChildCount();
2202 for (int i = 0; i < childCount; i++) {
2203 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002204 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2205 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002206 return (ActivatableNotificationView) child;
2207 }
2208 }
2209 return null;
2210 }
2211
Selim Cinek67b22602014-03-10 15:40:16 +01002212 /**
2213 * Fling the scroll view
2214 *
2215 * @param velocityY The initial velocity in the Y direction. Positive
2216 * numbers mean that the finger/cursor is moving down the screen,
2217 * which means we want to scroll towards the top.
2218 */
Muyuan Li26e30ae2016-04-11 17:31:42 -07002219 protected void fling(int velocityY) {
Selim Cinek67b22602014-03-10 15:40:16 +01002220 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02002221 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01002222
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002223 float topAmount = getCurrentOverScrollAmount(true);
2224 float bottomAmount = getCurrentOverScrollAmount(false);
2225 if (velocityY < 0 && topAmount > 0) {
Selim Cinekef406062016-09-29 17:33:13 -07002226 setOwnScrollY(mOwnScrollY - (int) topAmount);
Selim Cinek1408eb52014-06-02 14:45:38 +02002227 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002228 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002229 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002230 * mOverflingDistance + topAmount;
2231 } else if (velocityY > 0 && bottomAmount > 0) {
Selim Cinekef406062016-09-29 17:33:13 -07002232 setOwnScrollY((int) (mOwnScrollY + bottomAmount));
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002233 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002234 mMaxOverScroll = Math.abs(velocityY) / 1000f
2235 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
2236 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002237 } else {
2238 // it will be set once we reach the boundary
2239 mMaxOverScroll = 0.0f;
2240 }
Selim Cinek94ab18c2016-02-25 12:35:51 -08002241 int minScrollY = Math.max(0, scrollRange);
2242 if (mExpandedInThisMotion) {
2243 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2244 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002245 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek94ab18c2016-02-25 12:35:51 -08002246 minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01002247
2248 postInvalidateOnAnimation();
2249 }
2250 }
2251
Selim Cinek1408eb52014-06-02 14:45:38 +02002252 /**
2253 * @return Whether a fling performed on the top overscroll edge lead to the expanded
2254 * overScroll view (i.e QS).
2255 */
2256 private boolean shouldOverScrollFling(int initialVelocity) {
2257 float topOverScroll = getCurrentOverScrollAmount(true);
2258 return mScrolledToTopOnFirstDown
2259 && !mExpandedInThisMotion
2260 && topOverScroll > mMinTopOverScrollToEscape
2261 && initialVelocity > 0;
2262 }
2263
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002264 /**
2265 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2266 * account.
2267 *
2268 * @param qsHeight the top padding imposed by the quick settings panel
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002269 * @param animate whether to animate the change
2270 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
2271 * {@code qsHeight} is the final top padding
2272 */
Jason Monk16ac3772016-02-10 15:39:21 -05002273 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002274 boolean ignoreIntrinsicPadding) {
Selim Cinekbc243a92016-09-27 16:35:13 -07002275 int topPadding = (int) qsHeight;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002276 int minStackHeight = getLayoutMinHeight();
Selim Cinekbc243a92016-09-27 16:35:13 -07002277 if (topPadding + minStackHeight > getHeight()) {
2278 mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
Selim Cinek1408eb52014-06-02 14:45:38 +02002279 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002280 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02002281 }
Selim Cinekbc243a92016-09-27 16:35:13 -07002282 setTopPadding(ignoreIntrinsicPadding ? topPadding : clampPadding(topPadding),
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002283 animate);
Selim Cinekbc243a92016-09-27 16:35:13 -07002284 setExpandedHeight(mExpandedHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02002285 }
2286
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002287 public int getLayoutMinHeight() {
Selim Cinek48ff9b42016-11-09 19:31:51 -08002288 return mShelf.getIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07002289 }
2290
Selim Cineka981d082016-08-03 14:45:31 -07002291 public int getFirstChildIntrinsicHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002292 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002293 int firstChildMinHeight = firstChild != null
2294 ? firstChild.getIntrinsicHeight()
Selim Cinek90b6a272016-06-03 16:50:52 -07002295 : mEmptyShadeView != null
Selim Cineka981d082016-08-03 14:45:31 -07002296 ? mEmptyShadeView.getIntrinsicHeight()
Selim Cinek90b6a272016-06-03 16:50:52 -07002297 : mCollapsedSize;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002298 if (mOwnScrollY > 0) {
2299 firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
2300 }
Selim Cinek94c2d822016-07-13 18:50:04 -07002301 return firstChildMinHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002302 }
2303
2304 public float getTopPaddingOverflow() {
2305 return mTopPaddingOverflow;
2306 }
2307
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002308 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002309 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek567e8452016-03-24 10:54:56 -07002310 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08002311 : mCollapsedSize;
Selim Cinekdb167372016-11-17 15:41:17 -08002312 int shelfHeight = 0;
2313 if (mLastVisibleBackgroundChild != null) {
2314 shelfHeight = mShelf.getIntrinsicHeight();
2315 }
2316 return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002317 }
2318
Selim Cinek1408eb52014-06-02 14:45:38 +02002319 private int clampPadding(int desiredPadding) {
2320 return Math.max(desiredPadding, mIntrinsicPadding);
2321 }
2322
Selim Cinekfed1ab62014-06-17 14:10:33 -07002323 private float getRubberBandFactor(boolean onTop) {
2324 if (!onTop) {
2325 return RUBBER_BAND_FACTOR_NORMAL;
2326 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002327 if (mExpandedInThisMotion) {
2328 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002329 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002330 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2331 } else if (mScrolledToTopOnFirstDown) {
2332 return 1.0f;
2333 }
2334 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02002335 }
2336
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002337 /**
2338 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2339 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2340 * overscroll view (e.g. expand QS).
2341 */
2342 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002343 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002344 || !mScrolledToTopOnFirstDown;
2345 }
2346
Selim Cinek67b22602014-03-10 15:40:16 +01002347 private void endDrag() {
2348 setIsBeingDragged(false);
2349
2350 recycleVelocityTracker();
2351
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002352 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
2353 setOverScrollAmount(0, true /* onTop */, true /* animate */);
2354 }
2355 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
2356 setOverScrollAmount(0, false /* onTop */, true /* animate */);
2357 }
Selim Cinek67b22602014-03-10 15:40:16 +01002358 }
2359
Jorim Jaggi56306252014-07-03 00:40:09 +02002360 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
2361 ev.offsetLocation(sourceView.getX(), sourceView.getY());
2362 ev.offsetLocation(-targetView.getX(), -targetView.getY());
2363 }
2364
Selim Cinek67b22602014-03-10 15:40:16 +01002365 @Override
2366 public boolean onInterceptTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002367 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002368 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02002369 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002370 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002371 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
2372 }
Selim Cinek67b22602014-03-10 15:40:16 +01002373 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02002374 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01002375 scrollWantsIt = onInterceptTouchEventScroll(ev);
2376 }
2377 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02002378 if (!mIsBeingDragged
2379 && !mExpandingNotification
2380 && !mExpandedInThisMotion
Adrian Roosfa139752016-04-27 09:59:08 -07002381 && !mOnlyScrollingInThisMotion
2382 && !mDisallowDismissInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01002383 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
2384 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002385 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
2386 }
2387
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002388 private void handleEmptySpaceClick(MotionEvent ev) {
2389 switch (ev.getActionMasked()) {
2390 case MotionEvent.ACTION_MOVE:
2391 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
2392 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
2393 mTouchIsClick = false;
2394 }
2395 break;
2396 case MotionEvent.ACTION_UP:
Selim Cinek355652a2016-12-07 13:32:12 -08002397 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002398 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
2399 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
2400 }
2401 break;
2402 }
2403 }
2404
Selim Cinek1408eb52014-06-02 14:45:38 +02002405 private void initDownStates(MotionEvent ev) {
2406 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
2407 mExpandedInThisMotion = false;
2408 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002409 mDisallowScrollingInThisMotion = false;
Adrian Roosfa139752016-04-27 09:59:08 -07002410 mDisallowDismissInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002411 mTouchIsClick = true;
2412 mInitialTouchX = ev.getX();
2413 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02002414 }
Selim Cinek67b22602014-03-10 15:40:16 +01002415 }
2416
Selim Cinekef5127e2015-12-21 16:55:58 -08002417 public void setChildTransferInProgress(boolean childTransferInProgress) {
2418 mChildTransferInProgress = childTransferInProgress;
2419 }
2420
Christoph Studer068f5922014-04-08 17:43:07 -04002421 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002422 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04002423 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002424 // we only call our internal methods if this is actually a removal and not just a
2425 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08002426 if (!mChildTransferInProgress) {
Selim Cinekd1395642016-04-28 12:22:42 -07002427 onViewRemovedInternal(child, this);
Selim Cinekb5605e52015-02-20 18:21:41 +01002428 }
2429 }
2430
Selim Cinek197823d2016-03-24 13:06:00 -07002431 @Override
2432 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2433 super.requestDisallowInterceptTouchEvent(disallowIntercept);
2434 if (disallowIntercept) {
2435 mSwipeHelper.removeLongPressCallback();
2436 }
2437 }
2438
Selim Cineka5703182016-05-11 21:23:16 -04002439 private void onViewRemovedInternal(View child, ViewGroup container) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002440 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002441 // This is only a position change, don't do anything special
2442 return;
2443 }
Selim Cinekd1395642016-04-28 12:22:42 -07002444 ExpandableView expandableView = (ExpandableView) child;
2445 expandableView.setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04002446 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002447 updateScrollStateForRemovedChild(expandableView);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002448 boolean animationGenerated = generateRemoveAnimation(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002449 if (animationGenerated) {
2450 if (!mSwipedOutViews.contains(child)) {
Selim Cineka5703182016-05-11 21:23:16 -04002451 container.getOverlay().add(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002452 } else if (Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
Selim Cineka5703182016-05-11 21:23:16 -04002453 container.addTransientView(child, 0);
2454 expandableView.setTransientContainer(container);
Selim Cinekd1395642016-04-28 12:22:42 -07002455 }
2456 } else {
2457 mSwipedOutViews.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002458 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002459 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002460
2461 // Make sure the clipRect we might have set is removed
Mady Mellorc128f222016-04-26 11:42:46 -07002462 expandableView.setClipTopAmount(0);
Selim Cineke9bad242016-06-15 11:46:37 -07002463
2464 focusNextViewIfFocused(child);
2465 }
2466
2467 private void focusNextViewIfFocused(View view) {
2468 if (view instanceof ExpandableNotificationRow) {
2469 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2470 if (row.shouldRefocusOnDismiss()) {
2471 View nextView = row.getChildAfterViewWhenDismissed();
2472 if (nextView == null) {
2473 View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
2474 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
2475 ? groupParentWhenDismissed.getTranslationY()
2476 : view.getTranslationY());
2477 }
2478 if (nextView != null) {
2479 nextView.requestAccessibilityFocus();
2480 }
2481 }
2482 }
2483
Selim Cinekc27437b2014-05-14 10:23:33 +02002484 }
2485
Selim Cinekb5605e52015-02-20 18:21:41 +01002486 private boolean isChildInGroup(View child) {
2487 return child instanceof ExpandableNotificationRow
2488 && mGroupManager.isChildInGroupWithSummary(
2489 ((ExpandableNotificationRow) child).getStatusBarNotification());
2490 }
2491
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002492 /**
2493 * Generate a remove animation for a child view.
2494 *
2495 * @param child The view to generate the remove animation for.
2496 * @return Whether an animation was generated.
2497 */
2498 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002499 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002500 mAddedHeadsUpChildren.remove(child);
2501 return false;
2502 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002503 if (isClickedHeadsUp(child)) {
2504 // An animation is already running, add it to the Overlay
2505 mClearOverlayViewsWhenFinished.add(child);
2506 return true;
2507 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002508 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002509 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002510 // Generate Animations
2511 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002512 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002513 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002514 } else {
2515 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002516 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002517 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002518 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002519 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002520 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002521 }
2522
Selim Cinek0fccc722015-07-29 17:04:36 -07002523 private boolean isClickedHeadsUp(View child) {
2524 return HeadsUpManager.isClickedHeadsUpNotification(child);
2525 }
2526
Selim Cineke0890e52015-06-17 11:17:08 -07002527 /**
2528 * Remove a removed child view from the heads up animations if it was just added there
2529 *
2530 * @return whether any child was removed from the list to animate
2531 */
2532 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2533 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002534 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2535 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002536 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002537 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002538 mTmpList.add(eventPair);
2539 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002540 }
2541 }
Selim Cineke0890e52015-06-17 11:17:08 -07002542 if (hasAddEvent) {
2543 // This child was just added lets remove all events.
2544 mHeadsUpChangeAnimations.removeAll(mTmpList);
Selim Cinekcafa87f2016-10-26 17:00:17 -07002545 ((ExpandableNotificationRow ) child).setHeadsUpAnimatingAway(false);
Selim Cineke0890e52015-06-17 11:17:08 -07002546 }
2547 mTmpList.clear();
2548 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002549 }
2550
Selim Cinek572bbd42014-04-25 16:43:27 +02002551 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002552 * @param child the child to query
2553 * @return whether a view is not a top level child but a child notification and that group is
2554 * not expanded
2555 */
2556 private boolean isChildInInvisibleGroup(View child) {
2557 if (child instanceof ExpandableNotificationRow) {
2558 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2559 ExpandableNotificationRow groupSummary =
2560 mGroupManager.getGroupSummary(row.getStatusBarNotification());
2561 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07002562 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01002563 }
2564 }
2565 return false;
2566 }
2567
2568 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02002569 * Updates the scroll position when a child was removed
2570 *
2571 * @param removedChild the removed child
2572 */
Selim Cinek61633a82016-01-25 15:54:10 -08002573 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002574 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinek42357e02016-02-24 18:48:01 -08002575 int padding = (int) NotificationUtils.interpolate(
2576 mPaddingBetweenElements,
2577 mIncreasedPaddingBetweenElements,
2578 removedChild.getIncreasedPaddingAmount());
Selim Cinek61633a82016-01-25 15:54:10 -08002579 int childHeight = getIntrinsicHeight(removedChild) + padding;
Selim Cinek572bbd42014-04-25 16:43:27 +02002580 int endPosition = startingPosition + childHeight;
2581 if (endPosition <= mOwnScrollY) {
2582 // This child is fully scrolled of the top, so we have to deduct its height from the
2583 // scrollPosition
Selim Cinekef406062016-09-29 17:33:13 -07002584 setOwnScrollY(mOwnScrollY - childHeight);
Selim Cinek572bbd42014-04-25 16:43:27 +02002585 } else if (startingPosition < mOwnScrollY) {
2586 // This child is currently being scrolled into, set the scroll position to the start of
2587 // this child
Selim Cinekef406062016-09-29 17:33:13 -07002588 setOwnScrollY(startingPosition);
Selim Cinek572bbd42014-04-25 16:43:27 +02002589 }
2590 }
2591
Selim Cinekd7c4e002014-07-04 18:36:42 +02002592 private int getIntrinsicHeight(View view) {
2593 if (view instanceof ExpandableView) {
2594 ExpandableView expandableView = (ExpandableView) view;
2595 return expandableView.getIntrinsicHeight();
2596 }
2597 return view.getHeight();
2598 }
2599
Adrian Roos4a579672016-05-24 16:54:37 -07002600 private int getPositionInLinearLayout(View requestedView) {
2601 ExpandableNotificationRow childInGroup = null;
2602 ExpandableNotificationRow requestedRow = null;
2603 if (isChildInGroup(requestedView)) {
2604 // We're asking for a child in a group. Calculate the position of the parent first,
2605 // then within the parent.
2606 childInGroup = (ExpandableNotificationRow) requestedView;
2607 requestedView = requestedRow = childInGroup.getNotificationParent();
2608 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002609 int position = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08002610 float previousIncreasedAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02002611 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002612 ExpandableView child = (ExpandableView) getChildAt(i);
2613 boolean notGone = child.getVisibility() != View.GONE;
Selim Cinek281c2022016-10-13 19:14:43 -07002614 if (notGone && !child.hasNoContentHeight()) {
Selim Cinek42357e02016-02-24 18:48:01 -08002615 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cinek61633a82016-01-25 15:54:10 -08002616 if (position != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08002617 position += (int) NotificationUtils.interpolate(
2618 mPaddingBetweenElements,
2619 mIncreasedPaddingBetweenElements,
2620 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Selim Cinek61633a82016-01-25 15:54:10 -08002621 }
Selim Cinek42357e02016-02-24 18:48:01 -08002622 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002623 }
Adrian Roos4a579672016-05-24 16:54:37 -07002624 if (child == requestedView) {
2625 if (requestedRow != null) {
2626 position += requestedRow.getPositionOfChild(childInGroup);
2627 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002628 return position;
2629 }
Selim Cinek61633a82016-01-25 15:54:10 -08002630 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02002631 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002632 }
2633 }
2634 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02002635 }
2636
2637 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002638 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02002639 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002640 onViewAddedInternal(child);
2641 }
2642
Selim Cinek614576e2016-01-20 10:54:09 -08002643 private void updateFirstAndLastBackgroundViews() {
2644 ActivatableNotificationView firstChild = getFirstChildWithBackground();
2645 ActivatableNotificationView lastChild = getLastChildWithBackground();
2646 if (mAnimationsEnabled && mIsExpanded) {
2647 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2648 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2649 } else {
2650 mAnimateNextBackgroundTop = false;
2651 mAnimateNextBackgroundBottom = false;
2652 }
2653 mFirstVisibleBackgroundChild = firstChild;
2654 mLastVisibleBackgroundChild = lastChild;
Selim Cinekdb167372016-11-17 15:41:17 -08002655 mAmbientState.setLastVisibleBackgroundChild(lastChild);
Selim Cinek614576e2016-01-20 10:54:09 -08002656 }
2657
Selim Cinekb5605e52015-02-20 18:21:41 +01002658 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002659 updateHideSensitiveForChild(child);
Jorim Jaggibe565df2014-04-28 17:51:23 +02002660 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00002661 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002662 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02002663 updateChronometerForChild(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002664 }
2665
Selim Cinekd06c41c2015-07-06 14:51:36 -07002666 private void updateHideSensitiveForChild(View child) {
Selim Cinekcfcd23e2016-05-05 12:31:42 -04002667 if (child instanceof ExpandableView) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002668 ExpandableView expandableView = (ExpandableView) child;
Selim Cinekcfcd23e2016-05-05 12:31:42 -04002669 expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
Selim Cinekd06c41c2015-07-06 14:51:36 -07002670 }
2671 }
2672
Selim Cinekd1395642016-04-28 12:22:42 -07002673 public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
2674 onViewRemovedInternal(row, childrenContainer);
Selim Cinekb5605e52015-02-20 18:21:41 +01002675 }
2676
2677 public void notifyGroupChildAdded(View row) {
2678 onViewAddedInternal(row);
2679 }
2680
Jorim Jaggi75c95042014-05-16 19:09:59 +02002681 public void setAnimationsEnabled(boolean animationsEnabled) {
2682 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002683 updateNotificationAnimationStates();
2684 }
2685
2686 private void updateNotificationAnimationStates() {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002687 boolean running = mAnimationsEnabled || mPulsing;
Selim Cinekcab4a602014-09-03 14:47:57 +02002688 int childCount = getChildCount();
2689 for (int i = 0; i < childCount; i++) {
2690 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07002691 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02002692 updateAnimationState(running, child);
2693 }
2694 }
2695
Selim Cinek51ae05d2014-09-09 15:51:38 +02002696 private void updateAnimationState(View child) {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002697 updateAnimationState((mAnimationsEnabled || mPulsing)
2698 && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002699 }
2700
2701
Selim Cinekcab4a602014-09-03 14:47:57 +02002702 private void updateAnimationState(boolean running, View child) {
2703 if (child instanceof ExpandableNotificationRow) {
2704 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2705 row.setIconAnimationRunning(running);
2706 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02002707 }
2708
2709 public boolean isAddOrRemoveAnimationPending() {
2710 return mNeedsAnimation
2711 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2712 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002713 /**
2714 * Generate an animation for an added child view.
2715 *
2716 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00002717 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002718 */
Jorim Jaggif6411742014-08-05 17:10:43 +00002719 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002720 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002721 // Generate Animations
2722 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00002723 if (fromMoreCard) {
2724 mFromMoreCardAdditions.add(child);
2725 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002726 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02002727 }
Adrian Roos777ef562015-12-01 17:37:14 -08002728 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002729 mAddedHeadsUpChildren.add(child);
2730 mChildrenToAddAnimated.remove(child);
2731 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002732 }
2733
2734 /**
2735 * Change the position of child to a new location
2736 *
2737 * @param child the view to change the position for
2738 * @param newIndex the new index
2739 */
2740 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002741 int currentIndex = indexOfChild(child);
2742 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002743 mChangePositionInProgress = true;
Adrian Roos14503e22016-03-09 14:01:24 -08002744 ((ExpandableView)child).setChangingPosition(true);
Selim Cinekc27437b2014-05-14 10:23:33 +02002745 removeView(child);
2746 addView(child, newIndex);
Adrian Roos14503e22016-03-09 14:01:24 -08002747 ((ExpandableView)child).setChangingPosition(false);
Selim Cinek159ffdb2014-06-04 22:24:18 +02002748 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04002749 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002750 mChildrenChangingPositions.add(child);
2751 mNeedsAnimation = true;
2752 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002753 }
2754 }
2755
Selim Cinekf4c19962014-05-01 21:55:31 +02002756 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002757 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002758 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002759 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002760 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002761 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Selim Cinekea66ca02016-05-24 13:33:47 -07002762 setAnimationRunning(true);
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002763 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2764 mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002765 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08002766 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002767 updateViewShadows();
Selim Cinekf4c19962014-05-01 21:55:31 +02002768 } else {
2769 applyCurrentState();
2770 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002771 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02002772 }
2773
2774 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07002775 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02002776 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002777 generateChildAdditionEvents();
2778 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02002779 generateSnapBackEvents();
2780 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002781 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002782 generateActivateEvent();
2783 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02002784 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04002785 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002786 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02002787 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01002788 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02002789 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002790 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002791 }
2792
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002793 private void generateHeadsUpAnimationEvents() {
2794 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002795 ExpandableNotificationRow row = eventPair.first;
2796 boolean isHeadsUp = eventPair.second;
2797 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2798 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07002799 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07002800 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002801 type = row.wasJustClicked()
2802 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2803 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinek76e813c2016-07-14 11:16:58 -07002804 if (row.isChildInGroup()) {
2805 // We can otherwise get stuck in there if it was just isolated
Selim Cinekcafa87f2016-10-26 17:00:17 -07002806 row.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07002807 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002808 } else {
Selim Cinekbbcebde2016-11-09 18:28:20 -08002809 ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
Selim Cinekeaee9c02015-06-25 11:04:20 -04002810 if (viewState == null) {
2811 // A view state was never generated for this view, so we don't need to animate
2812 // this. This may happen with notification children.
2813 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07002814 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002815 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2816 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2817 // Our custom add animation
2818 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2819 } else {
2820 // Normal add animation
2821 type = AnimationEvent.ANIMATION_TYPE_ADD;
2822 }
2823 onBottom = !pinnedAndClosed;
2824 }
Selim Cineka59ecc32015-04-07 10:51:49 -07002825 }
2826 AnimationEvent event = new AnimationEvent(row, type);
2827 event.headsUpFromBottom = onBottom;
2828 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002829 }
2830 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07002831 mAddedHeadsUpChildren.clear();
2832 }
2833
Selim Cinekbbcebde2016-11-09 18:28:20 -08002834 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002835 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2836 return false;
2837 }
2838 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002839 }
2840
Selim Cinekb5605e52015-02-20 18:21:41 +01002841 private void generateGroupExpansionEvent() {
2842 // Generate a group expansion/collapsing event if there is such a group at all
2843 if (mExpandedGroupView != null) {
2844 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2845 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2846 mExpandedGroupView = null;
2847 }
2848 }
2849
Selim Cineka5e211b2014-08-11 17:35:48 +02002850 private void generateViewResizeEvent() {
2851 if (mNeedViewResizeAnimation) {
2852 mAnimationEvents.add(
2853 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2854 }
2855 mNeedViewResizeAnimation = false;
2856 }
2857
Selim Cinekeb973562014-05-02 17:07:49 +02002858 private void generateSnapBackEvents() {
2859 for (View child : mSnappedBackChildren) {
2860 mAnimationEvents.add(new AnimationEvent(child,
2861 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2862 }
2863 mSnappedBackChildren.clear();
2864 }
2865
2866 private void generateDragEvents() {
2867 for (View child : mDragAnimPendingChildren) {
2868 mAnimationEvents.add(new AnimationEvent(child,
2869 AnimationEvent.ANIMATION_TYPE_START_DRAG));
2870 }
2871 mDragAnimPendingChildren.clear();
2872 }
2873
Selim Cinek572bbd42014-04-25 16:43:27 +02002874 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002875 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002876 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2877 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002878 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2879 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002880 AnimationEvent event = new AnimationEvent(child, animationType);
2881
2882 // we need to know the view after this one
2883 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2884 mAnimationEvents.add(event);
Selim Cinekd1395642016-04-28 12:22:42 -07002885 mSwipedOutViews.remove(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002886 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002887 mChildrenToRemoveAnimated.clear();
2888 }
2889
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002890 private void generatePositionChangeEvents() {
2891 for (View child : mChildrenChangingPositions) {
2892 mAnimationEvents.add(new AnimationEvent(child,
2893 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2894 }
2895 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002896 if (mGenerateChildOrderChangedEvent) {
2897 mAnimationEvents.add(new AnimationEvent(null,
2898 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2899 mGenerateChildOrderChangedEvent = false;
2900 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002901 }
2902
Selim Cinek572bbd42014-04-25 16:43:27 +02002903 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002904 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002905 if (mFromMoreCardAdditions.contains(child)) {
2906 mAnimationEvents.add(new AnimationEvent(child,
2907 AnimationEvent.ANIMATION_TYPE_ADD,
2908 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2909 } else {
2910 mAnimationEvents.add(new AnimationEvent(child,
2911 AnimationEvent.ANIMATION_TYPE_ADD));
2912 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002913 }
2914 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002915 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002916 }
2917
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002918 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002919 if (mTopPaddingNeedsAnimation) {
2920 mAnimationEvents.add(
2921 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2922 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002923 mTopPaddingNeedsAnimation = false;
2924 }
2925
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002926 private void generateActivateEvent() {
2927 if (mActivateNeedsAnimation) {
2928 mAnimationEvents.add(
2929 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2930 }
2931 mActivateNeedsAnimation = false;
2932 }
2933
Selim Cinekd9acca52014-09-01 22:33:25 +02002934 private void generateAnimateEverythingEvent() {
2935 if (mEverythingNeedsAnimation) {
2936 mAnimationEvents.add(
2937 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2938 }
2939 mEverythingNeedsAnimation = false;
2940 }
2941
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002942 private void generateDimmedEvent() {
2943 if (mDimmedNeedsAnimation) {
2944 mAnimationEvents.add(
2945 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2946 }
2947 mDimmedNeedsAnimation = false;
2948 }
2949
Jorim Jaggiae441282014-08-01 02:45:18 +02002950 private void generateHideSensitiveEvent() {
2951 if (mHideSensitiveNeedsAnimation) {
2952 mAnimationEvents.add(
2953 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2954 }
2955 mHideSensitiveNeedsAnimation = false;
2956 }
2957
John Spurlockbf370992014-06-17 13:58:31 -04002958 private void generateDarkEvent() {
2959 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002960 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2961 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2962 mAnimationEvents.add(ev);
Selim Cinek972123d2016-05-03 14:25:58 -07002963 startBackgroundFadeIn();
John Spurlockbf370992014-06-17 13:58:31 -04002964 }
2965 mDarkNeedsAnimation = false;
2966 }
2967
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002968 private void generateGoToFullShadeEvent() {
2969 if (mGoToFullShadeNeedsAnimation) {
2970 mAnimationEvents.add(
2971 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2972 }
2973 mGoToFullShadeNeedsAnimation = false;
2974 }
2975
Selim Cinek67b22602014-03-10 15:40:16 +01002976 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002977 if (!isScrollingEnabled()) {
2978 return false;
2979 }
Selim Cinek67b22602014-03-10 15:40:16 +01002980 /*
2981 * This method JUST determines whether we want to intercept the motion.
2982 * If we return true, onMotionEvent will be called and we do the actual
2983 * scrolling there.
2984 */
2985
2986 /*
2987 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002988 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002989 * motion.
2990 */
2991 final int action = ev.getAction();
2992 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2993 return true;
2994 }
2995
Selim Cinek67b22602014-03-10 15:40:16 +01002996 switch (action & MotionEvent.ACTION_MASK) {
2997 case MotionEvent.ACTION_MOVE: {
2998 /*
2999 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04003000 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01003001 */
3002
3003 /*
3004 * Locally do absolute value. mLastMotionY is set to the y value
3005 * of the down event.
3006 */
3007 final int activePointerId = mActivePointerId;
3008 if (activePointerId == INVALID_POINTER) {
3009 // If we don't have a valid id, the touch down wasn't on content.
3010 break;
3011 }
3012
3013 final int pointerIndex = ev.findPointerIndex(activePointerId);
3014 if (pointerIndex == -1) {
3015 Log.e(TAG, "Invalid pointerId=" + activePointerId
3016 + " in onInterceptTouchEvent");
3017 break;
3018 }
3019
3020 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02003021 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01003022 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02003023 final int xDiff = Math.abs(x - mDownX);
3024 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01003025 setIsBeingDragged(true);
3026 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02003027 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01003028 initVelocityTrackerIfNotExists();
3029 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01003030 }
3031 break;
3032 }
3033
3034 case MotionEvent.ACTION_DOWN: {
3035 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05303036 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01003037 if (getChildAtPosition(ev.getX(), y) == null) {
3038 setIsBeingDragged(false);
3039 recycleVelocityTracker();
3040 break;
3041 }
3042
3043 /*
3044 * Remember location of down touch.
3045 * ACTION_DOWN always refers to pointer index 0.
3046 */
3047 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02003048 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01003049 mActivePointerId = ev.getPointerId(0);
3050
3051 initOrResetVelocityTracker();
3052 mVelocityTracker.addMovement(ev);
3053 /*
3054 * If being flinged and user touches the screen, initiate drag;
3055 * otherwise don't. mScroller.isFinished should be false when
3056 * being flinged.
3057 */
3058 boolean isBeingDragged = !mScroller.isFinished();
3059 setIsBeingDragged(isBeingDragged);
3060 break;
3061 }
3062
3063 case MotionEvent.ACTION_CANCEL:
3064 case MotionEvent.ACTION_UP:
3065 /* Release the drag */
3066 setIsBeingDragged(false);
3067 mActivePointerId = INVALID_POINTER;
3068 recycleVelocityTracker();
3069 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
3070 postInvalidateOnAnimation();
3071 }
3072 break;
3073 case MotionEvent.ACTION_POINTER_UP:
3074 onSecondaryPointerUp(ev);
3075 break;
3076 }
3077
3078 /*
3079 * The only time we want to intercept motion events is if we are in the
3080 * drag mode.
3081 */
3082 return mIsBeingDragged;
3083 }
3084
Muyuan Li333a4fc2016-04-16 17:13:46 -07003085 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
3086 return new StackScrollAlgorithm(context);
3087 }
3088
Jorim Jaggife6bfa62014-05-07 23:23:18 +02003089 /**
3090 * @return Whether the specified motion event is actually happening over the content.
3091 */
3092 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01003093 return isInContentBounds(event.getY());
3094 }
3095
3096 /**
3097 * @return Whether a y coordinate is inside the content.
3098 */
3099 public boolean isInContentBounds(float y) {
3100 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02003101 }
3102
Selim Cinek67b22602014-03-10 15:40:16 +01003103 private void setIsBeingDragged(boolean isDragged) {
3104 mIsBeingDragged = isDragged;
3105 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01003106 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02003107 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01003108 }
3109 }
3110
3111 @Override
3112 public void onWindowFocusChanged(boolean hasWindowFocus) {
3113 super.onWindowFocusChanged(hasWindowFocus);
3114 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02003115 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01003116 }
3117 }
Selim Cinekfab078b2014-03-27 22:45:58 +01003118
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07003119 @Override
Adrian Roos181385c2016-05-05 17:45:44 -04003120 public void clearChildFocus(View child) {
3121 super.clearChildFocus(child);
3122 if (mForcedScroll == child) {
3123 mForcedScroll = null;
3124 }
3125 }
3126
3127 @Override
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07003128 public void requestDisallowLongPress() {
3129 removeLongPressCallback();
3130 }
3131
Adrian Roosfa139752016-04-27 09:59:08 -07003132 @Override
3133 public void requestDisallowDismiss() {
3134 mDisallowDismissInThisMotion = true;
3135 }
3136
Selim Cinek1408eb52014-06-02 14:45:38 +02003137 public void removeLongPressCallback() {
3138 mSwipeHelper.removeLongPressCallback();
3139 }
3140
Selim Cinekfab078b2014-03-27 22:45:58 +01003141 @Override
3142 public boolean isScrolledToTop() {
3143 return mOwnScrollY == 0;
3144 }
3145
3146 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01003147 public boolean isScrolledToBottom() {
3148 return mOwnScrollY >= getScrollRange();
3149 }
3150
3151 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01003152 public View getHostView() {
3153 return this;
3154 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003155
Selim Cinekb6d85eb2014-03-28 20:21:01 +01003156 public int getEmptyBottomMargin() {
Selim Cinekdb167372016-11-17 15:41:17 -08003157 return Math.max(mMaxLayoutHeight - mContentHeight, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01003158 }
3159
Selim Cinek1685e632014-04-08 02:27:49 +02003160 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02003161 mIsExpansionChanging = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08003162 mAmbientState.setExpansionChanging(true);
Selim Cinek1685e632014-04-08 02:27:49 +02003163 }
3164
3165 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02003166 mIsExpansionChanging = false;
Selim Cinekd5ab6452016-12-08 16:34:00 -08003167 mAmbientState.setExpansionChanging(false);
Selim Cinek4fe3e472014-07-03 16:32:54 +02003168 if (!mIsExpanded) {
Selim Cinekef406062016-09-29 17:33:13 -07003169 setOwnScrollY(0);
Selim Cinek78f40082016-03-10 18:06:27 -08003170 mPhoneStatusBar.resetUserExpandedStates();
Selim Cinekf336f4c2014-11-12 16:58:16 +01003171
Selim Cinekd1395642016-04-28 12:22:42 -07003172 // lets make sure nothing is in the overlay / transient anymore
Selim Cineka5703182016-05-11 21:23:16 -04003173 clearTemporaryViews(this);
Selim Cinekd1395642016-04-28 12:22:42 -07003174 for (int i = 0; i < getChildCount(); i++) {
3175 ExpandableView child = (ExpandableView) getChildAt(i);
3176 if (child instanceof ExpandableNotificationRow) {
3177 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Selim Cineka5703182016-05-11 21:23:16 -04003178 clearTemporaryViews(row.getChildrenContainer());
Selim Cinekd1395642016-04-28 12:22:42 -07003179 }
3180 }
Selim Cinek4fe3e472014-07-03 16:32:54 +02003181 }
Selim Cinek1685e632014-04-08 02:27:49 +02003182 }
3183
Selim Cineka5703182016-05-11 21:23:16 -04003184 private void clearTemporaryViews(ViewGroup viewGroup) {
Selim Cinekd1395642016-04-28 12:22:42 -07003185 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
Selim Cinek81f26d32016-05-09 18:54:10 -04003186 viewGroup.removeTransientView(viewGroup.getTransientView(0));
Selim Cinekd1395642016-04-28 12:22:42 -07003187 }
Selim Cineka5703182016-05-11 21:23:16 -04003188 if (viewGroup != null) {
3189 viewGroup.getOverlay().clear();
3190 }
Selim Cinekd1395642016-04-28 12:22:42 -07003191 }
3192
Jorim Jaggie4b840d2015-06-30 16:19:17 -07003193 public void onPanelTrackingStarted() {
3194 mPanelTracking = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08003195 mAmbientState.setPanelTracking(true);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07003196 }
3197 public void onPanelTrackingStopped() {
3198 mPanelTracking = false;
Selim Cinekd5ab6452016-12-08 16:34:00 -08003199 mAmbientState.setPanelTracking(false);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07003200 }
3201
Selim Cinekb24e0a92015-06-09 20:17:30 -07003202 public void resetScrollPosition() {
3203 mScroller.abortAnimation();
Selim Cinekef406062016-09-29 17:33:13 -07003204 setOwnScrollY(0);
Selim Cinekb24e0a92015-06-09 20:17:30 -07003205 }
3206
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02003207 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02003208 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02003209 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02003210 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02003211 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08003212 if (!mIsExpanded) {
3213 mGroupManager.collapseAllGroups();
3214 }
Selim Cinekcab4a602014-09-03 14:47:57 +02003215 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02003216 updateChronometers();
Selim Cinekf3fa6852016-12-20 18:36:02 +01003217 requestChildrenUpdate();
Selim Cinek98713a42015-09-21 15:47:20 +02003218 }
3219 }
3220
3221 private void updateChronometers() {
3222 int childCount = getChildCount();
3223 for (int i = 0; i < childCount; i++) {
3224 updateChronometerForChild(getChildAt(i));
3225 }
3226 }
3227
3228 private void updateChronometerForChild(View child) {
3229 if (child instanceof ExpandableNotificationRow) {
3230 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3231 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02003232 }
Selim Cinek1685e632014-04-08 02:27:49 +02003233 }
3234
Jorim Jaggibe565df2014-04-28 17:51:23 +02003235 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003236 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003237 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02003238 updateScrollPositionOnExpandInBottom(view);
3239 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02003240 notifyHeightChangeListener(view);
Selim Cinekbc243a92016-09-27 16:35:13 -07003241 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
3242 ? (ExpandableNotificationRow) view
3243 : null;
3244 if (row != null && (row == mFirstVisibleBackgroundChild
3245 || row.getNotificationParent() == mFirstVisibleBackgroundChild)) {
3246 updateAlgorithmLayoutMinHeight();
3247 }
Selim Cinekb5605e52015-02-20 18:21:41 +01003248 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08003249 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01003250 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003251 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02003252 }
3253
Selim Cineka5e211b2014-08-11 17:35:48 +02003254 @Override
3255 public void onReset(ExpandableView view) {
Selim Cinek51ae05d2014-09-09 15:51:38 +02003256 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02003257 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02003258 }
3259
Selim Cinekf7a14c02014-07-07 14:01:46 +02003260 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
3261 if (view instanceof ExpandableNotificationRow) {
3262 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02003263 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08003264 if (row.isSummaryWithChildren()) {
3265 return;
3266 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02003267 // We are actually expanding this view
Selim Cinek4e4cac32016-03-11 16:45:52 -08003268 float endPosition = row.getTranslationY() + row.getActualHeight();
Selim Cinek388df6d2015-10-22 13:25:11 -07003269 if (row.isChildInGroup()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08003270 endPosition += row.getNotificationParent().getTranslationY();
Selim Cinek388df6d2015-10-22 13:25:11 -07003271 }
Selim Cinekdb167372016-11-17 15:41:17 -08003272 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
3273 if (row != mLastVisibleBackgroundChild) {
3274 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
3275 }
3276 if (endPosition > layoutEnd) {
3277 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
Selim Cinekf7a14c02014-07-07 14:01:46 +02003278 mDisallowScrollingInThisMotion = true;
3279 }
3280 }
3281 }
3282 }
3283
Jorim Jaggibe565df2014-04-28 17:51:23 +02003284 public void setOnHeightChangedListener(
3285 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
3286 this.mOnHeightChangedListener = mOnHeightChangedListener;
3287 }
3288
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003289 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
3290 mOnEmptySpaceClickListener = listener;
3291 }
3292
Selim Cinek572bbd42014-04-25 16:43:27 +02003293 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08003294 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02003295 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07003296 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07003297 clearViewOverlays();
Selim Cinek8fc78752016-07-13 14:34:56 -07003298 clearHeadsUpDisappearRunning();
3299 }
3300
3301 private void clearHeadsUpDisappearRunning() {
3302 for (int i = 0; i < getChildCount(); i++) {
3303 View view = getChildAt(i);
3304 if (view instanceof ExpandableNotificationRow) {
Selim Cinek76e813c2016-07-14 11:16:58 -07003305 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcafa87f2016-10-26 17:00:17 -07003306 row.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07003307 if (row.isSummaryWithChildren()) {
3308 for (ExpandableNotificationRow child : row.getNotificationChildren()) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07003309 child.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07003310 }
3311 }
Selim Cinek8fc78752016-07-13 14:34:56 -07003312 }
3313 }
Selim Cinek0fccc722015-07-29 17:04:36 -07003314 }
3315
3316 private void clearViewOverlays() {
3317 for (View view : mClearOverlayViewsWhenFinished) {
Selim Cineka5703182016-05-11 21:23:16 -04003318 StackStateAnimator.removeFromOverlay(view);
Selim Cinek0fccc722015-07-29 17:04:36 -07003319 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07003320 }
3321
3322 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003323 for (Runnable runnable : mAnimationFinishedRunnables) {
3324 runnable.run();
3325 }
3326 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02003327 }
3328
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003329 /**
3330 * See {@link AmbientState#setDimmed}.
3331 */
3332 public void setDimmed(boolean dimmed, boolean animate) {
3333 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02003334 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003335 mDimmedNeedsAnimation = true;
3336 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08003337 animateDimmed(dimmed);
3338 } else {
3339 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003340 }
3341 requestChildrenUpdate();
3342 }
3343
Selim Cinekd35c2792016-01-21 13:20:57 -08003344 private void setDimAmount(float dimAmount) {
3345 mDimAmount = dimAmount;
3346 updateBackgroundDimming();
3347 }
3348
3349 private void animateDimmed(boolean dimmed) {
3350 if (mDimAnimator != null) {
3351 mDimAnimator.cancel();
3352 }
3353 float target = dimmed ? 1.0f : 0.0f;
3354 if (target == mDimAmount) {
3355 return;
3356 }
3357 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
3358 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
3359 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
3360 mDimAnimator.addListener(mDimEndListener);
3361 mDimAnimator.addUpdateListener(mDimUpdateListener);
3362 mDimAnimator.start();
3363 }
3364
Jorim Jaggiae441282014-08-01 02:45:18 +02003365 public void setHideSensitive(boolean hideSensitive, boolean animate) {
3366 if (hideSensitive != mAmbientState.isHideSensitive()) {
3367 int childCount = getChildCount();
3368 for (int i = 0; i < childCount; i++) {
3369 ExpandableView v = (ExpandableView) getChildAt(i);
3370 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
3371 }
3372 mAmbientState.setHideSensitive(hideSensitive);
3373 if (animate && mAnimationsEnabled) {
3374 mHideSensitiveNeedsAnimation = true;
3375 mNeedsAnimation = true;
3376 }
3377 requestChildrenUpdate();
3378 }
3379 }
3380
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003381 /**
3382 * See {@link AmbientState#setActivatedChild}.
3383 */
Selim Cineka32ab602014-06-11 15:06:01 +02003384 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003385 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02003386 if (mAnimationsEnabled) {
3387 mActivateNeedsAnimation = true;
3388 mNeedsAnimation = true;
3389 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003390 requestChildrenUpdate();
3391 }
3392
Selim Cineka32ab602014-06-11 15:06:01 +02003393 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003394 return mAmbientState.getActivatedChild();
3395 }
3396
Selim Cinek572bbd42014-04-25 16:43:27 +02003397 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02003398 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02003399 if (mListener != null) {
3400 mListener.onChildLocationsChanged(this);
3401 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07003402 runAnimationFinishedRunnables();
Selim Cinekea66ca02016-05-24 13:33:47 -07003403 setAnimationRunning(false);
Selim Cinek6811d722016-01-19 17:53:12 -08003404 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08003405 updateViewShadows();
3406 }
3407
3408 private void updateViewShadows() {
3409 // we need to work around an issue where the shadow would not cast between siblings when
3410 // their z difference is between 0 and 0.1
3411
3412 // Lefts first sort by Z difference
3413 for (int i = 0; i < getChildCount(); i++) {
3414 ExpandableView child = (ExpandableView) getChildAt(i);
3415 if (child.getVisibility() != GONE) {
3416 mTmpSortedChildren.add(child);
3417 }
3418 }
3419 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
3420
3421 // Now lets update the shadow for the views
3422 ExpandableView previous = null;
3423 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
3424 ExpandableView expandableView = mTmpSortedChildren.get(i);
3425 float translationZ = expandableView.getTranslationZ();
3426 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
3427 float diff = otherZ - translationZ;
3428 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
3429 // There is no fake shadow to be drawn
3430 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
3431 } else {
3432 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
Mady Mellorb0a82462016-04-30 17:31:02 -07003433 expandableView.getTranslationY() - previous.getExtraBottomPadding();
3434 expandableView.setFakeShadowIntensity(
3435 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
Selim Cinek33223572016-02-19 19:32:22 -08003436 previous.getOutlineAlpha(), (int) yLocation,
3437 previous.getOutlineTranslation());
3438 }
3439 previous = expandableView;
3440 }
3441
3442 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02003443 }
3444
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003445 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003446 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003447 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003448 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003449 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003450 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003451 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02003452 }
3453
Selim Cinek1408eb52014-06-02 14:45:38 +02003454 public void cancelExpandHelper() {
3455 mExpandHelper.cancel();
3456 }
3457
3458 public void setIntrinsicPadding(int intrinsicPadding) {
3459 mIntrinsicPadding = intrinsicPadding;
3460 }
3461
Jorim Jaggi30c305c2014-07-01 23:34:41 +02003462 public int getIntrinsicPadding() {
3463 return mIntrinsicPadding;
3464 }
3465
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003466 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02003467 * @return the y position of the first notification
3468 */
3469 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07003470 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02003471 }
3472
Selim Cinekc0ce82d2014-06-10 13:21:15 +02003473 @Override
3474 public boolean shouldDelayChildPressedState() {
3475 return true;
3476 }
3477
Jorim Jaggi457cc352014-06-02 22:47:42 +02003478 /**
John Spurlockbf370992014-06-17 13:58:31 -04003479 * See {@link AmbientState#setDark}.
3480 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003481 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04003482 mAmbientState.setDark(dark);
3483 if (animate && mAnimationsEnabled) {
3484 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003485 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04003486 mNeedsAnimation = true;
Selim Cinek972123d2016-05-03 14:25:58 -07003487 setBackgroundFadeAmount(0.0f);
3488 } else if (!dark) {
3489 setBackgroundFadeAmount(1.0f);
John Spurlockbf370992014-06-17 13:58:31 -04003490 }
3491 requestChildrenUpdate();
Selim Cinek6811d722016-01-19 17:53:12 -08003492 if (dark) {
3493 setWillNotDraw(!DEBUG);
3494 mScrimController.setExcludedBackgroundArea(null);
3495 } else {
3496 updateBackground();
3497 setWillNotDraw(false);
Selim Cinek6811d722016-01-19 17:53:12 -08003498 }
Adrian Roos7a9551a2017-01-11 12:27:49 -08003499 updateContentHeight();
3500 notifyHeightChangeListener(mShelf);
John Spurlockbf370992014-06-17 13:58:31 -04003501 }
3502
Selim Cinek972123d2016-05-03 14:25:58 -07003503 private void setBackgroundFadeAmount(float fadeAmount) {
3504 mBackgroundFadeAmount = fadeAmount;
3505 updateBackgroundDimming();
3506 }
3507
3508 public float getBackgroundFadeAmount() {
3509 return mBackgroundFadeAmount;
3510 }
3511
3512 private void startBackgroundFadeIn() {
3513 ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
3514 int maxLength;
3515 if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE
3516 || mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
3517 maxLength = getNotGoneChildCount() - 1;
3518 } else {
3519 maxLength = Math.max(mDarkAnimationOriginIndex,
3520 getNotGoneChildCount() - mDarkAnimationOriginIndex - 1);
3521 }
Selim Cinekea66ca02016-05-24 13:33:47 -07003522 maxLength = Math.max(0, maxLength);
Selim Cinek972123d2016-05-03 14:25:58 -07003523 long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK;
3524 fadeAnimator.setStartDelay(delay);
3525 fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
3526 fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
3527 fadeAnimator.start();
3528 }
3529
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003530 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
Selim Cinekbc243a92016-09-27 16:35:13 -07003531 if (screenLocation == null || screenLocation.y < mTopPadding) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003532 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3533 }
3534 if (screenLocation.y > getBottomMostNotificationBottom()) {
3535 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
3536 }
3537 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
3538 if (child != null) {
3539 return getNotGoneIndex(child);
3540 } else {
3541 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3542 }
3543 }
3544
3545 private int getNotGoneIndex(View child) {
3546 int count = getChildCount();
3547 int notGoneIndex = 0;
3548 for (int i = 0; i < count; i++) {
3549 View v = getChildAt(i);
3550 if (child == v) {
3551 return notGoneIndex;
3552 }
3553 if (v.getVisibility() != View.GONE) {
3554 notGoneIndex++;
3555 }
3556 }
3557 return -1;
3558 }
3559
Dan Sandlereceda3d2014-07-21 15:35:01 -04003560 public void setDismissView(DismissView dismissView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003561 int index = -1;
3562 if (mDismissView != null) {
3563 index = indexOfChild(mDismissView);
3564 removeView(mDismissView);
3565 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003566 mDismissView = dismissView;
Selim Cinek01af3342016-02-09 19:25:31 -08003567 addView(mDismissView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003568 }
3569
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003570 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003571 int index = -1;
3572 if (mEmptyShadeView != null) {
3573 index = indexOfChild(mEmptyShadeView);
3574 removeView(mEmptyShadeView);
3575 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003576 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08003577 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003578 }
3579
3580 public void updateEmptyShadeView(boolean visible) {
3581 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
3582 int newVisibility = visible ? VISIBLE : GONE;
3583 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003584 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003585 if (mEmptyShadeView.willBeGone()) {
3586 mEmptyShadeView.cancelAnimation();
3587 } else {
3588 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003589 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003590 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003591 mEmptyShadeView.setWillBeGone(false);
3592 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003593 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003594 } else {
Selim Cinek20867102014-12-10 17:09:17 +01003595 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003596 @Override
3597 public void run() {
3598 mEmptyShadeView.setVisibility(GONE);
3599 mEmptyShadeView.setWillBeGone(false);
3600 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003601 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003602 }
Selim Cinek20867102014-12-10 17:09:17 +01003603 };
Selim Cineka9d3efd2016-08-16 14:16:59 -07003604 if (mAnimationsEnabled && mIsExpanded) {
Selim Cinek20867102014-12-10 17:09:17 +01003605 mEmptyShadeView.setWillBeGone(true);
3606 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
3607 } else {
3608 mEmptyShadeView.setInvisible();
3609 onFinishedRunnable.run();
3610 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003611 }
3612 }
3613 }
3614
Dan Sandlereceda3d2014-07-21 15:35:01 -04003615 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003616 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003617 int newVisibility = visible ? VISIBLE : GONE;
3618 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003619 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003620 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003621 mDismissView.cancelAnimation();
3622 } else {
3623 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003624 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003625 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003626 mDismissView.setWillBeGone(false);
3627 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003628 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003629 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003630 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003631 @Override
3632 public void run() {
3633 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003634 mDismissView.setWillBeGone(false);
3635 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003636 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003637 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01003638 };
Selim Cinek20867102014-12-10 17:09:17 +01003639 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003640 mDismissView.setWillBeGone(true);
3641 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3642 } else {
3643 dimissHideFinishRunnable.run();
Selim Cinek7d5f3742014-11-07 18:07:49 +01003644 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003645 }
3646 }
3647 }
3648
3649 public void setDismissAllInProgress(boolean dismissAllInProgress) {
3650 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07003651 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07003652 handleDismissAllClipping();
3653 }
3654
3655 private void handleDismissAllClipping() {
3656 final int count = getChildCount();
3657 boolean previousChildWillBeDismissed = false;
3658 for (int i = 0; i < count; i++) {
3659 ExpandableView child = (ExpandableView) getChildAt(i);
3660 if (child.getVisibility() == GONE) {
3661 continue;
3662 }
3663 if (mDismissAllInProgress && previousChildWillBeDismissed) {
3664 child.setMinClipTopAmount(child.getClipTopAmount());
3665 } else {
3666 child.setMinClipTopAmount(0);
3667 }
3668 previousChildWillBeDismissed = canChildBeDismissed(child);
3669 }
Selim Cineka272dfe2015-02-20 18:12:28 +01003670 }
3671
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003672 public boolean isDismissViewNotGone() {
3673 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3674 }
3675
3676 public boolean isDismissViewVisible() {
3677 return mDismissView.isVisible();
3678 }
3679
3680 public int getDismissViewHeight() {
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08003681 return mDismissView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003682 }
3683
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01003684 public int getEmptyShadeViewHeight() {
3685 return mEmptyShadeView.getHeight();
3686 }
3687
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003688 public float getBottomMostNotificationBottom() {
3689 final int count = getChildCount();
3690 float max = 0;
3691 for (int childIdx = 0; childIdx < count; childIdx++) {
3692 ExpandableView child = (ExpandableView) getChildAt(childIdx);
3693 if (child.getVisibility() == GONE) {
3694 continue;
3695 }
Selim Cineka686b2c2016-10-26 13:58:27 -07003696 float bottom = child.getTranslationY() + child.getActualHeight()
3697 - child.getClipBottomAmount();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003698 if (bottom > max) {
3699 max = bottom;
3700 }
3701 }
Selim Cinekd2281152015-04-10 14:37:46 -07003702 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003703 }
3704
Selim Cinek19c8c702014-08-25 22:09:19 +02003705 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3706 this.mPhoneStatusBar = phoneStatusBar;
3707 }
3708
Selim Cinekb5605e52015-02-20 18:21:41 +01003709 public void setGroupManager(NotificationGroupManager groupManager) {
3710 this.mGroupManager = groupManager;
3711 }
3712
Selim Cinekd9acca52014-09-01 22:33:25 +02003713 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01003714 requestAnimateEverything();
3715 }
3716
3717 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02003718 if (mIsExpanded && mAnimationsEnabled) {
3719 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01003720 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02003721 requestChildrenUpdate();
3722 }
3723 }
3724
Selim Cinek04fb2582015-06-02 19:58:09 +02003725 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01003726 int childCount = getChildCount();
3727 for (int i = childCount - 1; i >= 0; i--) {
3728 ExpandableView child = (ExpandableView) getChildAt(i);
3729 if (child.getVisibility() != View.GONE) {
3730 float childTop = child.getY();
3731 if (childTop > touchY) {
3732 // we are above a notification entirely let's abort
3733 return false;
3734 }
Selim Cineka686b2c2016-10-26 13:58:27 -07003735 boolean belowChild = touchY > childTop + child.getActualHeight()
3736 - child.getClipBottomAmount();
Selim Cinekabf60bb2015-02-20 17:36:10 +01003737 if (child == mDismissView) {
3738 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3739 touchY - childTop)) {
3740 // We clicked on the dismiss button
3741 return false;
3742 }
3743 } else if (child == mEmptyShadeView) {
3744 // We arrived at the empty shade view, for which we accept all clicks
3745 return true;
3746 } else if (!belowChild){
3747 // We are on a child
3748 return false;
3749 }
3750 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003751 }
Selim Cinek04fb2582015-06-02 19:58:09 +02003752 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003753 }
3754
Selim Cinekb5605e52015-02-20 18:21:41 +01003755 @Override
3756 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003757 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
3758 && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01003759 if (animated) {
3760 mExpandedGroupView = changedRow;
3761 mNeedsAnimation = true;
3762 }
3763 changedRow.setChildrenExpanded(expanded, animated);
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003764 if (!mGroupExpandedForMeasure) {
3765 onHeightChanged(changedRow, false /* needsAnimation */);
3766 }
Mady Mellorb0a82462016-04-30 17:31:02 -07003767 runAfterAnimationFinished(new Runnable() {
3768 @Override
3769 public void run() {
3770 changedRow.onFinishedExpansionChange();
3771 }
3772 });
Selim Cinekb5605e52015-02-20 18:21:41 +01003773 }
3774
3775 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003776 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08003777 mPhoneStatusBar.requestNotificationUpdate();
3778 }
3779
Selim Cinekc22fff62016-05-20 12:44:30 -07003780 /** @hide */
3781 @Override
3782 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
3783 super.onInitializeAccessibilityEventInternal(event);
3784 event.setScrollable(mScrollable);
3785 event.setScrollX(mScrollX);
3786 event.setScrollY(mOwnScrollY);
3787 event.setMaxScrollX(mScrollX);
3788 event.setMaxScrollY(getScrollRange());
3789 }
3790
3791 @Override
3792 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
3793 super.onInitializeAccessibilityNodeInfoInternal(info);
Selim Cinekef406062016-09-29 17:33:13 -07003794 if (mScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07003795 info.setScrollable(true);
Selim Cinekef406062016-09-29 17:33:13 -07003796 if (mBackwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07003797 info.addAction(
3798 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
3799 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
3800 }
Selim Cinekef406062016-09-29 17:33:13 -07003801 if (mForwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07003802 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
3803 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
3804 }
3805 }
Selim Cinek41fe89a2016-06-02 15:27:56 -07003806 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
3807 info.setClassName(ScrollView.class.getName());
Selim Cinekc22fff62016-05-20 12:44:30 -07003808 }
3809
3810 /** @hide */
3811 @Override
3812 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
3813 if (super.performAccessibilityActionInternal(action, arguments)) {
3814 return true;
3815 }
3816 if (!isEnabled()) {
3817 return false;
3818 }
3819 int direction = -1;
3820 switch (action) {
3821 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
3822 // fall through
3823 case android.R.id.accessibilityActionScrollDown:
3824 direction = 1;
3825 // fall through
3826 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
3827 // fall through
3828 case android.R.id.accessibilityActionScrollUp:
3829 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
Selim Cinekad7fac02016-10-18 17:09:15 -07003830 - mShelf.getIntrinsicHeight();
Selim Cinekc22fff62016-05-20 12:44:30 -07003831 final int targetScrollY = Math.max(0,
3832 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
3833 if (targetScrollY != mOwnScrollY) {
3834 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
3835 postInvalidateOnAnimation();
3836 return true;
3837 }
3838 break;
3839 }
3840 return false;
3841 }
3842
Selim Cinekef5127e2015-12-21 16:55:58 -08003843 @Override
Selim Cinek2a739342016-03-17 10:28:55 -07003844 public void onGroupsChanged() {
Selim Cinekef5127e2015-12-21 16:55:58 -08003845 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01003846 }
3847
3848 public void generateChildOrderChangedEvent() {
3849 if (mIsExpanded && mAnimationsEnabled) {
3850 mGenerateChildOrderChangedEvent = true;
3851 mNeedsAnimation = true;
3852 requestChildrenUpdate();
3853 }
3854 }
3855
Selim Cinek684a4422015-04-15 16:18:39 -07003856 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003857 mAnimationFinishedRunnables.add(runnable);
3858 }
3859
3860 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3861 mHeadsUpManager = headsUpManager;
3862 mAmbientState.setHeadsUpManager(headsUpManager);
3863 }
3864
3865 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3866 if (mAnimationsEnabled) {
3867 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3868 mNeedsAnimation = true;
Selim Cinek73cf02a2016-06-17 13:08:00 -07003869 if (!mIsExpanded && !isHeadsUp) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07003870 row.setHeadsUpAnimatingAway(true);
Selim Cinek73cf02a2016-06-17 13:08:00 -07003871 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003872 requestChildrenUpdate();
3873 }
3874 }
3875
3876 public void setShadeExpanded(boolean shadeExpanded) {
3877 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07003878 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003879 }
3880
Selim Cineka59ecc32015-04-07 10:51:49 -07003881 /**
3882 * Set the boundary for the bottom heads up position. The heads up will always be above this
3883 * position.
3884 *
3885 * @param height the height of the screen
3886 * @param bottomBarHeight the height of the bar on the bottom
3887 */
3888 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3889 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3890 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003891 requestChildrenUpdate();
3892 }
3893
3894 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3895 mTrackingHeadsUp = trackingHeadsUp;
3896 }
3897
Selim Cinekaac93252015-04-14 20:04:12 -07003898 public void setScrimController(ScrimController scrimController) {
3899 mScrimController = scrimController;
Selim Cinekd35c2792016-01-21 13:20:57 -08003900 mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3901 @Override
3902 public void run() {
3903 updateBackgroundDimming();
3904 }
3905 });
Selim Cinekaac93252015-04-14 20:04:12 -07003906 }
3907
Selim Cinekbbc580b2015-06-03 14:11:03 +02003908 public void forceNoOverlappingRendering(boolean force) {
3909 mForceNoOverlappingRendering = force;
3910 }
3911
3912 @Override
3913 public boolean hasOverlappingRendering() {
3914 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3915 }
3916
Selim Cinek6811d722016-01-19 17:53:12 -08003917 public void setAnimationRunning(boolean animationRunning) {
3918 if (animationRunning != mAnimationRunning) {
3919 if (animationRunning) {
Selim Cinekc383fd02016-10-21 15:31:26 -07003920 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08003921 } else {
Selim Cinekc383fd02016-10-21 15:31:26 -07003922 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08003923 }
3924 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08003925 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08003926 }
3927 }
3928
Selim Cinek3776fe02016-02-04 13:32:43 -08003929 public boolean isExpanded() {
3930 return mIsExpanded;
3931 }
3932
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003933 public void setPulsing(boolean pulsing) {
3934 mPulsing = pulsing;
3935 updateNotificationAnimationStates();
Adrian Roos7a9551a2017-01-11 12:27:49 -08003936 updateContentHeight();
3937 notifyHeightChangeListener(mShelf);
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003938 }
3939
Selim Cinek07304f5222016-05-19 18:31:36 -07003940 public void setFadingOut(boolean fadingOut) {
3941 if (fadingOut != mFadingOut) {
3942 mFadingOut = fadingOut;
3943 updateFadingState();
Selim Cinek31d37b92016-04-26 09:56:42 -07003944 }
3945 }
3946
Selim Cinek48ff9b42016-11-09 19:31:51 -08003947 public void setParentNotFullyVisible(boolean parentNotFullyVisible) {
3948 if (mScrimController == null) {
3949 // we're not set up yet.
3950 return;
3951 }
3952 if (parentNotFullyVisible != mParentNotFullyVisible) {
3953 mParentNotFullyVisible = parentNotFullyVisible;
Selim Cinek07304f5222016-05-19 18:31:36 -07003954 updateFadingState();
3955 }
3956 }
3957
3958 private void updateFadingState() {
Selim Cinek9bfba9c2016-08-08 15:20:06 -07003959 applyCurrentBackgroundBounds();
Selim Cinek07304f5222016-05-19 18:31:36 -07003960 updateSrcDrawing();
3961 }
3962
Selim Cinek31d37b92016-04-26 09:56:42 -07003963 @Override
3964 public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) {
3965 super.setAlpha(alpha);
Selim Cinek07304f5222016-05-19 18:31:36 -07003966 setFadingOut(alpha != 1.0f);
Selim Cinek31d37b92016-04-26 09:56:42 -07003967 }
3968
Selim Cinek3afd00e2014-08-11 22:32:57 +02003969 /**
Selim Cinek7103fd42016-05-09 22:22:33 -04003970 * Remove the a given view from the viewstate. This is currently used when the children are
3971 * kept in the parent artificially to have a nicer animation.
3972 * @param view the view to remove
3973 */
3974 public void removeViewStateForView(View view) {
3975 mCurrentStackScrollState.removeViewStateForView(view);
3976 }
3977
Selim Cinekbc243a92016-09-27 16:35:13 -07003978 public void setQsExpanded(boolean qsExpanded) {
3979 mQsExpanded = qsExpanded;
3980 updateAlgorithmLayoutMinHeight();
3981 }
3982
Selim Cinekef406062016-09-29 17:33:13 -07003983 public void setOwnScrollY(int ownScrollY) {
3984 if (ownScrollY != mOwnScrollY) {
3985 mOwnScrollY = ownScrollY;
3986 updateForwardAndBackwardScrollability();
3987 }
3988 }
3989
Selim Cinek281c2022016-10-13 19:14:43 -07003990 public void setShelf(NotificationShelf shelf) {
Selim Cinek281c2022016-10-13 19:14:43 -07003991 int index = -1;
3992 if (mShelf != null) {
3993 index = indexOfChild(mShelf);
3994 removeView(mShelf);
3995 }
Selim Cinek0e8d77e2016-11-29 10:35:42 -08003996 mShelf = shelf;
Selim Cinek281c2022016-10-13 19:14:43 -07003997 addView(mShelf, index);
3998 mAmbientState.setShelf(shelf);
Selim Cinekeccb5de2016-10-28 15:04:05 -07003999 mStateAnimator.setShelf(shelf);
Selim Cinekc383fd02016-10-21 15:31:26 -07004000 shelf.bind(mAmbientState, this);
Selim Cinek281c2022016-10-13 19:14:43 -07004001 }
4002
4003 public NotificationShelf getNotificationShelf() {
4004 return mShelf;
4005 }
4006
Selim Cinekad7fac02016-10-18 17:09:15 -07004007 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
4008 if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
4009 mMaxDisplayedNotifications = maxDisplayedNotifications;
4010 updateContentHeight();
4011 notifyHeightChangeListener(mShelf);
4012 }
4013 }
4014
Selim Cinek48ff9b42016-11-09 19:31:51 -08004015 public int getMinExpansionHeight() {
Selim Cinekd127d792016-11-01 19:11:41 -07004016 return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
Selim Cinek48ff9b42016-11-09 19:31:51 -08004017 }
4018
Selim Cinekcafa87f2016-10-26 17:00:17 -07004019 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
4020 mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
4021 updateClipping();
4022 }
4023
4024 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
4025 mHeadsUpAnimatingAway = headsUpAnimatingAway;
4026 updateClipping();
4027 }
4028
Selim Cinek355652a2016-12-07 13:32:12 -08004029 public void setStatusBarState(int statusBarState) {
4030 mStatusBarState = statusBarState;
4031 mAmbientState.setStatusBarState(statusBarState);
4032 }
4033
Selim Cinekd5ab6452016-12-08 16:34:00 -08004034 public void setExpandingVelocity(float expandingVelocity) {
4035 mAmbientState.setExpandingVelocity(expandingVelocity);
4036 }
4037
Selim Cinekfcff4c62016-12-27 14:26:06 +01004038 public float getOpeningHeight() {
4039 if (mEmptyShadeView.getVisibility() == GONE) {
4040 return getMinExpansionHeight();
4041 } else {
4042 return getAppearEndPosition();
4043 }
4044 }
4045
4046 public void setIsFullWidth(boolean isFullWidth) {
4047 mAmbientState.setPanelFullWidth(isFullWidth);
4048 }
4049
Selim Cinek7103fd42016-05-09 22:22:33 -04004050 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02004051 * A listener that is notified when some child locations might have changed.
4052 */
4053 public interface OnChildLocationsChangedListener {
4054 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
4055 }
Selim Cinek572bbd42014-04-25 16:43:27 +02004056
Jorim Jaggi290600a2014-05-30 17:02:20 +02004057 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01004058 * A listener that is notified when the empty space below the notifications is clicked on
4059 */
4060 public interface OnEmptySpaceClickListener {
4061 public void onEmptySpaceClicked(float x, float y);
4062 }
4063
4064 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02004065 * A listener that gets notified when the overscroll at the top has changed.
4066 */
4067 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02004068
4069 /**
4070 * Notifies a listener that the overscroll has changed.
4071 *
4072 * @param amount the amount of overscroll, in pixels
4073 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
4074 * unrubberbanded motion to directly expand overscroll view (e.g expand
4075 * QS)
4076 */
4077 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02004078
4079 /**
4080 * Notify a listener that the scroller wants to escape from the scrolling motion and
4081 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
4082 *
4083 * @param velocity The velocity that the Scroller had when over flinging
4084 * @param open Should the fling open or close the overscroll view.
4085 */
4086 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02004087 }
4088
Mady Mellor4b80b102016-01-22 08:03:58 -08004089 private class NotificationSwipeHelper extends SwipeHelper {
Mady Mellor97c8df42016-03-22 18:09:39 -07004090 private static final long SHOW_GEAR_DELAY = 60;
4091 private static final long COVER_GEAR_DELAY = 4000;
Mady Mellor5b3cc112016-09-12 15:53:35 -07004092 private static final long SWIPE_GEAR_TIMING = 200;
Mady Mellor4b80b102016-01-22 08:03:58 -08004093 private CheckForDrag mCheckForDrag;
Mady Mellor97c8df42016-03-22 18:09:39 -07004094 private Runnable mFalsingCheck;
Mady Mellor4b80b102016-01-22 08:03:58 -08004095 private Handler mHandler;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004096 private boolean mGearSnappedTo;
4097 private boolean mGearSnappedOnLeft;
Mady Mellor4b80b102016-01-22 08:03:58 -08004098
4099 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
4100 super(swipeDirection, callback, context);
4101 mHandler = new Handler();
Mady Mellor97c8df42016-03-22 18:09:39 -07004102 mFalsingCheck = new Runnable() {
4103 @Override
4104 public void run() {
4105 resetExposedGearView(true /* animate */, true /* force */);
4106 }
4107 };
Mady Mellor4b80b102016-01-22 08:03:58 -08004108 }
4109
4110 @Override
4111 public void onDownUpdate(View currView) {
4112 // Set the active view
4113 mTranslatingParentView = currView;
4114
4115 // Reset check for drag gesture
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004116 cancelCheckForDrag();
4117 if (mCurrIconRow != null) {
4118 mCurrIconRow.setSnapping(false);
4119 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004120 mCheckForDrag = null;
Mady Mellor34958fa2016-02-23 09:52:17 -08004121 mCurrIconRow = null;
Mady Mellor97c8df42016-03-22 18:09:39 -07004122 mHandler.removeCallbacks(mFalsingCheck);
Mady Mellor4b80b102016-01-22 08:03:58 -08004123
4124 // Slide back any notifications that might be showing a gear
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004125 resetExposedGearView(true /* animate */, false /* force */);
Mady Mellor4b80b102016-01-22 08:03:58 -08004126
4127 if (currView instanceof ExpandableNotificationRow) {
4128 // Set the listener for the current row's gear
4129 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
Mady Mellor761cde12017-01-10 11:36:39 -08004130 mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this);
Mady Mellor4b80b102016-01-22 08:03:58 -08004131 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004132 }
4133
4134 @Override
4135 public void onMoveUpdate(View view, float translation, float delta) {
Mady Mellor97c8df42016-03-22 18:09:39 -07004136 mHandler.removeCallbacks(mFalsingCheck);
4137
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004138 if (mCurrIconRow != null) {
4139 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
4140
4141 // If the gear is visible and the movement is towards it it's not a location change.
Mady Mellor761cde12017-01-10 11:36:39 -08004142 boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft();
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004143 boolean locationChange = isTowardsGear(translation, onLeft)
Mady Mellor761cde12017-01-10 11:36:39 -08004144 ? false : mCurrIconRow.isMenuLocationChange(translation);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004145 if (locationChange) {
4146 // Don't consider it "snapped" if location has changed.
4147 setSnappedToGear(false);
4148
4149 // Changed directions, make sure we check to fade in icon again.
4150 if (!mHandler.hasCallbacks(mCheckForDrag)) {
4151 // No check scheduled, set null to schedule a new one.
4152 mCheckForDrag = null;
4153 } else {
4154 // Check scheduled, reset alpha and update location; check will fade it in
Mady Mellor761cde12017-01-10 11:36:39 -08004155 mCurrIconRow.setMenuAlpha(0f);
4156 mCurrIconRow.setMenuLocation((int) translation);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004157 }
4158 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004159 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004160
Mady Mellor34958fa2016-02-23 09:52:17 -08004161 final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
4162 && ((ExpandableNotificationRow) view).areGutsExposed();
4163
4164 if (!isPinnedHeadsUp(view) && !gutsExposed) {
4165 // Only show the gear if we're not a heads up view and guts aren't exposed.
4166 checkForDrag();
Mady Mellor4b80b102016-01-22 08:03:58 -08004167 }
4168 }
4169
4170 @Override
Mady Mellordc6c97d2016-03-31 14:18:35 -07004171 public void dismissChild(final View view, float velocity,
4172 boolean useAccelerateInterpolator) {
4173 super.dismissChild(view, velocity, useAccelerateInterpolator);
Selim Cinekd1395642016-04-28 12:22:42 -07004174 if (mIsExpanded) {
4175 // We don't want to quick-dismiss when it's a heads up as this might lead to closing
4176 // of the panel early.
4177 handleChildDismissed(view);
4178 }
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004179 handleGearCoveredOrDismissed();
Mady Mellor4b80b102016-01-22 08:03:58 -08004180 }
4181
4182 @Override
4183 public void snapChild(final View animView, final float targetLeft, float velocity) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004184 super.snapChild(animView, targetLeft, velocity);
4185 onDragCancelled(animView);
4186 if (targetLeft == 0) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004187 handleGearCoveredOrDismissed();
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004188 }
4189 }
4190
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004191 private void handleGearCoveredOrDismissed() {
4192 cancelCheckForDrag();
4193 setSnappedToGear(false);
4194 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
4195 mGearExposedView = null;
4196 }
4197 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004198
4199 @Override
4200 public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
4201 float translation) {
4202 if (mCurrIconRow == null) {
4203 cancelCheckForDrag();
4204 return false; // Let SwipeHelper handle it.
4205 }
4206
Mady Mellor761cde12017-01-10 11:36:39 -08004207 boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft());
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004208 boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
Mady Mellor5b3cc112016-09-12 15:53:35 -07004209 final double timeForGesture = ev.getEventTime() - ev.getDownTime();
4210 final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
4211 && timeForGesture >= SWIPE_GEAR_TIMING;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004212
4213 if (mGearSnappedTo && mCurrIconRow.isVisible()) {
Mady Mellor761cde12017-01-10 11:36:39 -08004214 if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004215 boolean coveringGear =
4216 Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
4217 if (gestureTowardsGear || coveringGear) {
4218 // Gesture is towards or covering the gear
4219 snapChild(animView, 0 /* leftTarget */, velocity);
4220 } else if (isDismissGesture(ev)) {
4221 // Gesture is a dismiss that's not towards the gear
Mady Mellordc6c97d2016-03-31 14:18:35 -07004222 dismissChild(animView, velocity,
4223 !swipedFastEnough() /* useAccelerateInterpolator */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004224 } else {
4225 // Didn't move enough to dismiss or cover, snap to the gear
4226 snapToGear(animView, velocity);
4227 }
4228 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
4229 || (gestureTowardsGear && !swipedFarEnough())) {
4230 // The gear has been snapped to previously, however, the gear is now on the
4231 // other side. If gesture is towards gear and not too far snap to the gear.
4232 snapToGear(animView, velocity);
4233 } else {
4234 dismissOrSnapBack(animView, velocity, ev);
4235 }
Mady Mellor5b3cc112016-09-12 15:53:35 -07004236 } else if (((!gestureFastEnough || showGearForSlowOnGoing)
4237 && swipedEnoughToShowGear(animView))
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004238 || gestureTowardsGear) {
4239 // Gear has not been snapped to previously and this is gear revealing gesture
4240 snapToGear(animView, velocity);
4241 } else {
4242 dismissOrSnapBack(animView, velocity, ev);
4243 }
4244 return true;
4245 }
4246
4247 private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
4248 if (isDismissGesture(ev)) {
Mady Mellordc6c97d2016-03-31 14:18:35 -07004249 dismissChild(animView, velocity,
4250 !swipedFastEnough() /* useAccelerateInterpolator */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004251 } else {
4252 snapChild(animView, 0 /* leftTarget */, velocity);
4253 }
4254 }
4255
4256 private void snapToGear(View animView, float velocity) {
4257 final float snapBackThreshold = getSpaceForGear(animView);
Mady Mellor761cde12017-01-10 11:36:39 -08004258 final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004259 : -snapBackThreshold;
4260 mGearExposedView = mTranslatingParentView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004261 if (animView instanceof ExpandableNotificationRow) {
4262 MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
4263 ((ExpandableNotificationRow) animView).getStatusBarNotification()
4264 .getPackageName());
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004265 }
4266 if (mCurrIconRow != null) {
4267 mCurrIconRow.setSnapping(true);
4268 setSnappedToGear(true);
4269 }
4270 onDragCancelled(animView);
Mady Mellor97c8df42016-03-22 18:09:39 -07004271
4272 // If we're on the lockscreen we want to false this.
Selim Cinekbc243a92016-09-27 16:35:13 -07004273 if (isAntiFalsingNeeded()) {
Mady Mellor97c8df42016-03-22 18:09:39 -07004274 mHandler.removeCallbacks(mFalsingCheck);
4275 mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
4276 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004277 super.snapChild(animView, target, velocity);
4278 }
4279
4280 private boolean swipedEnoughToShowGear(View animView) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004281 if (mTranslatingParentView == null) {
4282 return false;
4283 }
Mady Mellorfcba9592016-03-23 11:55:53 -07004284 // If the notification can't be dismissed then how far it can move is
4285 // restricted -- reduce the distance it needs to move in this case.
4286 final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
4287 final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
Mady Mellor4b80b102016-01-22 08:03:58 -08004288 final float translation = getTranslation(animView);
Mady Mellor761cde12017-01-10 11:36:39 -08004289 return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft()
Mady Mellor5b3cc112016-09-12 15:53:35 -07004290 ? translation > snapBackThreshold
4291 : translation < -snapBackThreshold);
Mady Mellor4b80b102016-01-22 08:03:58 -08004292 }
4293
4294 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -08004295 public Animator getViewTranslationAnimator(View v, float target,
4296 AnimatorUpdateListener listener) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07004297 if (v instanceof ExpandableNotificationRow) {
Mady Mellor34958fa2016-02-23 09:52:17 -08004298 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
4299 } else {
4300 return super.getViewTranslationAnimator(v, target, listener);
Mady Mellor4b80b102016-01-22 08:03:58 -08004301 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004302 }
4303
4304 @Override
4305 public void setTranslation(View v, float translate) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07004306 ((ExpandableView) v).setTranslation(translate);
Mady Mellor4b80b102016-01-22 08:03:58 -08004307 }
4308
4309 @Override
4310 public float getTranslation(View v) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07004311 return ((ExpandableView) v).getTranslation();
Mady Mellor4b80b102016-01-22 08:03:58 -08004312 }
4313
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004314 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4315 NotificationGuts guts = mPhoneStatusBar.getExposedGuts();
4316 View view = null;
4317 int height = 0;
4318 if (guts != null) {
4319 // Checking guts
4320 view = guts;
4321 height = guts.getActualHeight();
4322 } else if (mCurrIconRow != null && mCurrIconRow.isVisible()
4323 && mTranslatingParentView != null) {
4324 // Checking gear
4325 view = mTranslatingParentView;
4326 height = ((ExpandableView) mTranslatingParentView).getActualHeight();
4327 }
4328 if (view != null) {
4329 final int rx = (int) ev.getRawX();
4330 final int ry = (int) ev.getRawY();
4331
Selim Cinekfc7086e2016-09-22 14:03:41 -07004332 view.getLocationOnScreen(mTempInt2);
4333 final int x = mTempInt2[0];
4334 final int y = mTempInt2[1];
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004335 Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
Selim Cinekfc7086e2016-09-22 14:03:41 -07004336 if (!rect.contains(rx, ry)) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004337 // Touch was outside visible guts / gear notification, close what's visible
4338 mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
4339 }
4340 }
4341 }
4342
Mady Mellor4b80b102016-01-22 08:03:58 -08004343 /**
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004344 * Returns whether the gesture is towards the gear location or not.
4345 */
4346 private boolean isTowardsGear(float velocity, boolean onLeft) {
4347 if (mCurrIconRow == null) {
4348 return false;
4349 }
4350 return mCurrIconRow.isVisible()
4351 && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
4352 }
4353
4354 /**
4355 * Indicates the the gear has been snapped to.
4356 */
4357 private void setSnappedToGear(boolean snapped) {
Mady Mellor761cde12017-01-10 11:36:39 -08004358 mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004359 mGearSnappedTo = snapped && mCurrIconRow != null;
4360 }
4361
4362 /**
Mady Mellor4b80b102016-01-22 08:03:58 -08004363 * Returns the horizontal space in pixels required to display the gear behind a
4364 * notification.
4365 */
4366 private float getSpaceForGear(View view) {
4367 if (view instanceof ExpandableNotificationRow) {
4368 return ((ExpandableNotificationRow) view).getSpaceForGear();
4369 }
4370 return 0;
4371 }
4372
4373 private void checkForDrag() {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004374 if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
Mady Mellor4b80b102016-01-22 08:03:58 -08004375 mCheckForDrag = new CheckForDrag();
Mady Mellor97c8df42016-03-22 18:09:39 -07004376 mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
Mady Mellor4b80b102016-01-22 08:03:58 -08004377 }
4378 }
4379
4380 private void cancelCheckForDrag() {
4381 if (mCurrIconRow != null) {
4382 mCurrIconRow.cancelFadeAnimator();
4383 }
4384 mHandler.removeCallbacks(mCheckForDrag);
Mady Mellor4b80b102016-01-22 08:03:58 -08004385 }
4386
4387 private final class CheckForDrag implements Runnable {
4388 @Override
4389 public void run() {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004390 if (mTranslatingParentView == null) {
4391 return;
4392 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004393 final float translation = getTranslation(mTranslatingParentView);
4394 final float absTransX = Math.abs(translation);
4395 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
4396 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004397 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
Mady Mellor761cde12017-01-10 11:36:39 -08004398 || mCurrIconRow.isMenuLocationChange(translation)))
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004399 && absTransX >= bounceBackToGearWidth * 0.4
Mady Mellor4b80b102016-01-22 08:03:58 -08004400 && absTransX < notiThreshold) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004401 // Fade in the gear
Mady Mellor761cde12017-01-10 11:36:39 -08004402 mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation,
Mady Mellor4b80b102016-01-22 08:03:58 -08004403 notiThreshold);
Mady Mellor4b80b102016-01-22 08:03:58 -08004404 }
4405 }
4406 }
4407
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004408 public void resetExposedGearView(boolean animate, boolean force) {
4409 if (mGearExposedView == null
4410 || (!force && mGearExposedView == mTranslatingParentView)) {
Mady Mellor4b80b102016-01-22 08:03:58 -08004411 // If no gear is showing or it's showing for this view we do nothing.
4412 return;
4413 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004414 final View prevGearExposedView = mGearExposedView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004415 if (animate) {
4416 Animator anim = getViewTranslationAnimator(prevGearExposedView,
4417 0 /* leftTarget */, null /* updateListener */);
4418 if (anim != null) {
4419 anim.start();
4420 }
4421 } else if (mGearExposedView instanceof ExpandableNotificationRow) {
4422 ((ExpandableNotificationRow) mGearExposedView).resetTranslation();
4423 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004424 mGearExposedView = null;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004425 mGearSnappedTo = false;
Mady Mellor4b80b102016-01-22 08:03:58 -08004426 }
4427 }
4428
Selim Cinek33223572016-02-19 19:32:22 -08004429 private void updateContinuousShadowDrawing() {
4430 boolean continuousShadowUpdate = mAnimationRunning
4431 || !mAmbientState.getDraggedViews().isEmpty();
4432 if (continuousShadowUpdate != mContinuousShadowUpdate) {
4433 if (continuousShadowUpdate) {
4434 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
4435 } else {
4436 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
4437 }
Jorim Jaggi38b5ec92016-04-12 01:39:49 -07004438 mContinuousShadowUpdate = continuousShadowUpdate;
Selim Cinek33223572016-02-19 19:32:22 -08004439 }
4440 }
4441
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004442 public void resetExposedGearView(boolean animate, boolean force) {
4443 mSwipeHelper.resetExposedGearView(animate, force);
4444 }
4445
4446 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4447 mSwipeHelper.closeControlsIfOutsideTouch(ev);
4448 }
4449
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004450 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02004451
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004452 static AnimationFilter[] FILTERS = new AnimationFilter[] {
4453
4454 // ANIMATION_TYPE_ADD
4455 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004456 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004457 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004458 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004459 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004460 .animateZ()
4461 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004462
4463 // ANIMATION_TYPE_REMOVE
4464 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004465 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004466 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004467 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004468 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004469 .animateZ()
4470 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004471
4472 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4473 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004474 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004475 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004476 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004477 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004478 .animateZ()
4479 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004480
4481 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4482 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004483 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004484 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004485 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004486 .animateY()
4487 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004488 .animateZ(),
4489
4490 // ANIMATION_TYPE_START_DRAG
4491 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004492 .animateShadowAlpha(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004493
4494 // ANIMATION_TYPE_SNAP_BACK
4495 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004496 .animateShadowAlpha()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004497 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004498
4499 // ANIMATION_TYPE_ACTIVATED_CHILD
4500 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004501 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004502
4503 // ANIMATION_TYPE_DIMMED
4504 new AnimationFilter()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004505 .animateDimmed(),
4506
4507 // ANIMATION_TYPE_CHANGE_POSITION
4508 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004509 .animateAlpha() // maybe the children change positions
4510 .animateShadowAlpha()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004511 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004512 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004513 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04004514 .animateZ(),
4515
4516 // ANIMATION_TYPE_DARK
4517 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01004518 .animateDark()
4519 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004520
4521 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4522 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004523 .animateShadowAlpha()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004524 .animateHeight()
4525 .animateTopInset()
4526 .animateY()
4527 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02004528 .animateZ()
4529 .hasDelays(),
4530
4531 // ANIMATION_TYPE_HIDE_SENSITIVE
4532 new AnimationFilter()
4533 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02004534
4535 // ANIMATION_TYPE_VIEW_RESIZE
4536 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004537 .animateShadowAlpha()
Selim Cineka5e211b2014-08-11 17:35:48 +02004538 .animateHeight()
4539 .animateTopInset()
4540 .animateY()
4541 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02004542
Selim Cinekb5605e52015-02-20 18:21:41 +01004543 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
4544 new AnimationFilter()
4545 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004546 .animateShadowAlpha()
Selim Cinekb5605e52015-02-20 18:21:41 +01004547 .animateHeight()
4548 .animateTopInset()
4549 .animateY()
4550 .animateZ(),
4551
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004552 // ANIMATION_TYPE_HEADS_UP_APPEAR
4553 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004554 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004555 .animateHeight()
4556 .animateTopInset()
4557 .animateY()
4558 .animateZ(),
4559
4560 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4561 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004562 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004563 .animateHeight()
4564 .animateTopInset()
4565 .animateY()
4566 .animateZ(),
4567
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004568 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4569 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004570 .animateShadowAlpha()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004571 .animateHeight()
4572 .animateTopInset()
4573 .animateY()
4574 .animateZ()
4575 .hasDelays(),
4576
Selim Cineka59ecc32015-04-07 10:51:49 -07004577 // ANIMATION_TYPE_HEADS_UP_OTHER
4578 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004579 .animateShadowAlpha()
Selim Cineka59ecc32015-04-07 10:51:49 -07004580 .animateHeight()
4581 .animateTopInset()
4582 .animateY()
4583 .animateZ(),
4584
Selim Cinekd9acca52014-09-01 22:33:25 +02004585 // ANIMATION_TYPE_EVERYTHING
4586 new AnimationFilter()
4587 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004588 .animateShadowAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02004589 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02004590 .animateDimmed()
4591 .animateHideSensitive()
4592 .animateHeight()
4593 .animateTopInset()
4594 .animateY()
4595 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004596 };
4597
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004598 static int[] LENGTHS = new int[] {
4599
4600 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004601 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004602
4603 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004604 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004605
4606 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4607 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4608
4609 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4610 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4611
4612 // ANIMATION_TYPE_START_DRAG
4613 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4614
4615 // ANIMATION_TYPE_SNAP_BACK
4616 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4617
4618 // ANIMATION_TYPE_ACTIVATED_CHILD
4619 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
4620
4621 // ANIMATION_TYPE_DIMMED
4622 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004623
4624 // ANIMATION_TYPE_CHANGE_POSITION
4625 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04004626
4627 // ANIMATION_TYPE_DARK
4628 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004629
4630 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4631 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02004632
4633 // ANIMATION_TYPE_HIDE_SENSITIVE
4634 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02004635
4636 // ANIMATION_TYPE_VIEW_RESIZE
4637 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02004638
Selim Cinekb5605e52015-02-20 18:21:41 +01004639 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08004640 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01004641
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004642 // ANIMATION_TYPE_HEADS_UP_APPEAR
4643 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
4644
4645 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4646 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4647
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004648 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4649 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4650
Selim Cineka59ecc32015-04-07 10:51:49 -07004651 // ANIMATION_TYPE_HEADS_UP_OTHER
4652 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4653
Selim Cinekd9acca52014-09-01 22:33:25 +02004654 // ANIMATION_TYPE_EVERYTHING
4655 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004656 };
4657
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004658 static final int ANIMATION_TYPE_ADD = 0;
4659 static final int ANIMATION_TYPE_REMOVE = 1;
4660 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
4661 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
4662 static final int ANIMATION_TYPE_START_DRAG = 4;
4663 static final int ANIMATION_TYPE_SNAP_BACK = 5;
4664 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
4665 static final int ANIMATION_TYPE_DIMMED = 7;
4666 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04004667 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004668 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02004669 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02004670 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01004671 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004672 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
4673 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004674 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
4675 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
4676 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004677
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004678 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
4679 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
4680
Selim Cinek572bbd42014-04-25 16:43:27 +02004681 final long eventStartTime;
4682 final View changingView;
4683 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004684 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004685 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004686 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004687 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07004688 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02004689
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004690 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02004691 this(view, type, LENGTHS[type]);
4692 }
4693
4694 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02004695 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
4696 changingView = view;
4697 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004698 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02004699 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004700 }
4701
4702 /**
4703 * Combines the length of several animation events into a single value.
4704 *
4705 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004706 * @return The combined length. Depending on the event types, this might be the maximum of
4707 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004708 */
4709 static long combineLength(ArrayList<AnimationEvent> events) {
4710 long length = 0;
4711 int size = events.size();
4712 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004713 AnimationEvent event = events.get(i);
4714 length = Math.max(length, event.length);
4715 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
4716 return event.length;
4717 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004718 }
4719 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02004720 }
4721 }
Selim Cinek67b22602014-03-10 15:40:16 +01004722}