blob: 688e8eb8f2e3e209af0d68ba7d15f61cb9c6ae2b [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
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.stack;
Selim Cinek67b22602014-03-10 15:40:16 +010018
Gus Prevas59ec2ff2018-12-28 16:20:28 -050019import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
Gus Prevase83700cb2018-12-14 11:42:51 -050020import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
Gus Prevas0fa58d62019-01-11 13:58:40 -050021import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
Gus Prevase83700cb2018-12-14 11:42:51 -050022import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
Selim Cinekda330ff2019-06-11 15:41:28 -070023import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
Gus Prevas59ec2ff2018-12-28 16:20:28 -050024import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
Selim Cinek2627d722018-01-19 12:16:49 -080025
Ned Burns61269442019-05-02 18:27:23 -040026import static java.lang.annotation.RetentionPolicy.SOURCE;
27
Selim Cinek614576e2016-01-20 10:54:09 -080028import android.animation.Animator;
29import android.animation.AnimatorListenerAdapter;
Selim Cinekd35c2792016-01-21 13:20:57 -080030import android.animation.TimeAnimator;
31import android.animation.ValueAnimator;
Ned Burns61269442019-05-02 18:27:23 -040032import android.annotation.IntDef;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040033import android.annotation.NonNull;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010034import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010035import android.content.Context;
Jason Monke59dc402018-08-16 12:05:01 -040036import android.content.Intent;
Selim Cinek67b22602014-03-10 15:40:16 +010037import android.content.res.Configuration;
Anthony Chen3cb3ad92016-12-01 10:58:47 -080038import android.content.res.Resources;
Selim Cinek67b22602014-03-10 15:40:16 +010039import android.graphics.Canvas;
Lucas Dupind285cf02018-01-18 09:18:23 -080040import android.graphics.Color;
Lucas Dupin64e2f572019-03-21 14:21:14 -070041import android.graphics.Outline;
Selim Cinek67b22602014-03-10 15:40:16 +010042import android.graphics.Paint;
Evan Lairde55c6012019-03-13 12:54:37 -040043import android.graphics.Point;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010044import android.graphics.PointF;
Selim Cinek6811d722016-01-19 17:53:12 -080045import android.graphics.PorterDuff;
46import android.graphics.PorterDuffXfermode;
47import android.graphics.Rect;
Selim Cinekc22fff62016-05-20 12:44:30 -070048import android.os.Bundle;
Jason Monke59dc402018-08-16 12:05:01 -040049import android.os.ServiceManager;
50import android.provider.Settings;
Mady Mellorc2ff0112019-03-28 14:18:06 -070051import android.service.notification.NotificationListenerService;
Mady Mellor95d743c2017-01-10 12:05:27 -080052import android.service.notification.StatusBarNotification;
Selim Cinek67b22602014-03-10 15:40:16 +010053import android.util.AttributeSet;
Jason Monke59dc402018-08-16 12:05:01 -040054import android.util.DisplayMetrics;
Selim Cinek67b22602014-03-10 15:40:16 +010055import android.util.Log;
Lucas Dupind285cf02018-01-18 09:18:23 -080056import android.util.MathUtils;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070057import android.util.Pair;
Lucas Dupine17ce522017-07-17 15:45:06 -070058import android.view.ContextThemeWrapper;
Selim Cinek11e33232016-08-05 15:30:53 -070059import android.view.InputDevice;
Jason Monke59dc402018-08-16 12:05:01 -040060import android.view.LayoutInflater;
Selim Cinek67b22602014-03-10 15:40:16 +010061import android.view.MotionEvent;
62import android.view.VelocityTracker;
63import android.view.View;
64import android.view.ViewConfiguration;
65import android.view.ViewGroup;
Lucas Dupin64e2f572019-03-21 14:21:14 -070066import android.view.ViewOutlineProvider;
Selim Cinek343e6e22014-04-11 21:23:30 +020067import android.view.ViewTreeObserver;
Adrian Roos5153d4a2016-03-22 10:01:56 -070068import android.view.WindowInsets;
Selim Cinekc22fff62016-05-20 12:44:30 -070069import android.view.accessibility.AccessibilityEvent;
70import android.view.accessibility.AccessibilityNodeInfo;
Selim Cinek572bbd42014-04-25 16:43:27 +020071import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080072import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010073import android.widget.OverScroller;
Selim Cinek41fe89a2016-06-02 15:27:56 -070074import android.widget.ScrollView;
Lucas Dupin8da8f2e92017-04-21 14:02:16 -070075
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040076import com.android.internal.annotations.VisibleForTesting;
77import com.android.internal.graphics.ColorUtils;
Mady Mellora41587b2016-02-11 18:43:06 -080078import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010079import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Jason Monke59dc402018-08-16 12:05:01 -040080import com.android.internal.statusbar.IStatusBarService;
Lucas Dupin4e023812018-04-02 21:19:23 -070081import com.android.keyguard.KeyguardSliceView;
Lucas Dupine17ce522017-07-17 15:45:06 -070082import com.android.settingslib.Utils;
Rohan Shah524cf7b2018-03-15 14:40:02 -070083import com.android.systemui.Dependency;
Jason Monke59dc402018-08-16 12:05:01 -040084import com.android.systemui.Dumpable;
Selim Cinek67b22602014-03-10 15:40:16 +010085import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080086import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010087import com.android.systemui.R;
88import com.android.systemui.SwipeHelper;
Jason Monke59dc402018-08-16 12:05:01 -040089import com.android.systemui.colorextraction.SysuiColorExtractor;
Ned Burns9eb06332019-04-23 16:02:12 -040090import com.android.systemui.plugins.ActivityStarter;
Dave Mankoff468d4f62019-05-08 14:56:29 -040091import com.android.systemui.plugins.FalsingManager;
Mady Mellor95d743c2017-01-10 12:05:27 -080092import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
93import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040094import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
Mady Mellor95d743c2017-01-10 12:05:27 -080095import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
Beverly8fdb5332019-02-04 14:29:49 -050096import com.android.systemui.plugins.statusbar.StatusBarStateController;
97import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
Jason Monke59dc402018-08-16 12:05:01 -040098import com.android.systemui.statusbar.CommandQueue;
99import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200100import com.android.systemui.statusbar.EmptyShadeView;
Jason Monk297c04e2018-08-23 17:16:59 -0400101import com.android.systemui.statusbar.NotificationLockscreenUserManager;
102import com.android.systemui.statusbar.NotificationRemoteInputManager;
103import com.android.systemui.statusbar.NotificationShelf;
104import com.android.systemui.statusbar.RemoteInputController;
105import com.android.systemui.statusbar.StatusBarState;
Beverly8fdb5332019-02-04 14:29:49 -0500106import com.android.systemui.statusbar.SysuiStatusBarStateController;
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700107import com.android.systemui.statusbar.notification.DynamicPrivacyController;
Jason Monk297c04e2018-08-23 17:16:59 -0400108import com.android.systemui.statusbar.notification.FakeShadowView;
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500109import com.android.systemui.statusbar.notification.NotificationEntryListener;
Jason Monk297c04e2018-08-23 17:16:59 -0400110import com.android.systemui.statusbar.notification.NotificationEntryManager;
111import com.android.systemui.statusbar.notification.NotificationUtils;
112import com.android.systemui.statusbar.notification.ShadeViewRefactor;
113import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
Jason Monk297c04e2018-08-23 17:16:59 -0400114import com.android.systemui.statusbar.notification.VisualStabilityManager;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500115import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Jason Monk297c04e2018-08-23 17:16:59 -0400116import com.android.systemui.statusbar.notification.logging.NotificationLogger;
117import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
Rohan Shah20790b82018-07-02 17:21:04 -0700118import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
119import com.android.systemui.statusbar.notification.row.ExpandableView;
120import com.android.systemui.statusbar.notification.row.FooterView;
121import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
Rohan Shah20790b82018-07-02 17:21:04 -0700122import com.android.systemui.statusbar.notification.row.NotificationGuts;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400123import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
Rohan Shah20790b82018-07-02 17:21:04 -0700124import com.android.systemui.statusbar.notification.row.NotificationSnooze;
125import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700126import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900127import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400128import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
Selim Cinekc3fec682019-06-06 18:11:07 -0700129import com.android.systemui.statusbar.phone.KeyguardBypassController;
Jason Monke59dc402018-08-16 12:05:01 -0400130import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
Selim Cinekb5605e52015-02-20 18:21:41 +0100131import com.android.systemui.statusbar.phone.NotificationGroupManager;
Jason Monke59dc402018-08-16 12:05:01 -0400132import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700133import com.android.systemui.statusbar.phone.NotificationIconAreaController;
Jason Monke59dc402018-08-16 12:05:01 -0400134import com.android.systemui.statusbar.phone.NotificationPanelView;
Selim Cinekaac93252015-04-14 20:04:12 -0700135import com.android.systemui.statusbar.phone.ScrimController;
Jason Monk297c04e2018-08-23 17:16:59 -0400136import com.android.systemui.statusbar.phone.ShadeController;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800137import com.android.systemui.statusbar.phone.StatusBar;
Jason Monke59dc402018-08-16 12:05:01 -0400138import com.android.systemui.statusbar.policy.ConfigurationController;
139import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900140import com.android.systemui.statusbar.policy.HeadsUpUtil;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100141import com.android.systemui.statusbar.policy.ScrollAdapter;
Gus Prevas33619af2018-10-26 15:40:27 -0400142import com.android.systemui.tuner.TunerService;
Ned Burnsd4a69f72019-06-19 19:49:19 -0400143import com.android.systemui.util.Assert;
Selim Cinek67b22602014-03-10 15:40:16 +0100144
Selim Cinek707e2072017-06-30 18:32:40 +0200145import java.io.FileDescriptor;
146import java.io.PrintWriter;
Ned Burns61269442019-05-02 18:27:23 -0400147import java.lang.annotation.Retention;
Selim Cinek572bbd42014-04-25 16:43:27 +0200148import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -0800149import java.util.Collections;
150import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200151import java.util.HashSet;
Selim Cinekef8c2252017-02-10 14:52:18 -0800152import java.util.List;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800153import java.util.function.BiConsumer;
Selim Cinek572bbd42014-04-25 16:43:27 +0200154
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500155import javax.inject.Inject;
156import javax.inject.Named;
157
Selim Cinek67b22602014-03-10 15:40:16 +0100158/**
159 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
160 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400161public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700162 NotificationListContainer, ConfigurationListener, Dumpable,
163 DynamicPrivacyController.Listener {
Selim Cinek67b22602014-03-10 15:40:16 +0100164
Selim Cinekd35c2792016-01-21 13:20:57 -0800165 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -0800166 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +0100167 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200168 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
169 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200170 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +0100171 /**
172 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
173 */
Lucas Dupind285cf02018-01-18 09:18:23 -0800174 private static final int INVALID_POINTER = -1;
Gus Prevase2d6f042018-10-17 15:25:30 -0400175 static final int NUM_SECTIONS = 2;
176 /**
177 * The distance in pixels between sections when the sections are directly adjacent (no visible
178 * gap is drawn between them). In this case we don't want to round their corners.
179 */
180 private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
Selim Cinekc3fec682019-06-06 18:11:07 -0700181 private final KeyguardBypassController mKeyguardBypassController;
Selim Cinek5454a0d2019-07-30 17:14:50 -0700182 private final DynamicPrivacyController mDynamicPrivacyController;
183 private final SysuiStatusBarStateController mStatusbarStateController;
Selim Cinek67b22602014-03-10 15:40:16 +0100184
Selim Cinek1408eb52014-06-02 14:45:38 +0200185 private ExpandHelper mExpandHelper;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400186 private final NotificationSwipeHelper mSwipeHelper;
Selim Cinek67b22602014-03-10 15:40:16 +0100187 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800188 private final Paint mBackgroundPaint = new Paint();
Adrian Roosf0b4f962017-05-25 11:53:11 -0700189 private final boolean mShouldDrawNotificationBackground;
Selim Cinekda330ff2019-06-11 15:41:28 -0700190 private boolean mHighPriorityBeforeSpeedBump;
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500191 private final boolean mAllowLongPress;
Gus Prevasa18dc572019-01-14 16:11:22 -0500192 private boolean mDismissRtl;
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100193
Selim Cinekbc243a92016-09-27 16:35:13 -0700194 private float mExpandedHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100195 private int mOwnScrollY;
Gus Prevas0fa58d62019-01-11 13:58:40 -0500196 private View mScrollAnchorView;
197 private int mScrollAnchorViewY;
Selim Cinek67b22602014-03-10 15:40:16 +0100198 private int mMaxLayoutHeight;
199
200 private VelocityTracker mVelocityTracker;
201 private OverScroller mScroller;
Gus Prevascdc98342019-01-14 14:29:44 -0500202 /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
203 private int mLastScrollerY;
204 /**
205 * True if the max position was set to a known position on the last call to {@link #mScroller}.
206 */
207 private boolean mIsScrollerBoundSet;
Ricky Waicd35def2016-05-03 11:07:07 +0100208 private Runnable mFinishScrollingCallback;
Selim Cinek67b22602014-03-10 15:40:16 +0100209 private int mTouchSlop;
210 private int mMinimumVelocity;
211 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100212 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200213 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100214 private boolean mIsBeingDragged;
215 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200216 private int mDownX;
Dong-wan Kimb9266662016-09-21 13:08:30 -0700217 private int mActivePointerId = INVALID_POINTER;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100218 private boolean mTouchIsClick;
219 private float mInitialTouchX;
220 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100221
Selim Cinek67b22602014-03-10 15:40:16 +0100222 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100223 private int mContentHeight;
Lucas Dupin60661a62018-04-12 10:50:13 -0700224 private int mIntrinsicContentHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100225 private int mCollapsedSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100226 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800227 private int mIncreasedPaddingBetweenElements;
shawnlin8e4e92c2018-04-12 18:47:24 +0800228 private int mMaxTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200229 private int mTopPadding;
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700230 private int mBottomMargin;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700231 private int mBottomInset = 0;
shawnlin8e4e92c2018-04-12 18:47:24 +0800232 private float mQsExpansionFraction;
Selim Cinek67b22602014-03-10 15:40:16 +0100233
234 /**
235 * The algorithm which calculates the properties for our children
236 */
Muyuan Li87798022016-04-07 17:51:25 -0700237 protected final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100238
Selim Cinek281c2022016-10-13 19:14:43 -0700239 private final AmbientState mAmbientState;
Selim Cinekb5605e52015-02-20 18:21:41 +0100240 private NotificationGroupManager mGroupManager;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500241 private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700242 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500243 private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
244 private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200245 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700246 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
247 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200248 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200249 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200250 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800251 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200252
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200253 /**
254 * The raw amount of the overScroll on the top, which is not rubber-banded.
255 */
256 private float mOverScrolledTopPixels;
257
258 /**
259 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
260 */
261 private float mOverScrolledBottomPixels;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900262 private NotificationLogger.OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200263 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200264 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100265 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200266 private boolean mNeedsAnimation;
267 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200268 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200269 private boolean mHideSensitiveNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200270 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200271 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200272 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200273 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200274 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700275 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200276 private boolean mExpandingNotification;
277 private boolean mExpandedInThisMotion;
shawnlin8e4e92c2018-04-12 18:47:24 +0800278 private boolean mShouldShowShelfOnly;
Muyuan Li84b45612016-06-01 11:05:08 -0700279 protected boolean mScrollingEnabled;
Julia Reynoldsed1c9af2018-03-21 15:21:09 -0400280 protected FooterView mFooterView;
Muyuan Lidd9ae752016-05-13 16:45:43 -0700281 protected EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400282 private boolean mDismissAllInProgress;
Anthony Chen7acbb772017-04-07 16:45:25 -0700283 private boolean mFadeNotificationsOnDismiss;
Selim Cinek1408eb52014-06-02 14:45:38 +0200284
285 /**
286 * Was the scroller scrolled to the top when the down motion was observed?
287 */
288 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200289 /**
290 * The minimal amount of over scroll which is needed in order to switch to the quick settings
291 * when over scrolling on a expanded card.
292 */
293 private float mMinTopOverScrollToEscape;
294 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700295 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200296 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200297 private boolean mDontReportNextOverScroll;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700298 private boolean mDontClampNextScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200299 private boolean mNeedViewResizeAnimation;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500300 private ExpandableView mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700301 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700302
Selim Cinek1408eb52014-06-02 14:45:38 +0200303 /**
304 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
305 * This is needed to avoid scrolling too far after the notification was collapsed in the same
306 * motion.
307 */
308 private int mMaxScrollAfterExpand;
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000309 private ExpandableNotificationRow.LongPressListener mLongPressListener;
Mady Mellorc2dbe492017-03-30 13:22:03 -0700310 boolean mCheckForLeavebehind;
Selim Cinek1408eb52014-06-02 14:45:38 +0200311
312 /**
313 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
314 * animating.
315 */
316 private boolean mOnlyScrollingInThisMotion;
Adrian Roosfa139752016-04-27 09:59:08 -0700317 private boolean mDisallowDismissInThisMotion;
Selim Cineka59ecc32015-04-07 10:51:49 -0700318 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700319 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200320 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200321 = new ViewTreeObserver.OnPreDrawListener() {
322 @Override
323 public boolean onPreDraw() {
Adrian Roos181385c2016-05-05 17:45:44 -0400324 updateForcedScroll();
Selim Cinek1f553cf2014-05-02 12:01:36 +0200325 updateChildren();
326 mChildrenUpdateRequested = false;
327 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200328 return true;
329 }
330 };
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500331 private StatusBar mStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100332 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100333 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700334 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek9dd0d042018-05-14 18:12:42 -0700335 private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700336 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
337 = new HashSet<>();
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900338 private HeadsUpManagerPhone mHeadsUpManager;
Lucas Dupin00be88f2019-01-03 17:50:52 -0800339 private final NotificationRoundnessManager mRoundnessManager;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700340 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700341 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200342 private boolean mForceNoOverlappingRendering;
Selim Cineke0890e52015-06-17 11:17:08 -0700343 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700344 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800345 private boolean mAnimationRunning;
Selim Cinekc383fd02016-10-21 15:31:26 -0700346 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
Selim Cinek6811d722016-01-19 17:53:12 -0800347 = new ViewTreeObserver.OnPreDrawListener() {
348 @Override
349 public boolean onPreDraw() {
Selim Cinekc383fd02016-10-21 15:31:26 -0700350 onPreDrawDuringAnimation();
Selim Cinek6811d722016-01-19 17:53:12 -0800351 return true;
352 }
353 };
Gus Prevase2d6f042018-10-17 15:25:30 -0400354 private NotificationSection[] mSections = new NotificationSection[NUM_SECTIONS];
Selim Cinek614576e2016-01-20 10:54:09 -0800355 private boolean mAnimateNextBackgroundTop;
Gus Prevase2d6f042018-10-17 15:25:30 -0400356 private boolean mAnimateNextBackgroundBottom;
357 private boolean mAnimateNextSectionBoundsChange;
Selim Cinekd35c2792016-01-21 13:20:57 -0800358 private int mBgColor;
359 private float mDimAmount;
360 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800361 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400362 private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
Selim Cinekd35c2792016-01-21 13:20:57 -0800363 @Override
364 public void onAnimationEnd(Animator animation) {
365 mDimAnimator = null;
366 }
367 };
368 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
369 = new ValueAnimator.AnimatorUpdateListener() {
370
371 @Override
372 public void onAnimationUpdate(ValueAnimator animation) {
373 setDimAmount((Float) animation.getAnimatedValue());
374 }
375 };
Muyuan Li4fe4a402016-03-30 16:50:11 -0700376 protected ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800377 private boolean mContinuousShadowUpdate;
Selim Cinekae55d832019-02-22 17:43:43 -0800378 private boolean mContinuousBackgroundUpdate;
Selim Cinek33223572016-02-19 19:32:22 -0800379 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
380 = new ViewTreeObserver.OnPreDrawListener() {
381
382 @Override
383 public boolean onPreDraw() {
384 updateViewShadows();
385 return true;
386 }
387 };
Selim Cinekae55d832019-02-22 17:43:43 -0800388 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
389 updateBackground();
390 return true;
391 };
Selim Cinek33223572016-02-19 19:32:22 -0800392 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
393 @Override
394 public int compare(ExpandableView view, ExpandableView otherView) {
395 float endY = view.getTranslationY() + view.getActualHeight();
396 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
397 if (endY < otherEndY) {
398 return -1;
399 } else if (endY > otherEndY) {
400 return 1;
401 } else {
402 // The two notifications end at the same location
403 return 0;
404 }
405 }
406 };
Lucas Dupin64e2f572019-03-21 14:21:14 -0700407 private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
408 @Override
409 public void getOutline(View view, Outline outline) {
Selim Cinek195dfc52019-05-30 19:35:05 -0700410 if (mAmbientState.isHiddenAtAll()) {
411 float xProgress = mHideXInterpolator.getInterpolation(
412 (1 - mLinearHideAmount) * mBackgroundXFactor);
Selim Cinek9ad240c2019-04-08 19:07:15 -0700413 outline.setRoundRect(mBackgroundAnimationRect,
414 MathUtils.lerp(mCornerRadius / 2.0f, mCornerRadius,
415 xProgress));
Selim Cinek33295a42019-07-26 19:49:08 -0700416 outline.setAlpha(1.0f - mAmbientState.getHideAmount());
Lucas Dupin64e2f572019-03-21 14:21:14 -0700417 } else {
418 ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
419 }
420 }
421 };
Selim Cinek25503252016-03-03 15:31:43 -0800422 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900423 private boolean mPulsing;
Selim Cinek1b2a05e2016-04-28 14:20:39 -0700424 private boolean mGroupExpandedForMeasure;
Selim Cinekc22fff62016-05-20 12:44:30 -0700425 private boolean mScrollable;
Adrian Roos181385c2016-05-05 17:45:44 -0400426 private View mForcedScroll;
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200427
428 /**
Selim Cinek195dfc52019-05-30 19:35:05 -0700429 * @see #setHideAmount(float, float)
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200430 */
Selim Cinek195dfc52019-05-30 19:35:05 -0700431 private float mInterpolatedHideAmount = 0f;
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200432
433 /**
Selim Cinek195dfc52019-05-30 19:35:05 -0700434 * @see #setHideAmount(float, float)
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200435 */
Selim Cinek195dfc52019-05-30 19:35:05 -0700436 private float mLinearHideAmount = 0f;
Lucas Dupin439bd442018-06-12 15:05:28 -0700437
438 /**
439 * How fast the background scales in the X direction as a factor of the Y expansion.
440 */
441 private float mBackgroundXFactor = 1f;
Selim Cinek972123d2016-05-03 14:25:58 -0700442
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -0500443 private boolean mSwipingInProgress;
444
Lucas Dupine17ce522017-07-17 15:45:06 -0700445 private boolean mUsingLightTheme;
Selim Cinekbc243a92016-09-27 16:35:13 -0700446 private boolean mQsExpanded;
Selim Cinekef406062016-09-29 17:33:13 -0700447 private boolean mForwardScrollable;
448 private boolean mBackwardScrollable;
Selim Cinek281c2022016-10-13 19:14:43 -0700449 private NotificationShelf mShelf;
Selim Cinekad7fac02016-10-18 17:09:15 -0700450 private int mMaxDisplayedNotifications = -1;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800451 private int mStatusBarHeight;
Selim Cinek51d21972017-07-19 17:39:20 -0700452 private int mMinInteractionHeight;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800453 private boolean mNoAmbient;
454 private final Rect mClipRect = new Rect();
455 private boolean mIsClipped;
Selim Cinekcafa87f2016-10-26 17:00:17 -0700456 private Rect mRequestedClipBounds;
457 private boolean mInHeadsUpPinnedMode;
458 private boolean mHeadsUpAnimatingAway;
Selim Cinek355652a2016-12-07 13:32:12 -0800459 private int mStatusBarState;
Selim Cinekfb6ee6d2016-12-29 16:49:26 +0100460 private int mCachedBackgroundColor;
Selim Cinek5cf1d052017-06-01 17:36:46 -0700461 private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
Gus Prevascdc98342019-01-14 14:29:44 -0500462 private Runnable mReflingAndAnimateScroll = () -> {
463 if (ANCHOR_SCROLLING) {
464 maybeReflingScroller();
465 }
466 animateScroll();
467 };
Selim Cinek0fe07392017-11-09 13:26:34 -0800468 private int mCornerRadius;
Selim Cinek515b2032017-11-15 10:20:19 -0800469 private int mSidePaddings;
Lucas Dupin16cfe452018-02-08 13:14:50 -0800470 private final Rect mBackgroundAnimationRect = new Rect();
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800471 private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
472 private int mHeadsUpInset;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700473 private HeadsUpAppearanceController mHeadsUpAppearanceController;
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700474 private NotificationIconAreaController mIconAreaController;
Jason Monk1fd3fc32018-08-14 17:20:09 -0400475 private final NotificationLockscreenUserManager mLockscreenUserManager =
476 Dependency.get(NotificationLockscreenUserManager.class);
Jason Monke59dc402018-08-16 12:05:01 -0400477 private final Rect mTmpRect = new Rect();
478 private final NotificationEntryManager mEntryManager =
479 Dependency.get(NotificationEntryManager.class);
480 private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
481 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Will Brockmane718d582019-01-17 16:38:38 -0500482 @VisibleForTesting
483 protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
Jason Monke59dc402018-08-16 12:05:01 -0400484 private final NotificationRemoteInputManager mRemoteInputManager =
485 Dependency.get(NotificationRemoteInputManager.class);
486 private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
487
488 private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
489 private final LockscreenGestureLogger mLockscreenGestureLogger =
490 Dependency.get(LockscreenGestureLogger.class);
491 private final VisualStabilityManager mVisualStabilityManager =
492 Dependency.get(VisualStabilityManager.class);
493 protected boolean mClearAllEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100494
Selim Cinek195dfc52019-05-30 19:35:05 -0700495 private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
Jason Monke59dc402018-08-16 12:05:01 -0400496 private NotificationPanelView mNotificationPanel;
Jason Monk297c04e2018-08-23 17:16:59 -0400497 private final ShadeController mShadeController = Dependency.get(ShadeController.class);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400498
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400499 private final NotificationGutsManager
500 mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
Ned Burns9eb06332019-04-23 16:02:12 -0400501 private final NotificationSectionsManager mSectionsManager;
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700502 private boolean mAnimateBottomOnLayout;
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700503 private float mLastSentAppear;
504 private float mLastSentExpandedHeight;
Selim Cinekaae9ad62019-07-22 18:36:54 -0700505 private boolean mWillExpand;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400506
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500507 @Inject
508 public NotificationStackScrollLayout(
509 @Named(VIEW_CONTEXT) Context context,
510 AttributeSet attrs,
Lucas Dupin00be88f2019-01-03 17:50:52 -0800511 @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
Selim Cinek3d6ae232019-01-04 14:14:33 -0800512 NotificationRoundnessManager notificationRoundnessManager,
Ned Burns9eb06332019-04-23 16:02:12 -0400513 DynamicPrivacyController dynamicPrivacyController,
Ned Burns2c74c2a2019-06-13 19:06:47 -0400514 ConfigurationController configurationController,
Ned Burns7eeccdd2019-05-15 14:50:11 -0400515 ActivityStarter activityStarter,
Selim Cinekc3fec682019-06-06 18:11:07 -0700516 StatusBarStateController statusBarStateController,
517 HeadsUpManagerPhone headsUpManager,
Dave Mankoff781ef7e2019-06-28 16:33:25 -0400518 KeyguardBypassController keyguardBypassController,
519 FalsingManager falsingManager) {
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500520 super(context, attrs, 0, 0);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800521 Resources res = getResources();
522
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500523 mAllowLongPress = allowLongPress;
524
Gus Prevase2d6f042018-10-17 15:25:30 -0400525 for (int i = 0; i < NUM_SECTIONS; i++) {
526 mSections[i] = new NotificationSection(this);
527 }
Selim Cinekc3fec682019-06-06 18:11:07 -0700528 mRoundnessManager = notificationRoundnessManager;
Gus Prevase2d6f042018-10-17 15:25:30 -0400529
Selim Cinekc3fec682019-06-06 18:11:07 -0700530 mHeadsUpManager = headsUpManager;
531 mHeadsUpManager.addListener(mRoundnessManager);
532 mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
533 mKeyguardBypassController = keyguardBypassController;
Dave Mankoff781ef7e2019-06-28 16:33:25 -0400534 mFalsingManager = falsingManager;
Ned Burns61269442019-05-02 18:27:23 -0400535
Ned Burns9eb06332019-04-23 16:02:12 -0400536 mSectionsManager =
537 new NotificationSectionsManager(
538 this,
539 activityStarter,
Ned Burns7eeccdd2019-05-15 14:50:11 -0400540 statusBarStateController,
Ned Burns2c74c2a2019-06-13 19:06:47 -0400541 configurationController,
Ned Burns9eb06332019-04-23 16:02:12 -0400542 NotificationUtils.useNewInterruptionModel(context));
Ned Burns2c74c2a2019-06-13 19:06:47 -0400543 mSectionsManager.initialize(LayoutInflater.from(context));
Ned Burns61269442019-05-02 18:27:23 -0400544 mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
545 // Leave the shade open if there will be other notifs left over to clear
546 final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
547 clearNotifications(ROWS_GENTLE, closeShade);
548 });
549
Selim Cinekc3fec682019-06-06 18:11:07 -0700550 mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
Selim Cinekd35c2792016-01-21 13:20:57 -0800551 mBgColor = context.getColor(R.color.notification_shade_background_color);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800552 int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
553 int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400554 mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
Selim Cinek1cf41c12014-08-12 20:06:19 +0200555 minHeight, maxHeight);
556 mExpandHelper.setEventSource(this);
557 mExpandHelper.setScrollAdapter(this);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400558 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback,
Dave Mankoff781ef7e2019-06-28 16:33:25 -0400559 getContext(), mMenuEventListener, mFalsingManager);
Muyuan Li333a4fc2016-04-16 17:13:46 -0700560 mStackScrollAlgorithm = createStackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100561 initView(context);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800562 mShouldDrawNotificationBackground =
563 res.getBoolean(R.bool.config_drawNotificationBackground);
Anthony Chen7acbb772017-04-07 16:45:25 -0700564 mFadeNotificationsOnDismiss =
565 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
Selim Cinek29aab962018-02-27 17:05:45 -0800566 mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated);
567 mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700568 addOnExpandedHeightChangedListener(mRoundnessManager::setExpanded);
Lucas Dupin64e2f572019-03-21 14:21:14 -0700569 setOutlineProvider(mOutlineProvider);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800570
Rohan Shah524cf7b2018-03-15 14:40:02 -0700571 // Blocking helper manager wants to know the expanded state, update as well.
572 NotificationBlockingHelperManager blockingHelperManager =
573 Dependency.get(NotificationBlockingHelperManager.class);
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700574 addOnExpandedHeightChangedListener((height, unused) -> {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700575 blockingHelperManager.setNotificationShadeExpanded(height);
576 });
577
Selim Cinek195dfc52019-05-30 19:35:05 -0700578 boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
579 setWillNotDraw(!willDraw);
Lucas Dupind285cf02018-01-18 09:18:23 -0800580 mBackgroundPaint.setAntiAlias(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100581 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100582 mDebugPaint = new Paint();
583 mDebugPaint.setColor(0xffff0000);
584 mDebugPaint.setStrokeWidth(2);
585 mDebugPaint.setStyle(Paint.Style.STROKE);
Gus Prevas0fa58d62019-01-11 13:58:40 -0500586 mDebugPaint.setTextSize(25f);
Selim Cinek67b22602014-03-10 15:40:16 +0100587 }
Jason Monke59dc402018-08-16 12:05:01 -0400588 mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
Gus Prevas33619af2018-10-26 15:40:27 -0400589
590 TunerService tunerService = Dependency.get(TunerService.class);
591 tunerService.addTunable((key, newValue) -> {
Selim Cinekda330ff2019-06-11 15:41:28 -0700592 if (key.equals(HIGH_PRIORITY)) {
593 mHighPriorityBeforeSpeedBump = "1".equals(newValue);
Gus Prevasa18dc572019-01-14 16:11:22 -0500594 } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
595 updateDismissRtlSetting("1".equals(newValue));
Gus Prevas33619af2018-10-26 15:40:27 -0400596 }
Selim Cinekda330ff2019-06-11 15:41:28 -0700597 }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500598
599 mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
600 @Override
Mady Mellor0ad5b9d2019-01-08 14:59:55 -0800601 public void onPostEntryUpdated(NotificationEntry entry) {
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500602 if (!entry.notification.isClearable()) {
603 // The user may have performed a dismiss action on the notification, since it's
604 // not clearable we should snap it back.
605 snapViewIfNeeded(entry);
606 }
607 }
608 });
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700609 dynamicPrivacyController.addListener(this);
Selim Cinek5454a0d2019-07-30 17:14:50 -0700610 mDynamicPrivacyController = dynamicPrivacyController;
611 mStatusbarStateController = (SysuiStatusBarStateController) statusBarStateController;
Jason Monke59dc402018-08-16 12:05:01 -0400612 }
613
Gus Prevasa18dc572019-01-14 16:11:22 -0500614 private void updateDismissRtlSetting(boolean dismissRtl) {
615 mDismissRtl = dismissRtl;
616 for (int i = 0; i < getChildCount(); i++) {
617 View child = getChildAt(i);
618 if (child instanceof ExpandableNotificationRow) {
619 ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
620 }
621 }
622 }
623
Jason Monke59dc402018-08-16 12:05:01 -0400624 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400625 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400626 protected void onFinishInflate() {
627 super.onFinishInflate();
628
629 inflateEmptyShadeView();
630 inflateFooterView();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400631 mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500632 if (mAllowLongPress) {
Steve Elliottff6c25e2019-05-30 17:38:52 -0400633 setLongPressListener(mNotificationGutsManager::openGuts);
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500634 }
Jason Monke59dc402018-08-16 12:05:01 -0400635 }
636
Selim Cinek34518f62019-02-28 19:41:18 -0800637 /**
638 * @return the height at which we will wake up when pulsing
639 */
Selim Cinekb0fada62019-06-17 19:03:59 -0700640 public float getWakeUpHeight() {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800641 ActivatableNotificationView firstChild = getFirstChildWithBackground();
642 if (firstChild != null) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700643 if (mKeyguardBypassController.getBypassEnabled()) {
644 return firstChild.getHeadsUpHeightWithoutHeader();
645 } else {
646 return firstChild.getCollapsedHeight();
647 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800648 }
Selim Cinek34518f62019-02-28 19:41:18 -0800649 return 0f;
Selim Cinek3d6ae232019-01-04 14:14:33 -0800650 }
651
Jason Monke59dc402018-08-16 12:05:01 -0400652 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400653 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400654 public void onDensityOrFontScaleChanged() {
Selim Cinekdd26a7e2019-02-11 18:42:55 -0800655 reinflateViews();
656 }
657
658 private void reinflateViews() {
Jason Monke59dc402018-08-16 12:05:01 -0400659 inflateFooterView();
660 inflateEmptyShadeView();
661 updateFooter();
Ned Burns2c74c2a2019-06-13 19:06:47 -0400662 mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
Jason Monke59dc402018-08-16 12:05:01 -0400663 }
664
665 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400666 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400667 public void onThemeChanged() {
Lucas Dupin89233c42019-06-10 18:00:22 -0700668 final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
Jason Monke59dc402018-08-16 12:05:01 -0400669 updateDecorViews(useDarkText);
670
671 updateFooter();
672 }
673
Fabian Kozynskid254b192019-02-05 13:42:58 -0500674 @Override
675 public void onOverlayChanged() {
676 int newRadius = mContext.getResources().getDimensionPixelSize(
677 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
678 if (mCornerRadius != newRadius) {
679 mCornerRadius = newRadius;
680 invalidate();
681 }
Selim Cinekdd26a7e2019-02-11 18:42:55 -0800682 reinflateViews();
Fabian Kozynskid254b192019-02-05 13:42:58 -0500683 }
684
Jason Monke59dc402018-08-16 12:05:01 -0400685 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400686 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400687 public void updateFooter() {
Ned Burns61269442019-05-02 18:27:23 -0400688 boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
Jason Monke59dc402018-08-16 12:05:01 -0400689 boolean showFooterView = (showDismissView ||
690 mEntryManager.getNotificationData().getActiveNotifications().size() != 0)
691 && mStatusBarState != StatusBarState.KEYGUARD
692 && !mRemoteInputManager.getController().isRemoteInputActive();
693
694 updateFooterView(showFooterView, showDismissView);
695 }
696
697 /**
698 * Return whether there are any clearable notifications
699 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400700 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -0400701 public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
Selim Cinek5454a0d2019-07-30 17:14:50 -0700702 if (mDynamicPrivacyController.isInLockedDownShade()) {
703 return false;
704 }
Jason Monke59dc402018-08-16 12:05:01 -0400705 int childCount = getChildCount();
706 for (int i = 0; i < childCount; i++) {
707 View child = getChildAt(i);
708 if (!(child instanceof ExpandableNotificationRow)) {
709 continue;
710 }
Ned Burns61269442019-05-02 18:27:23 -0400711 final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
712 if (row.canViewBeDismissed() && matchesSelection(row, selection)) {
Jason Monke59dc402018-08-16 12:05:01 -0400713 return true;
714 }
715 }
716 return false;
717 }
718
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400719 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400720 public RemoteInputController.Delegate createDelegate() {
Jason Monke59dc402018-08-16 12:05:01 -0400721 return new RemoteInputController.Delegate() {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500722 public void setRemoteInputActive(NotificationEntry entry,
Jason Monke59dc402018-08-16 12:05:01 -0400723 boolean remoteInputActive) {
724 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
Evan Laird94492852018-10-25 13:43:01 -0400725 entry.notifyHeightChanged(true /* needsAnimation */);
Jason Monke59dc402018-08-16 12:05:01 -0400726 updateFooter();
727 }
728
Ned Burnsf81c4c42019-01-07 14:10:43 -0500729 public void lockScrollTo(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -0400730 NotificationStackScrollLayout.this.lockScrollTo(entry.getRow());
Jason Monke59dc402018-08-16 12:05:01 -0400731 }
732
733 public void requestDisallowLongPressAndDismiss() {
734 requestDisallowLongPress();
735 requestDisallowDismiss();
736 }
737 };
Selim Cinek67b22602014-03-10 15:40:16 +0100738 }
739
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900740 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400741 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk1fd3fc32018-08-14 17:20:09 -0400742 protected void onAttachedToWindow() {
743 super.onAttachedToWindow();
Beverly8fdb5332019-02-04 14:29:49 -0500744 ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
745 .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
Jason Monke59dc402018-08-16 12:05:01 -0400746 Dependency.get(ConfigurationController.class).addCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400747 }
748
749 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400750 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk1fd3fc32018-08-14 17:20:09 -0400751 protected void onDetachedFromWindow() {
752 super.onDetachedFromWindow();
Jason Monkaf08c152018-12-04 11:12:39 -0500753 Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
Jason Monke59dc402018-08-16 12:05:01 -0400754 Dependency.get(ConfigurationController.class).removeCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400755 }
756
757 @Override
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400758 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellor95d743c2017-01-10 12:05:27 -0800759 public NotificationSwipeActionHelper getSwipeActionHelper() {
760 return mSwipeHelper;
761 }
762
Selim Cinek67b22602014-03-10 15:40:16 +0100763 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400764 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupinf03e7522018-06-25 16:21:13 -0700765 public void onUiModeChanged() {
766 mBgColor = mContext.getColor(R.color.notification_shade_background_color);
767 updateBackgroundDimming();
Selim Cinekab9c7b22018-12-11 18:15:47 -0800768 mShelf.onUiModeChanged();
Ned Burns9eb06332019-04-23 16:02:12 -0400769 mSectionsManager.onUiModeChanged();
Lucas Dupinf03e7522018-06-25 16:21:13 -0700770 }
771
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400772 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Selim Cinek67b22602014-03-10 15:40:16 +0100773 protected void onDraw(Canvas canvas) {
Lucas Dupind285cf02018-01-18 09:18:23 -0800774 if (mShouldDrawNotificationBackground
Gus Prevase2d6f042018-10-17 15:25:30 -0400775 && (mSections[0].getCurrentBounds().top
776 < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700777 || mAmbientState.isDozing())) {
Lucas Dupind285cf02018-01-18 09:18:23 -0800778 drawBackground(canvas);
Gus Prevas211181532018-12-13 14:49:33 -0500779 } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
780 drawHeadsUpBackground(canvas);
Selim Cinekd381bc32016-08-15 12:40:57 -0700781 }
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800782
Selim Cinek67b22602014-03-10 15:40:16 +0100783 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800784 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100785 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Mady Mellor43c2cd12016-12-12 21:05:13 -0800786 y = getLayoutHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100787 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200788 y = getHeight() - getEmptyBottomMargin();
789 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100790 }
791 }
792
Gus Prevas0fa58d62019-01-11 13:58:40 -0500793 @Override
794 public void draw(Canvas canvas) {
795 super.draw(canvas);
796
797 if (DEBUG && ANCHOR_SCROLLING) {
798 if (mScrollAnchorView instanceof ExpandableNotificationRow) {
799 canvas.drawRect(0,
800 mScrollAnchorView.getTranslationY(),
801 getWidth(),
802 mScrollAnchorView.getTranslationY()
803 + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
804 mDebugPaint);
805 canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
806 mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
807 int y = (int) mShelf.getTranslationY();
808 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
809 }
Gus Prevascdc98342019-01-14 14:29:44 -0500810 canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
Selim Cinekdb6d6df2019-05-22 20:39:59 -0700811 getTopPadding() + 30, mDebugPaint);
Gus Prevascdc98342019-01-14 14:29:44 -0500812 canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
813 getHeight() - 30, mDebugPaint);
Gus Prevas0fa58d62019-01-11 13:58:40 -0500814 }
815 }
816
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400817 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Lucas Dupind285cf02018-01-18 09:18:23 -0800818 private void drawBackground(Canvas canvas) {
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700819 int lockScreenLeft = mSidePaddings;
820 int lockScreenRight = getWidth() - mSidePaddings;
Gus Prevase2d6f042018-10-17 15:25:30 -0400821 int lockScreenTop = mSections[0].getCurrentBounds().top;
822 int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom;
Selim Cinek195dfc52019-05-30 19:35:05 -0700823 int hiddenLeft = getWidth() / 2;
824 int hiddenTop = mTopPadding;
Lucas Dupind285cf02018-01-18 09:18:23 -0800825
Selim Cinek195dfc52019-05-30 19:35:05 -0700826 float yProgress = 1 - mInterpolatedHideAmount;
827 float xProgress = mHideXInterpolator.getInterpolation(
828 (1 - mLinearHideAmount) * mBackgroundXFactor);
Lucas Dupin60661a62018-04-12 10:50:13 -0700829
Selim Cinek195dfc52019-05-30 19:35:05 -0700830 int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
831 int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
832 int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
833 int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700834 mBackgroundAnimationRect.set(
Gus Prevase2d6f042018-10-17 15:25:30 -0400835 left,
836 top,
837 right,
838 bottom);
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200839
Gus Prevase2d6f042018-10-17 15:25:30 -0400840 int backgroundTopAnimationOffset = top - lockScreenTop;
841 // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
842 boolean anySectionHasVisibleChild = false;
843 for (NotificationSection section : mSections) {
844 if (section.getFirstVisibleChild() != null) {
845 anySectionHasVisibleChild = true;
846 break;
847 }
848 }
Selim Cinek5fedd812019-06-26 19:34:24 -0700849 boolean shouldDrawBackground;
850 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
851 shouldDrawBackground = isPulseExpanding();
852 } else {
853 shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
854 }
855 if (shouldDrawBackground) {
Gus Prevase2d6f042018-10-17 15:25:30 -0400856 drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
Lucas Dupind285cf02018-01-18 09:18:23 -0800857 }
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700858
Lucas Dupin16cfe452018-02-08 13:14:50 -0800859 updateClipping();
Lucas Dupind285cf02018-01-18 09:18:23 -0800860 }
861
Gus Prevase2d6f042018-10-17 15:25:30 -0400862 /**
863 * Draws round rects for each background section.
864 *
865 * We want to draw a round rect for each background section as defined by {@link #mSections}.
866 * However, if two sections are directly adjacent with no gap between them (e.g. on the
867 * lockscreen where the shelf can appear directly below the high priority section, or while
868 * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
869 * section), we don't want to round the adjacent corners.
870 *
871 * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
872 * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
873 * This method tracks the top of each rect we need to draw, then iterates through the visible
874 * sections. If a section is not adjacent to the previous section, we draw the previous rect
875 * behind the sections we've accumulated up to that point, then start a new rect at the top of
876 * the current section. When we're done iterating we will always have one rect left to draw.
877 */
878 private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
879 int animationYOffset) {
880 int backgroundRectTop = top;
881 int lastSectionBottom =
882 mSections[0].getCurrentBounds().bottom + animationYOffset;
Selim Cinek0acf4302019-06-11 12:43:03 -0700883 int currentLeft = left;
884 int currentRight = right;
Selim Cinekae55d832019-02-22 17:43:43 -0800885 boolean first = true;
Gus Prevase2d6f042018-10-17 15:25:30 -0400886 for (NotificationSection section : mSections) {
887 if (section.getFirstVisibleChild() == null) {
888 continue;
889 }
890 int sectionTop = section.getCurrentBounds().top + animationYOffset;
Selim Cinekae55d832019-02-22 17:43:43 -0800891 int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
Selim Cinekd0b48e32019-05-24 20:49:23 -0700892 int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
Gus Prevase2d6f042018-10-17 15:25:30 -0400893 // If sections are directly adjacent to each other, we don't want to draw them
894 // as separate roundrects, as the rounded corners right next to each other look
895 // bad.
Selim Cinekae55d832019-02-22 17:43:43 -0800896 if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
Selim Cinek0acf4302019-06-11 12:43:03 -0700897 || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
898 canvas.drawRoundRect(currentLeft,
Gus Prevase2d6f042018-10-17 15:25:30 -0400899 backgroundRectTop,
Selim Cinek0acf4302019-06-11 12:43:03 -0700900 currentRight,
Gus Prevase2d6f042018-10-17 15:25:30 -0400901 lastSectionBottom,
902 mCornerRadius, mCornerRadius, mBackgroundPaint);
903 backgroundRectTop = sectionTop;
904 }
Selim Cinek0acf4302019-06-11 12:43:03 -0700905 currentLeft = ownLeft;
906 currentRight = ownRight;
Gus Prevase2d6f042018-10-17 15:25:30 -0400907 lastSectionBottom =
908 section.getCurrentBounds().bottom + animationYOffset;
Selim Cinekae55d832019-02-22 17:43:43 -0800909 first = false;
Gus Prevase2d6f042018-10-17 15:25:30 -0400910 }
Selim Cinek0acf4302019-06-11 12:43:03 -0700911 canvas.drawRoundRect(currentLeft,
Gus Prevase2d6f042018-10-17 15:25:30 -0400912 backgroundRectTop,
Selim Cinek0acf4302019-06-11 12:43:03 -0700913 currentRight,
Gus Prevase2d6f042018-10-17 15:25:30 -0400914 lastSectionBottom,
915 mCornerRadius, mCornerRadius, mBackgroundPaint);
916 }
917
Gus Prevas211181532018-12-13 14:49:33 -0500918 private void drawHeadsUpBackground(Canvas canvas) {
919 int left = mSidePaddings;
920 int right = getWidth() - mSidePaddings;
921
922 float top = getHeight();
923 float bottom = 0;
924 int childCount = getChildCount();
925 for (int i = 0; i < childCount; i++) {
926 View child = getChildAt(i);
927 if (child.getVisibility() != View.GONE
928 && child instanceof ExpandableNotificationRow) {
929 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
shawnlin3a950c32019-05-15 20:06:10 +0800930 if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
931 && row.getProvider().shouldShowGutsOnSnapOpen()) {
Gus Prevas211181532018-12-13 14:49:33 -0500932 top = Math.min(top, row.getTranslationY());
933 bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
934 }
935 }
936 }
937
938 if (top < bottom) {
939 canvas.drawRoundRect(
940 left, top, right, bottom,
941 mCornerRadius, mCornerRadius, mBackgroundPaint);
942 }
943 }
944
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400945 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd35c2792016-01-21 13:20:57 -0800946 private void updateBackgroundDimming() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800947 // No need to update the background color if it's not being drawn.
948 if (!mShouldDrawNotificationBackground) {
949 return;
950 }
951
Lucas Dupinb561eda2018-04-09 17:25:04 -0700952 // Interpolate between semi-transparent notification panel background color
953 // and white AOD separator.
Lucas Dupinf03e7522018-06-25 16:21:13 -0700954 float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
Selim Cinek195dfc52019-05-30 19:35:05 -0700955 mLinearHideAmount);
Felka Chang97bc8d52019-05-29 02:51:15 +0800956 int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
Lucas Dupind285cf02018-01-18 09:18:23 -0800957
Selim Cinekfb6ee6d2016-12-29 16:49:26 +0100958 if (mCachedBackgroundColor != color) {
959 mCachedBackgroundColor = color;
960 mBackgroundPaint.setColor(color);
961 invalidate();
962 }
Selim Cinekd35c2792016-01-21 13:20:57 -0800963 }
964
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400965 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +0100966 private void initView(Context context) {
967 mScroller = new OverScroller(getContext());
Selim Cinek67b22602014-03-10 15:40:16 +0100968 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200969 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100970 final ViewConfiguration configuration = ViewConfiguration.get(context);
971 mTouchSlop = configuration.getScaledTouchSlop();
972 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
973 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100974 mOverflingDistance = configuration.getScaledOverflingDistance();
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700975
976 Resources res = context.getResources();
977 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800978 mStackScrollAlgorithm.initView(context);
Selim Cinek281c2022016-10-13 19:14:43 -0700979 mAmbientState.reload(context);
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700980 mPaddingBetweenElements = Math.max(1,
981 res.getDimensionPixelSize(R.dimen.notification_divider_height));
982 mIncreasedPaddingBetweenElements =
983 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
984 mMinTopOverScrollToEscape = res.getDimensionPixelSize(
Selim Cinek1408eb52014-06-02 14:45:38 +0200985 R.dimen.min_top_overscroll_to_qs);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800986 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700987 mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
Selim Cinekb95fd182017-12-21 13:03:32 -0800988 mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
Selim Cinek51d21972017-07-19 17:39:20 -0700989 mMinInteractionHeight = res.getDimensionPixelSize(
990 R.dimen.notification_min_interaction_height);
Selim Cinek0fe07392017-11-09 13:26:34 -0800991 mCornerRadius = res.getDimensionPixelSize(
992 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800993 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
994 R.dimen.heads_up_status_bar_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200995 }
996
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400997 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekaef92ef2014-06-06 18:06:04 +0200998 private void notifyHeightChangeListener(ExpandableView view) {
Lucas Dupin60661a62018-04-12 10:50:13 -0700999 notifyHeightChangeListener(view, false /* needsAnimation */);
1000 }
1001
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001002 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Lucas Dupin60661a62018-04-12 10:50:13 -07001003 private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
Selim Cinekaef92ef2014-06-06 18:06:04 +02001004 if (mOnHeightChangedListener != null) {
Lucas Dupin60661a62018-04-12 10:50:13 -07001005 mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
Selim Cinekaef92ef2014-06-06 18:06:04 +02001006 }
Selim Cinek67b22602014-03-10 15:40:16 +01001007 }
1008
Selim Cinekb0fada62019-06-17 19:03:59 -07001009 public boolean isPulseExpanding() {
1010 return mAmbientState.isPulseExpanding();
1011 }
1012
Selim Cinek67b22602014-03-10 15:40:16 +01001013 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001014 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001015 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1016 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinekb95fd182017-12-21 13:03:32 -08001017
1018 int width = MeasureSpec.getSize(widthMeasureSpec);
1019 int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2,
1020 MeasureSpec.getMode(widthMeasureSpec));
Evan Lairdeb7dbd52018-06-28 13:17:25 -04001021 // Don't constrain the height of the children so we know how big they'd like to be
1022 int childHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
1023 MeasureSpec.UNSPECIFIED);
1024
Selim Cinekfa760d42016-05-10 15:50:53 -04001025 // We need to measure all children even the GONE ones, such that the heights are calculated
1026 // correctly as they are used to calculate how many we can fit on the screen.
1027 final int size = getChildCount();
1028 for (int i = 0; i < size; i++) {
Evan Lairdeb7dbd52018-06-28 13:17:25 -04001029 measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -04001030 }
Selim Cinek67b22602014-03-10 15:40:16 +01001031 }
1032
1033 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001034 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001035 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinek67b22602014-03-10 15:40:16 +01001036 // we layout all our children centered on the top
1037 float centerX = getWidth() / 2.0f;
1038 for (int i = 0; i < getChildCount(); i++) {
1039 View child = getChildAt(i);
Selim Cinekfa760d42016-05-10 15:50:53 -04001040 // We need to layout all children even the GONE ones, such that the heights are
1041 // calculated correctly as they are used to calculate how many we can fit on the screen
Selim Cinek67b22602014-03-10 15:40:16 +01001042 float width = child.getMeasuredWidth();
1043 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001044 child.layout((int) (centerX - width / 2.0f),
1045 0,
1046 (int) (centerX + width / 2.0f),
1047 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +01001048 }
Jorim Jaggi1d480692014-05-20 19:41:58 +02001049 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +01001050 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001051 clampScrollPosition();
Selim Cinek319bdc42014-05-01 23:01:58 +02001052 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -08001053 updateFirstAndLastBackgroundViews();
Selim Cinekbc243a92016-09-27 16:35:13 -07001054 updateAlgorithmLayoutMinHeight();
Selim Cinek33295a42019-07-26 19:49:08 -07001055 updateOwnTranslationZ();
Selim Cinek67b22602014-03-10 15:40:16 +01001056 }
1057
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001058 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5bc852a2015-12-21 12:19:09 -08001059 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
1060 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +02001061 mNeedViewResizeAnimation = true;
1062 mNeedsAnimation = true;
1063 }
Selim Cineka5e211b2014-08-11 17:35:48 +02001064 }
1065
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001066 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekdb167372016-11-17 15:41:17 -08001067 public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) {
1068 mAmbientState.setSpeedBumpIndex(newIndex);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001069 mNoAmbient = noAmbient;
Selim Cinekc27437b2014-05-14 10:23:33 +02001070 }
1071
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001072 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001073 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001074 public void setChildLocationsChangedListener(
1075 NotificationLogger.OnChildLocationsChangedListener listener) {
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001076 mListener = listener;
1077 }
1078
Selim Cineka7d4f822016-12-06 14:34:47 -08001079 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001080 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001081 public boolean isInVisibleLocation(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04001082 ExpandableNotificationRow row = entry.getRow();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05001083 ExpandableViewState childViewState = row.getViewState();
1084
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001085 if (childViewState == null) {
Selim Cineka7d4f822016-12-06 14:34:47 -08001086 return false;
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001087 }
Selim Cinek9b9d6e12017-11-30 12:29:47 +01001088 if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
Selim Cineka7d4f822016-12-06 14:34:47 -08001089 return false;
Christoph Studer12cf9e52014-10-29 17:35:30 +01001090 }
Selim Cineka7d4f822016-12-06 14:34:47 -08001091 if (row.getVisibility() != View.VISIBLE) {
1092 return false;
1093 }
1094 return true;
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001095 }
1096
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001097 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Selim Cinek67b22602014-03-10 15:40:16 +01001098 private void setMaxLayoutHeight(int maxLayoutHeight) {
1099 mMaxLayoutHeight = maxLayoutHeight;
Selim Cinek9458b192016-10-25 19:02:42 -07001100 mShelf.setMaxLayoutHeight(maxLayoutHeight);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001101 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +01001102 }
1103
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001104 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001105 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001106 mAmbientState.setLayoutHeight(getLayoutHeight());
Selim Cinekbc243a92016-09-27 16:35:13 -07001107 updateAlgorithmLayoutMinHeight();
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001108 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +01001109 }
1110
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001111 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Selim Cinekbc243a92016-09-27 16:35:13 -07001112 private void updateAlgorithmLayoutMinHeight() {
shawnlinc3457912018-05-15 16:39:56 +08001113 mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition()
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001114 ? getLayoutMinHeight() : 0);
Selim Cinekbc243a92016-09-27 16:35:13 -07001115 }
1116
Selim Cinek67b22602014-03-10 15:40:16 +01001117 /**
1118 * Updates the children views according to the stack scroll algorithm. Call this whenever
1119 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
1120 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001121 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001122 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -08001123 updateScrollStateForAddedChildren();
Selim Cinek727903c2016-12-06 17:28:10 -08001124 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
1125 ? 0
1126 : mScroller.getCurrVelocity());
Gus Prevas0fa58d62019-01-11 13:58:40 -05001127 if (ANCHOR_SCROLLING) {
1128 mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
1129 mAmbientState.setAnchorViewY(mScrollAnchorViewY);
1130 } else {
1131 mAmbientState.setScrollY(mOwnScrollY);
1132 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05001133 mStackScrollAlgorithm.resetViewStates(mAmbientState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001134 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001135 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +01001136 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +02001137 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +01001138 }
1139 }
1140
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001141 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc383fd02016-10-21 15:31:26 -07001142 private void onPreDrawDuringAnimation() {
Selim Cineka686b2c2016-10-26 13:58:27 -07001143 mShelf.updateAppearance();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001144 updateClippingToTopRoundedCorner();
Selim Cinekc383fd02016-10-21 15:31:26 -07001145 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
1146 updateBackground();
1147 }
Selim Cinekc383fd02016-10-21 15:31:26 -07001148 }
1149
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001150 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001151 private void updateClippingToTopRoundedCorner() {
Arthur Hungc0ef5652018-05-22 14:00:42 +08001152 Float clipStart = (float) mTopPadding
Jason Monke59dc402018-08-16 12:05:01 -04001153 + mStackTranslation
1154 + mAmbientState.getExpandAnimationTopChange();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001155 Float clipEnd = clipStart + mCornerRadius;
1156 boolean first = true;
1157 for (int i = 0; i < getChildCount(); i++) {
1158 ExpandableView child = (ExpandableView) getChildAt(i);
1159 if (child.getVisibility() == GONE) {
1160 continue;
1161 }
1162 float start = child.getTranslationY();
Arthur Hungc0ef5652018-05-22 14:00:42 +08001163 float end = start + child.getActualHeight();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001164 boolean clip = clipStart > start && clipStart < end
1165 || clipEnd >= start && clipEnd <= end;
Gus Prevas0fa58d62019-01-11 13:58:40 -05001166 clip &= !(first && isScrolledToTop());
Selim Cinekeccf4942018-05-30 09:55:36 -07001167 child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
1168 : ExpandableView.NO_ROUNDNESS);
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001169 first = false;
1170 }
1171 }
1172
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001173 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek3776fe02016-02-04 13:32:43 -08001174 private void updateScrollStateForAddedChildren() {
1175 if (mChildrenToAddAnimated.isEmpty()) {
1176 return;
1177 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001178 if (!ANCHOR_SCROLLING) {
1179 for (int i = 0; i < getChildCount(); i++) {
1180 ExpandableView child = (ExpandableView) getChildAt(i);
1181 if (mChildrenToAddAnimated.contains(child)) {
1182 int startingPosition = getPositionInLinearLayout(child);
1183 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
1184 int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
1185 : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
1186 int childHeight = getIntrinsicHeight(child) + padding;
1187 if (startingPosition < mOwnScrollY) {
1188 // This child starts off screen, so let's keep it offscreen to keep the
1189 // others visible
Selim Cinek3776fe02016-02-04 13:32:43 -08001190
Gus Prevas0fa58d62019-01-11 13:58:40 -05001191 setOwnScrollY(mOwnScrollY + childHeight);
1192 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001193 }
1194 }
1195 }
1196 clampScrollPosition();
1197 }
1198
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001199 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04001200 private void updateForcedScroll() {
1201 if (mForcedScroll != null && (!mForcedScroll.hasFocus()
1202 || !mForcedScroll.isAttachedToWindow())) {
1203 mForcedScroll = null;
1204 }
1205 if (mForcedScroll != null) {
1206 ExpandableView expandableView = (ExpandableView) mForcedScroll;
1207 int positionInLinearLayout = getPositionInLinearLayout(expandableView);
1208 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
Adrian Roos4a579672016-05-24 16:54:37 -07001209 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Adrian Roos181385c2016-05-05 17:45:44 -04001210
Gus Prevas0fa58d62019-01-11 13:58:40 -05001211 if (ANCHOR_SCROLLING) {
1212 // TODO
1213 } else {
1214 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
Adrian Roos4a579672016-05-24 16:54:37 -07001215
Gus Prevas0fa58d62019-01-11 13:58:40 -05001216 // Only apply the scroll if we're scrolling the view upwards, or the view is so
1217 // far up that it is not visible anymore.
1218 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1219 setOwnScrollY(targetScroll);
1220 }
Adrian Roos181385c2016-05-05 17:45:44 -04001221 }
1222 }
1223 }
1224
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001225 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek319bdc42014-05-01 23:01:58 +02001226 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +02001227 if (!mChildrenUpdateRequested) {
1228 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
1229 mChildrenUpdateRequested = true;
1230 invalidate();
1231 }
Selim Cinek319bdc42014-05-01 23:01:58 +02001232 }
1233
Robert Snoeberger168949a2019-04-18 09:39:42 -04001234 /**
1235 * Returns best effort count of visible notifications.
1236 */
1237 public int getVisibleNotificationCount() {
1238 int count = 0;
1239 for (int i = 0; i < getChildCount(); i++) {
1240 final View child = getChildAt(i);
1241 if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
1242 count++;
1243 }
1244 }
1245 return count;
1246 }
1247
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001248 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001249 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +02001250 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +01001251 }
1252
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001253 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekf7a14c02014-07-07 14:01:46 +02001254 private void clampScrollPosition() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001255 if (ANCHOR_SCROLLING) {
1256 // TODO
1257 } else {
1258 int scrollRange = getScrollRange();
1259 if (scrollRange < mOwnScrollY) {
1260 setOwnScrollY(scrollRange);
1261 }
Selim Cinek67b22602014-03-10 15:40:16 +01001262 }
1263 }
1264
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001265 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001266 public int getTopPadding() {
1267 return mTopPadding;
1268 }
1269
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001270 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001271 private void setTopPadding(int topPadding, boolean animate) {
Lucas Dupin00be88f2019-01-03 17:50:52 -08001272 if (mTopPadding != topPadding) {
1273 mTopPadding = topPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001274 updateAlgorithmHeightAndPadding();
1275 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +02001276 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001277 mTopPaddingNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04001278 mNeedsAnimation = true;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001279 }
Selim Cinek319bdc42014-05-01 23:01:58 +02001280 requestChildrenUpdate();
Lucas Dupin60661a62018-04-12 10:50:13 -07001281 notifyHeightChangeListener(null, animate);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001282 }
1283 }
1284
1285 /**
Selim Cinekbc243a92016-09-27 16:35:13 -07001286 * Update the height of the panel.
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001287 *
Selim Cinekbc243a92016-09-27 16:35:13 -07001288 * @param height the expanded height of the panel
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001289 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001290 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekbc243a92016-09-27 16:35:13 -07001291 public void setExpandedHeight(float height) {
1292 mExpandedHeight = height;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001293 setIsExpanded(height > 0);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001294 int minExpansionHeight = getMinExpansionHeight();
1295 if (height < minExpansionHeight) {
1296 mClipRect.left = 0;
1297 mClipRect.right = getWidth();
1298 mClipRect.top = 0;
1299 mClipRect.bottom = (int) height;
1300 height = minExpansionHeight;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001301 setRequestedClipBounds(mClipRect);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001302 } else {
Selim Cinekcafa87f2016-10-26 17:00:17 -07001303 setRequestedClipBounds(null);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001304 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001305 int stackHeight;
Selim Cinek94c2d822016-07-13 18:50:04 -07001306 float translationY;
1307 float appearEndPosition = getAppearEndPosition();
1308 float appearStartPosition = getAppearStartPosition();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001309 float appearFraction = 1.0f;
shawnlin5be1f7c2018-05-21 20:50:54 +08001310 boolean appearing = height < appearEndPosition;
1311 mAmbientState.setAppearing(appearing);
1312 if (!appearing) {
Selim Cinekbc243a92016-09-27 16:35:13 -07001313 translationY = 0;
shawnlin8e4e92c2018-04-12 18:47:24 +08001314 if (mShouldShowShelfOnly) {
1315 stackHeight = mTopPadding + mShelf.getIntrinsicHeight();
1316 } else if (mQsExpanded) {
1317 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding;
1318 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
1319 if (stackStartPosition <= stackEndPosition) {
1320 stackHeight = stackEndPosition;
1321 } else {
1322 stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
1323 stackEndPosition, mQsExpansionFraction);
1324 }
1325 } else {
1326 stackHeight = (int) height;
1327 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001328 } else {
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001329 appearFraction = calculateAppearFraction(height);
Selim Cinek94c2d822016-07-13 18:50:04 -07001330 if (appearFraction >= 0) {
1331 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
1332 appearFraction);
1333 } else {
1334 // This may happen when pushing up a heads up. We linearly push it up from the
1335 // start
1336 translationY = height - appearStartPosition + getExpandTranslationStart();
1337 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001338 if (isHeadsUpTransition()) {
Gus Prevase2d6f042018-10-17 15:25:30 -04001339 stackHeight =
1340 getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001341 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
1342 } else {
1343 stackHeight = (int) (height - translationY);
1344 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001345 }
1346 if (stackHeight != mCurrentStackHeight) {
1347 mCurrentStackHeight = stackHeight;
1348 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +02001349 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001350 }
Selim Cinek94c2d822016-07-13 18:50:04 -07001351 setStackTranslation(translationY);
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001352 notifyAppearChangedListeners();
1353 }
1354
1355 private void notifyAppearChangedListeners() {
1356 float appear;
1357 float expandAmount;
1358 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
1359 appear = calculateAppearFractionBypass();
1360 expandAmount = getPulseHeight();
1361 } else {
1362 appear = MathUtils.saturate(calculateAppearFraction(mExpandedHeight));
1363 expandAmount = mExpandedHeight;
1364 }
1365 if (appear != mLastSentAppear || expandAmount != mLastSentExpandedHeight) {
1366 mLastSentAppear = appear;
1367 mLastSentExpandedHeight = expandAmount;
1368 for (int i = 0; i < mExpandedHeightListeners.size(); i++) {
1369 BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i);
1370 listener.accept(expandAmount, appear);
1371 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001372 }
Selim Cinekcafa87f2016-10-26 17:00:17 -07001373 }
1374
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001375 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekcafa87f2016-10-26 17:00:17 -07001376 private void setRequestedClipBounds(Rect clipRect) {
1377 mRequestedClipBounds = clipRect;
1378 updateClipping();
1379 }
1380
Lucas Dupin60661a62018-04-12 10:50:13 -07001381 /**
1382 * Return the height of the content ignoring the footer.
1383 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001384 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Lucas Dupin60661a62018-04-12 10:50:13 -07001385 public int getIntrinsicContentHeight() {
1386 return mIntrinsicContentHeight;
1387 }
1388
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001389 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcafa87f2016-10-26 17:00:17 -07001390 public void updateClipping() {
1391 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
1392 && !mHeadsUpAnimatingAway;
Lucas Dupin64e2f572019-03-21 14:21:14 -07001393 boolean clipToOutline = false;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001394 if (mIsClipped != clipped) {
1395 mIsClipped = clipped;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001396 }
Lucas Dupin16cfe452018-02-08 13:14:50 -08001397
Selim Cinek195dfc52019-05-30 19:35:05 -07001398 if (mAmbientState.isHiddenAtAll()) {
Lucas Dupin64e2f572019-03-21 14:21:14 -07001399 clipToOutline = true;
1400 invalidateOutline();
Selim Cinek195dfc52019-05-30 19:35:05 -07001401 if (isFullyHidden()) {
1402 setClipBounds(null);
1403 }
Lucas Dupin16cfe452018-02-08 13:14:50 -08001404 } else if (clipped) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07001405 setClipBounds(mRequestedClipBounds);
1406 } else {
1407 setClipBounds(null);
1408 }
Lucas Dupin64e2f572019-03-21 14:21:14 -07001409
1410 setClipToOutline(clipToOutline);
Selim Cinek94c2d822016-07-13 18:50:04 -07001411 }
1412
1413 /**
1414 * @return The translation at the beginning when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001415 * Measured relative to the resting position.
Selim Cinek94c2d822016-07-13 18:50:04 -07001416 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001417 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001418 private float getExpandTranslationStart() {
Selim Cinek083f2142018-11-06 16:32:23 -08001419 return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07001420 }
1421
1422 /**
1423 * @return the position from where the appear transition starts when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001424 * Measured in absolute height.
Selim Cinek94c2d822016-07-13 18:50:04 -07001425 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001426 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001427 private float getAppearStartPosition() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001428 if (isHeadsUpTransition()) {
Gus Prevase2d6f042018-10-17 15:25:30 -04001429 return mHeadsUpInset
1430 + getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
Selim Cinekd127d792016-11-01 19:11:41 -07001431 }
Selim Cinek48ff9b42016-11-09 19:31:51 -08001432 return getMinExpansionHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07001433 }
1434
1435 /**
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001436 * @return the height of the top heads up notification when pinned. This is different from the
Jason Monke59dc402018-08-16 12:05:01 -04001437 * intrinsic height, which also includes whether the notification is system expanded and
1438 * is mainly used when dragging down from a heads up notification.
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001439 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001440 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001441 private int getTopHeadsUpPinnedHeight() {
Ned Burnsf81c4c42019-01-07 14:10:43 -05001442 NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001443 if (topEntry == null) {
1444 return 0;
1445 }
Evan Laird94492852018-10-25 13:43:01 -04001446 ExpandableNotificationRow row = topEntry.getRow();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001447 if (row.isChildInGroup()) {
Ned Burnsf81c4c42019-01-07 14:10:43 -05001448 final NotificationEntry groupSummary
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001449 = mGroupManager.getGroupSummary(row.getStatusBarNotification());
1450 if (groupSummary != null) {
Evan Laird94492852018-10-25 13:43:01 -04001451 row = groupSummary.getRow();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001452 }
1453 }
1454 return row.getPinnedHeadsUpHeight();
1455 }
1456
1457 /**
Selim Cinek94c2d822016-07-13 18:50:04 -07001458 * @return the position from where the appear transition ends when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001459 * Measured in absolute height.
Selim Cinek94c2d822016-07-13 18:50:04 -07001460 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001461 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001462 private float getAppearEndPosition() {
Selim Cinekaa417da2016-10-27 18:17:08 -07001463 int appearPosition;
Selim Cinek66440cf2017-05-26 13:48:47 -07001464 int notGoneChildCount = getNotGoneChildCount();
Julia Reynolds34f14962018-05-03 12:40:20 +00001465 if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001466 if (isHeadsUpTransition()
Selim Cinekc1d9ab22019-05-21 18:08:30 -07001467 || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDozing())) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001468 appearPosition = getTopHeadsUpPinnedHeight();
Selim Cinekcde90e52016-12-22 21:01:49 +01001469 } else {
1470 appearPosition = 0;
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001471 if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) {
1472 appearPosition += mShelf.getIntrinsicHeight();
1473 }
Selim Cinekcde90e52016-12-22 21:01:49 +01001474 }
Selim Cinekaa417da2016-10-27 18:17:08 -07001475 } else {
Selim Cinekcde90e52016-12-22 21:01:49 +01001476 appearPosition = mEmptyShadeView.getHeight();
Selim Cinekaa417da2016-10-27 18:17:08 -07001477 }
1478 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
Selim Cinek94c2d822016-07-13 18:50:04 -07001479 }
1480
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001481 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001482 private boolean isHeadsUpTransition() {
Gus Prevase2d6f042018-10-17 15:25:30 -04001483 NotificationSection firstVisibleSection = getFirstVisibleSection();
1484 return mTrackingHeadsUp && firstVisibleSection != null
Selim Cinek459aee32019-02-20 11:18:56 -08001485 && firstVisibleSection.getFirstVisibleChild().isAboveShelf();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001486 }
1487
Selim Cinek94c2d822016-07-13 18:50:04 -07001488 /**
1489 * @param height the height of the panel
1490 * @return the fraction of the appear animation that has been performed
1491 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001492 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001493 public float calculateAppearFraction(float height) {
Selim Cinek94c2d822016-07-13 18:50:04 -07001494 float appearEndPosition = getAppearEndPosition();
1495 float appearStartPosition = getAppearStartPosition();
1496 return (height - appearStartPosition)
1497 / (appearEndPosition - appearStartPosition);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001498 }
1499
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001500 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd2281152015-04-10 14:37:46 -07001501 public float getStackTranslation() {
1502 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001503 }
1504
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001505 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd2281152015-04-10 14:37:46 -07001506 private void setStackTranslation(float stackTranslation) {
1507 if (stackTranslation != mStackTranslation) {
1508 mStackTranslation = stackTranslation;
1509 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001510 requestChildrenUpdate();
1511 }
Selim Cinek67b22602014-03-10 15:40:16 +01001512 }
1513
1514 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001515 * 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 +01001516 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
1517 *
1518 * @return either the layout height or the externally defined height, whichever is smaller
1519 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001520 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek343e6e22014-04-11 21:23:30 +02001521 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001522 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
1523 }
1524
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001525 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek816c8e42015-11-19 12:00:45 -08001526 public int getFirstItemMinHeight() {
1527 final ExpandableView firstChild = getFirstChildNotGone();
1528 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001529 }
1530
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001531 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Jason Monk16ac3772016-02-10 15:39:21 -05001532 public void setQsContainer(ViewGroup qsContainer) {
1533 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +02001534 }
1535
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001536 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek684a4422015-04-15 16:18:39 -07001537 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001538 if (v instanceof ExpandableNotificationRow) {
1539 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -07001540 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -07001541 }
1542 return false;
1543 }
1544
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001545 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cineka59ecc32015-04-07 10:51:49 -07001546 private boolean isHeadsUp(View v) {
1547 if (v instanceof ExpandableNotificationRow) {
1548 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1549 return row.isHeadsUp();
1550 }
1551 return false;
1552 }
1553
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001554 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001555 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
1556 getLocationOnScreen(mTempInt2);
1557 float localTouchY = touchY - mTempInt2[1];
1558
1559 ExpandableView closestChild = null;
1560 float minDist = Float.MAX_VALUE;
1561
1562 // find the view closest to the location, accounting for GONE views
1563 final int count = getChildCount();
1564 for (int childIdx = 0; childIdx < count; childIdx++) {
1565 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1566 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -08001567 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001568 continue;
1569 }
1570 float childTop = slidingChild.getTranslationY();
1571 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001572 float bottom = childTop + slidingChild.getActualHeight()
1573 - slidingChild.getClipBottomAmount();
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001574
1575 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
1576 if (dist < minDist) {
1577 closestChild = slidingChild;
1578 minDist = dist;
1579 }
1580 }
1581 return closestChild;
1582 }
1583
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001584 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1585 private ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek34ed7c02017-09-08 15:03:12 -07001586 return getChildAtPosition(touchX, touchY, true /* requireMinHeight */);
1587
1588 }
1589
1590 /**
1591 * Get the child at a certain screen location.
1592 *
Jason Monke59dc402018-08-16 12:05:01 -04001593 * @param touchX the x coordinate
1594 * @param touchY the y coordinate
Selim Cinek34ed7c02017-09-08 15:03:12 -07001595 * @param requireMinHeight Whether a minimum height is required for a child to be returned.
1596 * @return the child at the given location.
1597 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001598 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek34ed7c02017-09-08 15:03:12 -07001599 private ExpandableView getChildAtPosition(float touchX, float touchY,
1600 boolean requireMinHeight) {
Selim Cinek67b22602014-03-10 15:40:16 +01001601 // find the view under the pointer, accounting for GONE views
1602 final int count = getChildCount();
1603 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001604 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek51d21972017-07-19 17:39:20 -07001605 if (slidingChild.getVisibility() != VISIBLE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -08001606 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +01001607 continue;
1608 }
Selim Cinek89faff12014-06-19 16:29:04 -07001609 float childTop = slidingChild.getTranslationY();
1610 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001611 float bottom = childTop + slidingChild.getActualHeight()
1612 - slidingChild.getClipBottomAmount();
Jorim Jaggi28f0e592014-08-05 22:03:07 +02001613
1614 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
1615 // camera affordance).
1616 int left = 0;
1617 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +01001618
Selim Cinek34ed7c02017-09-08 15:03:12 -07001619 if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
Selim Cinek51d21972017-07-19 17:39:20 -07001620 && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001621 if (slidingChild instanceof ExpandableNotificationRow) {
1622 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Ned Burnsf81c4c42019-01-07 14:10:43 -05001623 NotificationEntry entry = row.getEntry();
Selim Cinek131c1e22015-05-11 19:04:49 -07001624 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Evan Laird94492852018-10-25 13:43:01 -04001625 && mHeadsUpManager.getTopEntry().getRow() != row
Selim Cinek5bc852a2015-12-21 12:19:09 -08001626 && mGroupManager.getGroupSummary(
Evan Laird94492852018-10-25 13:43:01 -04001627 mHeadsUpManager.getTopEntry().notification)
1628 != entry) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001629 continue;
1630 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001631 return row.getViewAtPosition(touchY - childTop);
1632 }
Selim Cinek67b22602014-03-10 15:40:16 +01001633 return slidingChild;
1634 }
1635 }
1636 return null;
1637 }
1638
Selim Cinek3d6ae232019-01-04 14:14:33 -08001639 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001640 getLocationOnScreen(mTempInt2);
1641 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001642 }
1643
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001644 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001645 public void setScrollingEnabled(boolean enable) {
1646 mScrollingEnabled = enable;
1647 }
1648
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001649 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04001650 public void lockScrollTo(View v) {
1651 if (mForcedScroll == v) {
1652 return;
1653 }
1654 mForcedScroll = v;
1655 scrollTo(v);
1656 }
1657
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001658 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ricky Waicd35def2016-05-03 11:07:07 +01001659 public boolean scrollTo(View v) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001660 ExpandableView expandableView = (ExpandableView) v;
Gus Prevas0fa58d62019-01-11 13:58:40 -05001661 if (ANCHOR_SCROLLING) {
1662 // TODO
1663 } else {
1664 int positionInLinearLayout = getPositionInLinearLayout(v);
1665 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1666 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Ricky Waicd35def2016-05-03 11:07:07 +01001667
Gus Prevas0fa58d62019-01-11 13:58:40 -05001668 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1669 // that it is not visible anymore.
1670 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1671 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1672 mDontReportNextOverScroll = true;
1673 animateScroll();
1674 return true;
1675 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001676 }
Ricky Waicd35def2016-05-03 11:07:07 +01001677 return false;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001678 }
1679
Adrian Roos181385c2016-05-05 17:45:44 -04001680 /**
1681 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
Jason Monke59dc402018-08-16 12:05:01 -04001682 * the IME.
Adrian Roos181385c2016-05-05 17:45:44 -04001683 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001684 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Adrian Roos181385c2016-05-05 17:45:44 -04001685 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1686 return positionInLinearLayout + v.getIntrinsicHeight() +
felkachang529bfe62018-07-04 12:51:44 +08001687 getImeInset() - getHeight()
1688 + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
Adrian Roos181385c2016-05-05 17:45:44 -04001689 }
1690
Adrian Roos5153d4a2016-03-22 10:01:56 -07001691 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001692 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Adrian Roos5153d4a2016-03-22 10:01:56 -07001693 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Selim Cineka424c502016-04-05 13:07:54 -07001694 mBottomInset = insets.getSystemWindowInsetBottom();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001695
Gus Prevas0fa58d62019-01-11 13:58:40 -05001696 if (ANCHOR_SCROLLING) {
1697 // TODO
1698 } else {
1699 int range = getScrollRange();
1700 if (mOwnScrollY > range) {
1701 // HACK: We're repeatedly getting staggered insets here while the IME is
1702 // animating away. To work around that we'll wait until things have settled.
1703 removeCallbacks(mReclamp);
1704 postDelayed(mReclamp, 50);
1705 } else if (mForcedScroll != null) {
1706 // The scroll was requested before we got the actual inset - in case we need
1707 // to scroll up some more do so now.
1708 scrollTo(mForcedScroll);
1709 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001710 }
1711 return insets;
1712 }
1713
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001714 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Adrian Roos5153d4a2016-03-22 10:01:56 -07001715 private Runnable mReclamp = new Runnable() {
1716 @Override
1717 public void run() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001718 if (ANCHOR_SCROLLING) {
1719 // TODO
1720 } else {
1721 int range = getScrollRange();
1722 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1723 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001724 mDontReportNextOverScroll = true;
1725 mDontClampNextScroll = true;
Selim Cinek9212de82017-02-06 16:04:28 -08001726 animateScroll();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001727 }
1728 };
1729
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001730 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1731 public void setExpandingEnabled(boolean enable) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001732 mExpandHelper.setEnabled(enable);
1733 }
1734
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001735 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001736 private boolean isScrollingEnabled() {
1737 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +01001738 }
1739
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001740 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbc243a92016-09-27 16:35:13 -07001741 private boolean onKeyguard() {
Selim Cinek355652a2016-12-07 13:32:12 -08001742 return mStatusBarState == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +02001743 }
1744
Selim Cinek67b22602014-03-10 15:40:16 +01001745 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001746 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001747 protected void onConfigurationChanged(Configuration newConfig) {
1748 super.onConfigurationChanged(newConfig);
Adrian Roos22af6502018-02-22 16:57:08 +01001749 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
Selim Cinek67b22602014-03-10 15:40:16 +01001750 float densityScale = getResources().getDisplayMetrics().density;
1751 mSwipeHelper.setDensityScale(densityScale);
1752 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1753 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1754 initView(getContext());
1755 }
1756
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001757 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dan Sandlereceda3d2014-07-21 15:35:01 -04001758 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07001759 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1760 true /* isDismissAll */);
Selim Cinek67b22602014-03-10 15:40:16 +01001761 }
1762
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001763 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001764 private void snapViewIfNeeded(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04001765 ExpandableNotificationRow child = entry.getRow();
dongwan0605.kim30637e42016-03-02 17:16:47 +09001766 boolean animate = mIsExpanded || isPinnedHeadsUp(child);
Mady Mellor95d743c2017-01-10 12:05:27 -08001767 // If the child is showing the notification menu snap to that
Evan Lairde55c6012019-03-13 12:54:37 -04001768 if (child.getProvider() != null) {
1769 float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
1770 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
1771 }
dongwan0605.kim30637e42016-03-02 17:16:47 +09001772 }
1773
Selim Cinek67b22602014-03-10 15:40:16 +01001774 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001775 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001776 public ViewGroup getViewParentForNotification(NotificationEntry entry) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001777 return this;
1778 }
1779
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001780 /**
1781 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1782 *
1783 * @param deltaY The amount to scroll upwards, has to be positive.
1784 * @return The amount of scrolling to be performed by the scroller,
Jason Monke59dc402018-08-16 12:05:01 -04001785 * not handled by the overScroll amount.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001786 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001787 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001788 private float overScrollUp(int deltaY, int range) {
1789 deltaY = Math.max(deltaY, 0);
1790 float currentTopAmount = getCurrentOverScrollAmount(true);
1791 float newTopAmount = currentTopAmount - deltaY;
1792 if (currentTopAmount > 0) {
1793 setOverScrollAmount(newTopAmount, true /* onTop */,
1794 false /* animate */);
1795 }
1796 // Top overScroll might not grab all scrolling motion,
1797 // we have to scroll as well.
Gus Prevas0fa58d62019-01-11 13:58:40 -05001798 if (ANCHOR_SCROLLING) {
1799 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1800 // TODO: once we're recycling this will need to check the adapter position of the child
1801 ExpandableView lastRow = getLastRowNotGone();
Gus Prevascdc98342019-01-14 14:29:44 -05001802 if (lastRow != null && !lastRow.isInShelf()) {
1803 float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
1804 if (scrollAmount > distanceToMax) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001805 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1806 // We overScroll on the bottom
1807 setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
1808 false /* onTop */,
1809 false /* animate */);
1810 mScrollAnchorViewY -= distanceToMax;
1811 scrollAmount = 0f;
1812 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001813 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001814 return scrollAmount;
1815 } else {
1816 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1817 float newScrollY = mOwnScrollY + scrollAmount;
1818 if (newScrollY > range) {
1819 if (!mExpandedInThisMotion) {
1820 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1821 // We overScroll on the bottom
1822 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1823 false /* onTop */,
1824 false /* animate */);
1825 }
1826 setOwnScrollY(range);
1827 scrollAmount = 0.0f;
1828 }
1829 return scrollAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001830 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001831 }
1832
1833 /**
1834 * Perform a scroll downward and adapt the overscroll amounts accordingly
1835 *
1836 * @param deltaY The amount to scroll downwards, has to be negative.
1837 * @return The amount of scrolling to be performed by the scroller,
Jason Monke59dc402018-08-16 12:05:01 -04001838 * not handled by the overScroll amount.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001839 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001840 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001841 private float overScrollDown(int deltaY) {
1842 deltaY = Math.min(deltaY, 0);
1843 float currentBottomAmount = getCurrentOverScrollAmount(false);
1844 float newBottomAmount = currentBottomAmount + deltaY;
1845 if (currentBottomAmount > 0) {
1846 setOverScrollAmount(newBottomAmount, false /* onTop */,
1847 false /* animate */);
1848 }
1849 // Bottom overScroll might not grab all scrolling motion,
1850 // we have to scroll as well.
Gus Prevas0fa58d62019-01-11 13:58:40 -05001851 if (ANCHOR_SCROLLING) {
1852 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1853 // TODO: once we're recycling this will need to check the adapter position of the child
1854 ExpandableView firstChild = getFirstChildNotGone();
1855 float top = firstChild.getTranslationY();
1856 float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
1857 if (distanceToTop < -scrollAmount) {
1858 float currentTopPixels = getCurrentOverScrolledPixels(true);
1859 // We overScroll on the top
1860 setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
1861 true /* onTop */,
1862 false /* animate */);
1863 mScrollAnchorView = firstChild;
1864 mScrollAnchorViewY = 0;
1865 scrollAmount = 0f;
1866 }
1867 return scrollAmount;
1868 } else {
1869 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1870 float newScrollY = mOwnScrollY + scrollAmount;
1871 if (newScrollY < 0) {
1872 float currentTopPixels = getCurrentOverScrolledPixels(true);
1873 // We overScroll on the top
1874 setOverScrolledPixels(currentTopPixels - newScrollY,
1875 true /* onTop */,
1876 false /* animate */);
1877 setOwnScrollY(0);
1878 scrollAmount = 0.0f;
1879 }
1880 return scrollAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001881 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001882 }
1883
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001884 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001885 private void initVelocityTrackerIfNotExists() {
1886 if (mVelocityTracker == null) {
1887 mVelocityTracker = VelocityTracker.obtain();
1888 }
1889 }
1890
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001891 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001892 private void recycleVelocityTracker() {
1893 if (mVelocityTracker != null) {
1894 mVelocityTracker.recycle();
1895 mVelocityTracker = null;
1896 }
1897 }
1898
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001899 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001900 private void initOrResetVelocityTracker() {
1901 if (mVelocityTracker == null) {
1902 mVelocityTracker = VelocityTracker.obtain();
1903 } else {
1904 mVelocityTracker.clear();
1905 }
1906 }
1907
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001908 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ricky Waicd35def2016-05-03 11:07:07 +01001909 public void setFinishScrollingCallback(Runnable runnable) {
1910 mFinishScrollingCallback = runnable;
1911 }
1912
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001913 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek9212de82017-02-06 16:04:28 -08001914 private void animateScroll() {
Selim Cinek67b22602014-03-10 15:40:16 +01001915 if (mScroller.computeScrollOffset()) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001916 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05001917 int oldY = mLastScrollerY;
1918 int y = mScroller.getCurrY();
1919 int deltaY = y - oldY;
1920 if (deltaY != 0) {
1921 int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
1922 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
1923 if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
1924 || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
1925 // This frame takes us into overscroll, so set the max overscroll based on
1926 // the current velocity
1927 setMaxOverScrollFromCurrentVelocity();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001928 }
Gus Prevascdc98342019-01-14 14:29:44 -05001929 customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
1930 mLastScrollerY = y;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001931 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001932 } else {
1933 int oldY = mOwnScrollY;
1934 int y = mScroller.getCurrY();
Selim Cinek67b22602014-03-10 15:40:16 +01001935
Gus Prevas0fa58d62019-01-11 13:58:40 -05001936 if (oldY != y) {
1937 int range = getScrollRange();
1938 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
Gus Prevascdc98342019-01-14 14:29:44 -05001939 // This frame takes us into overscroll, so set the max overscroll based on
1940 // the current velocity
1941 setMaxOverScrollFromCurrentVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +01001942 }
Selim Cinek67b22602014-03-10 15:40:16 +01001943
Gus Prevas0fa58d62019-01-11 13:58:40 -05001944 if (mDontClampNextScroll) {
1945 range = Math.max(range, oldY);
1946 }
1947 customOverScrollBy(y - oldY, oldY, range,
1948 (int) (mMaxOverScroll));
Adrian Roos5153d4a2016-03-22 10:01:56 -07001949 }
Selim Cinek67b22602014-03-10 15:40:16 +01001950 }
1951
Gus Prevascdc98342019-01-14 14:29:44 -05001952 postOnAnimation(mReflingAndAnimateScroll);
Adrian Roos5153d4a2016-03-22 10:01:56 -07001953 } else {
1954 mDontClampNextScroll = false;
Ricky Waicd35def2016-05-03 11:07:07 +01001955 if (mFinishScrollingCallback != null) {
1956 mFinishScrollingCallback.run();
1957 }
Selim Cinek67b22602014-03-10 15:40:16 +01001958 }
1959 }
1960
Gus Prevascdc98342019-01-14 14:29:44 -05001961 private void setMaxOverScrollFromCurrentVelocity() {
1962 float currVelocity = mScroller.getCurrVelocity();
1963 if (currVelocity >= mMinimumVelocity) {
1964 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
Selim Cinek4195dd02014-05-19 18:16:14 +02001965 }
Gus Prevascdc98342019-01-14 14:29:44 -05001966 }
Selim Cinek4195dd02014-05-19 18:16:14 +02001967
Gus Prevascdc98342019-01-14 14:29:44 -05001968 /**
1969 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
1970 * would cause us to exceed the provided maximum overscroll, springs back instead.
1971 *
1972 * This method performs the determination of whether we're exceeding the overscroll and clamps
1973 * the scroll amount if so. The actual scrolling/overscrolling happens in
1974 * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
1975 * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
1976 *
1977 * @param deltaY The (signed) number of pixels to scroll.
1978 * @param scrollY The current scroll position (absolute scrolling only).
1979 * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
1980 * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
1981 */
Selim Cinek4195dd02014-05-19 18:16:14 +02001982 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Gus Prevascdc98342019-01-14 14:29:44 -05001983 private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001984 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05001985 boolean clampedY = false;
1986 if (deltaY < 0) {
1987 int maxScrollAmount = getMaxNegativeScrollAmount();
1988 if (maxScrollAmount > Integer.MIN_VALUE) {
1989 maxScrollAmount -= maxOverScrollY;
1990 if (deltaY < maxScrollAmount) {
1991 deltaY = maxScrollAmount;
1992 clampedY = true;
1993 }
1994 }
1995 } else {
1996 int maxScrollAmount = getMaxPositiveScrollAmount();
1997 if (maxScrollAmount < Integer.MAX_VALUE) {
1998 maxScrollAmount += maxOverScrollY;
1999 if (deltaY > maxScrollAmount) {
2000 deltaY = maxScrollAmount;
2001 clampedY = true;
2002 }
2003 }
2004 }
2005 onCustomOverScrolledBy(deltaY, clampedY);
Gus Prevas0fa58d62019-01-11 13:58:40 -05002006 } else {
2007 int newScrollY = scrollY + deltaY;
2008 final int top = -maxOverScrollY;
2009 final int bottom = maxOverScrollY + scrollRangeY;
Selim Cinek4195dd02014-05-19 18:16:14 +02002010
Gus Prevas0fa58d62019-01-11 13:58:40 -05002011 boolean clampedY = false;
2012 if (newScrollY > bottom) {
2013 newScrollY = bottom;
2014 clampedY = true;
2015 } else if (newScrollY < top) {
2016 newScrollY = top;
2017 clampedY = true;
2018 }
Selim Cinek4195dd02014-05-19 18:16:14 +02002019
Gus Prevas0fa58d62019-01-11 13:58:40 -05002020 onCustomOverScrolled(newScrollY, clampedY);
Selim Cinek4195dd02014-05-19 18:16:14 +02002021 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002022 }
2023
2024 /**
2025 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
2026 * overscroll effect based on numPixels. By default this will also cancel animations on the
2027 * same overScroll edge.
2028 *
2029 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
2030 * the rubber-banding logic.
Jason Monke59dc402018-08-16 12:05:01 -04002031 * @param onTop Should the effect be applied on top of the scroller.
2032 * @param animate Should an animation be performed.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002033 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002034 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002035 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07002036 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002037 }
2038
2039 /**
2040 * Set the effective overScroll amount which will be directly reflected in the layout.
2041 * By default this will also cancel animations on the same overScroll edge.
2042 *
Jason Monke59dc402018-08-16 12:05:01 -04002043 * @param amount The amount to overScroll by.
2044 * @param onTop Should the effect be applied on top of the scroller.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002045 * @param animate Should an animation be performed.
2046 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002047
2048 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002049 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
2050 setOverScrollAmount(amount, onTop, animate, true);
2051 }
2052
2053 /**
2054 * Set the effective overScroll amount which will be directly reflected in the layout.
2055 *
Jason Monke59dc402018-08-16 12:05:01 -04002056 * @param amount The amount to overScroll by.
2057 * @param onTop Should the effect be applied on top of the scroller.
2058 * @param animate Should an animation be performed.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002059 * @param cancelAnimators Should running animations be cancelled.
2060 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002061 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002062 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2063 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002064 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
2065 }
2066
2067 /**
2068 * Set the effective overScroll amount which will be directly reflected in the layout.
2069 *
Jason Monke59dc402018-08-16 12:05:01 -04002070 * @param amount The amount to overScroll by.
2071 * @param onTop Should the effect be applied on top of the scroller.
2072 * @param animate Should an animation be performed.
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002073 * @param cancelAnimators Should running animations be cancelled.
Jason Monke59dc402018-08-16 12:05:01 -04002074 * @param isRubberbanded The value which will be passed to
2075 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002076 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002077 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002078 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2079 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002080 if (cancelAnimators) {
2081 mStateAnimator.cancelOverScrollAnimators(onTop);
2082 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002083 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002084 }
2085
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002086 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002087 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
2088 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002089 amount = Math.max(0, amount);
2090 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002091 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002092 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07002093 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002094 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002095 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002096 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002097 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002098 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002099 }
2100 }
2101
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002102 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002103 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002104 mExpandHelper.onlyObserveMovements(amount > 1.0f);
2105 if (mDontReportNextOverScroll) {
2106 mDontReportNextOverScroll = false;
2107 return;
2108 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02002109 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002110 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002111 }
2112 }
2113
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002114 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi290600a2014-05-30 17:02:20 +02002115 public void setOverscrollTopChangedListener(
2116 OnOverscrollTopChangedListener overscrollTopChangedListener) {
2117 mOverscrollTopChangedListener = overscrollTopChangedListener;
2118 }
2119
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002120 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002121 public float getCurrentOverScrollAmount(boolean top) {
2122 return mAmbientState.getOverScrollAmount(top);
2123 }
2124
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002125 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002126 public float getCurrentOverScrolledPixels(boolean top) {
Jason Monke59dc402018-08-16 12:05:01 -04002127 return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002128 }
2129
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002130 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002131 private void setOverScrolledPixels(float amount, boolean onTop) {
2132 if (onTop) {
2133 mOverScrolledTopPixels = amount;
2134 } else {
2135 mOverScrolledBottomPixels = amount;
2136 }
2137 }
2138
Gus Prevascdc98342019-01-14 14:29:44 -05002139 /**
2140 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
2141 * would cause us to exceed the provided maximum overscroll, springs back instead.
2142 *
2143 * @param deltaY The (signed) number of pixels to scroll.
2144 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2145 * the overscroll limit.
2146 */
2147 private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
2148 assert ANCHOR_SCROLLING;
Gus Prevas0fa58d62019-01-11 13:58:40 -05002149 mScrollAnchorViewY -= deltaY;
2150 // Treat animating scrolls differently; see #computeScroll() for why.
2151 if (!mScroller.isFinished()) {
Gus Prevascdc98342019-01-14 14:29:44 -05002152 if (clampedY) {
2153 springBack();
2154 } else {
2155 float overScrollTop = getCurrentOverScrollAmount(true /* top */);
2156 if (isScrolledToTop() && mScrollAnchorViewY > 0) {
2157 notifyOverscrollTopListener(mScrollAnchorViewY,
2158 isRubberbanded(true /* onTop */));
2159 } else {
2160 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
2161 }
2162 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002163 }
2164 updateScrollAnchor();
2165 updateOnScrollChange();
2166 }
2167
Gus Prevascdc98342019-01-14 14:29:44 -05002168 /**
2169 * Scrolls to the given position, overscrolling if needed. If called during a fling and the
2170 * position exceeds the provided maximum overscroll, springs back instead.
2171 *
2172 * @param scrollY The target scroll position.
2173 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2174 * the overscroll limit.
2175 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002176 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek9212de82017-02-06 16:04:28 -08002177 private void onCustomOverScrolled(int scrollY, boolean clampedY) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002178 assert !ANCHOR_SCROLLING;
Selim Cinek67b22602014-03-10 15:40:16 +01002179 // Treat animating scrolls differently; see #computeScroll() for why.
2180 if (!mScroller.isFinished()) {
Selim Cinekef406062016-09-29 17:33:13 -07002181 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01002182 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002183 springBack();
2184 } else {
Jorim Jaggi290600a2014-05-30 17:02:20 +02002185 float overScrollTop = getCurrentOverScrollAmount(true);
2186 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002187 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02002188 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002189 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02002190 }
Selim Cinek67b22602014-03-10 15:40:16 +01002191 }
Selim Cinek67b22602014-03-10 15:40:16 +01002192 } else {
Selim Cinek9212de82017-02-06 16:04:28 -08002193 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01002194 }
2195 }
2196
Gus Prevascdc98342019-01-14 14:29:44 -05002197 /**
2198 * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
2199 * overscroll amount back to zero.
2200 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002201 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002202 private void springBack() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002203 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05002204 boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
2205 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
2206 boolean overscrolledBottom = maxPositiveScrollAmount < 0;
2207 if (overScrolledTop || overscrolledBottom) {
2208 float newAmount;
2209 if (overScrolledTop) {
2210 newAmount = mScrollAnchorViewY;
2211 mScrollAnchorViewY = 0;
2212 mDontReportNextOverScroll = true;
2213 } else {
2214 newAmount = -maxPositiveScrollAmount;
2215 mScrollAnchorViewY -= maxPositiveScrollAmount;
2216 }
2217 setOverScrollAmount(newAmount, overScrolledTop, false);
2218 setOverScrollAmount(0.0f, overScrolledTop, true);
2219 mScroller.forceFinished(true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002220 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002221 } else {
2222 int scrollRange = getScrollRange();
2223 boolean overScrolledTop = mOwnScrollY <= 0;
2224 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
2225 if (overScrolledTop || overScrolledBottom) {
2226 boolean onTop;
2227 float newAmount;
2228 if (overScrolledTop) {
2229 onTop = true;
2230 newAmount = -mOwnScrollY;
2231 setOwnScrollY(0);
2232 mDontReportNextOverScroll = true;
2233 } else {
2234 onTop = false;
2235 newAmount = mOwnScrollY - scrollRange;
2236 setOwnScrollY(scrollRange);
2237 }
2238 setOverScrollAmount(newAmount, onTop, false);
2239 setOverScrollAmount(0.0f, onTop, true);
2240 mScroller.forceFinished(true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002241 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002242 }
2243 }
2244
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002245 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek67b22602014-03-10 15:40:16 +01002246 private int getScrollRange() {
felkachang529bfe62018-07-04 12:51:44 +08002247 // In current design, it only use the top HUN to treat all of HUNs
2248 // although there are more than one HUNs
2249 int contentHeight = mContentHeight;
2250 if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
2251 contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
2252 }
2253 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
Selim Cineka424c502016-04-05 13:07:54 -07002254 int imeInset = getImeInset();
felkachang529bfe62018-07-04 12:51:44 +08002255 scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
Selim Cineka424c502016-04-05 13:07:54 -07002256 return scrollRange;
2257 }
2258
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002259 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cineka424c502016-04-05 13:07:54 -07002260 private int getImeInset() {
2261 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
Selim Cinek67b22602014-03-10 15:40:16 +01002262 }
2263
Selim Cinek343e6e22014-04-11 21:23:30 +02002264 /**
2265 * @return the first child which has visibility unequal to GONE
2266 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002267 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb55386d2015-12-16 17:26:49 -08002268 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02002269 int childCount = getChildCount();
2270 for (int i = 0; i < childCount; i++) {
2271 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002272 if (child.getVisibility() != View.GONE && child != mShelf) {
Selim Cinek816c8e42015-11-19 12:00:45 -08002273 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02002274 }
2275 }
2276 return null;
2277 }
2278
Selim Cinek4a1ac842014-05-01 15:51:58 +02002279 /**
Selim Cinek1b2a05e2016-04-28 14:20:39 -07002280 * @return the child before the given view which has visibility unequal to GONE
2281 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002282 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1b2a05e2016-04-28 14:20:39 -07002283 public ExpandableView getViewBeforeView(ExpandableView view) {
2284 ExpandableView previousView = null;
2285 int childCount = getChildCount();
2286 for (int i = 0; i < childCount; i++) {
2287 View child = getChildAt(i);
2288 if (child == view) {
2289 return previousView;
2290 }
2291 if (child.getVisibility() != View.GONE) {
2292 previousView = (ExpandableView) child;
2293 }
2294 }
2295 return null;
2296 }
2297
2298 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002299 * @return The first child which has visibility unequal to GONE which is currently below the
Jason Monke59dc402018-08-16 12:05:01 -04002300 * given translationY or equal to it.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002301 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002302 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekef8c2252017-02-10 14:52:18 -08002303 private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002304 int childCount = getChildCount();
2305 for (int i = 0; i < childCount; i++) {
2306 View child = getChildAt(i);
Selim Cinekef8c2252017-02-10 14:52:18 -08002307 if (child.getVisibility() == View.GONE) {
2308 continue;
2309 }
2310 float rowTranslation = child.getTranslationY();
2311 if (rowTranslation >= translationY) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002312 return child;
Selim Cinekef8c2252017-02-10 14:52:18 -08002313 } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
2314 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2315 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
2316 List<ExpandableNotificationRow> notificationChildren =
2317 row.getNotificationChildren();
2318 for (int childIndex = 0; childIndex < notificationChildren.size();
2319 childIndex++) {
2320 ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
2321 if (rowChild.getTranslationY() + rowTranslation >= translationY) {
2322 return rowChild;
2323 }
2324 }
2325 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002326 }
2327 }
2328 return null;
2329 }
2330
2331 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02002332 * @return the last child which has visibility unequal to GONE
2333 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002334 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002335 public ExpandableView getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02002336 int childCount = getChildCount();
2337 for (int i = childCount - 1; i >= 0; i--) {
2338 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002339 if (child.getVisibility() != View.GONE && child != mShelf) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002340 return (ExpandableView) child;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002341 }
2342 }
2343 return null;
2344 }
2345
Gus Prevas0fa58d62019-01-11 13:58:40 -05002346 private ExpandableNotificationRow getLastRowNotGone() {
2347 int childCount = getChildCount();
2348 for (int i = childCount - 1; i >= 0; i--) {
2349 View child = getChildAt(i);
2350 if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
2351 return (ExpandableNotificationRow) child;
2352 }
2353 }
2354 return null;
2355 }
2356
Jorim Jaggi069cd032014-05-15 03:09:01 +02002357 /**
2358 * @return the number of children which have visibility unequal to GONE
2359 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002360 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi069cd032014-05-15 03:09:01 +02002361 public int getNotGoneChildCount() {
2362 int childCount = getChildCount();
2363 int count = 0;
2364 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07002365 ExpandableView child = (ExpandableView) getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002366 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02002367 count++;
2368 }
2369 }
2370 return count;
2371 }
2372
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002373 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01002374 private void updateContentHeight() {
2375 int height = 0;
Selim Cineka7ed2c12017-01-23 20:47:24 -08002376 float previousPaddingRequest = mPaddingBetweenElements;
2377 float previousPaddingAmount = 0.0f;
Selim Cinekad7fac02016-10-18 17:09:15 -07002378 int numShownItems = 0;
2379 boolean finish = false;
Selim Cinek5040f2e2019-02-14 18:22:42 -08002380 int maxDisplayedNotifications = mMaxDisplayedNotifications;
Adrian Roos7a9551a2017-01-11 12:27:49 -08002381
Selim Cinek67b22602014-03-10 15:40:16 +01002382 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002383 ExpandableView expandableView = (ExpandableView) getChildAt(i);
Lucas Dupin60661a62018-04-12 10:50:13 -07002384 boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
Selim Cinek281c2022016-10-13 19:14:43 -07002385 if (expandableView.getVisibility() != View.GONE
Lucas Dupin60661a62018-04-12 10:50:13 -07002386 && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
Adrian Roos7d062c42017-03-30 15:11:43 -07002387 boolean limitReached = maxDisplayedNotifications != -1
2388 && numShownItems >= maxDisplayedNotifications;
Selim Cinek5040f2e2019-02-14 18:22:42 -08002389 if (limitReached) {
Selim Cinekad7fac02016-10-18 17:09:15 -07002390 expandableView = mShelf;
2391 finish = true;
2392 }
Selim Cinek42357e02016-02-24 18:48:01 -08002393 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Selim Cineka7ed2c12017-01-23 20:47:24 -08002394 float padding;
2395 if (increasedPaddingAmount >= 0.0f) {
2396 padding = (int) NotificationUtils.interpolate(
2397 previousPaddingRequest,
2398 mIncreasedPaddingBetweenElements,
2399 increasedPaddingAmount);
2400 previousPaddingRequest = (int) NotificationUtils.interpolate(
Selim Cinek42357e02016-02-24 18:48:01 -08002401 mPaddingBetweenElements,
2402 mIncreasedPaddingBetweenElements,
Selim Cineka7ed2c12017-01-23 20:47:24 -08002403 increasedPaddingAmount);
2404 } else {
2405 int ownPadding = (int) NotificationUtils.interpolate(
2406 0,
2407 mPaddingBetweenElements,
2408 1.0f + increasedPaddingAmount);
2409 if (previousPaddingAmount > 0.0f) {
2410 padding = (int) NotificationUtils.interpolate(
2411 ownPadding,
2412 mIncreasedPaddingBetweenElements,
2413 previousPaddingAmount);
2414 } else {
2415 padding = ownPadding;
2416 }
2417 previousPaddingRequest = ownPadding;
Jorim Jaggid4a57442014-04-10 02:45:55 +02002418 }
Selim Cineka7ed2c12017-01-23 20:47:24 -08002419 if (height != 0) {
2420 height += padding;
2421 }
2422 previousPaddingAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002423 height += expandableView.getIntrinsicHeight();
Selim Cinekad7fac02016-10-18 17:09:15 -07002424 numShownItems++;
2425 if (finish) {
2426 break;
2427 }
Selim Cinek67b22602014-03-10 15:40:16 +01002428 }
2429 }
Lucas Dupin60661a62018-04-12 10:50:13 -07002430 mIntrinsicContentHeight = height;
Selim Cinekf4b04ae2018-06-13 18:23:45 -07002431
Selim Cinekdb6d6df2019-05-22 20:39:59 -07002432 // The topPadding can be bigger than the regular padding when qs is expanded, in that
2433 // state the maxPanelHeight and the contentHeight should be bigger
2434 mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
Selim Cinekc22fff62016-05-20 12:44:30 -07002435 updateScrollability();
Selim Cinek51d21972017-07-19 17:39:20 -07002436 clampScrollPosition();
Selim Cinek91d4cba2016-11-10 19:59:48 -08002437 mAmbientState.setLayoutMaxHeight(mContentHeight);
Selim Cinekc22fff62016-05-20 12:44:30 -07002438 }
2439
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09002440 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002441 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbe2c4432017-05-30 12:11:09 -07002442 public boolean hasPulsingNotifications() {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09002443 return mPulsing;
Adrian Roos7d062c42017-03-30 15:11:43 -07002444 }
2445
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002446 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07002447 private void updateScrollability() {
Riddle Hsu065c01c2018-05-10 23:14:19 +08002448 boolean scrollable = !mQsExpanded && getScrollRange() > 0;
Selim Cinekc22fff62016-05-20 12:44:30 -07002449 if (scrollable != mScrollable) {
2450 mScrollable = scrollable;
2451 setFocusable(scrollable);
Selim Cinekef406062016-09-29 17:33:13 -07002452 updateForwardAndBackwardScrollability();
2453 }
2454 }
2455
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002456 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekef406062016-09-29 17:33:13 -07002457 private void updateForwardAndBackwardScrollability() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002458 boolean forwardScrollable = mScrollable && !isScrolledToBottom();
2459 boolean backwardsScrollable = mScrollable && !isScrolledToTop();
Selim Cinekef406062016-09-29 17:33:13 -07002460 boolean changed = forwardScrollable != mForwardScrollable
2461 || backwardsScrollable != mBackwardScrollable;
2462 mForwardScrollable = forwardScrollable;
2463 mBackwardScrollable = backwardsScrollable;
2464 if (changed) {
2465 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Selim Cinekc22fff62016-05-20 12:44:30 -07002466 }
Selim Cinek67b22602014-03-10 15:40:16 +01002467 }
2468
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002469 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek6811d722016-01-19 17:53:12 -08002470 private void updateBackground() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08002471 // No need to update the background color if it's not being drawn.
Selim Cinek195dfc52019-05-30 19:35:05 -07002472 if (!mShouldDrawNotificationBackground) {
Selim Cinek6811d722016-01-19 17:53:12 -08002473 return;
2474 }
Anthony Chen3cb3ad92016-12-01 10:58:47 -08002475
Selim Cinek6811d722016-01-19 17:53:12 -08002476 updateBackgroundBounds();
Gus Prevase2d6f042018-10-17 15:25:30 -04002477 if (didSectionBoundsChange()) {
2478 boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
2479 || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
Selim Cinek54680902016-10-19 16:49:44 -07002480 if (!isExpanded()) {
2481 abortBackgroundAnimators();
2482 animate = false;
2483 }
2484 if (animate) {
Selim Cinek614576e2016-01-20 10:54:09 -08002485 startBackgroundAnimation();
2486 } else {
Gus Prevase2d6f042018-10-17 15:25:30 -04002487 for (NotificationSection section : mSections) {
2488 section.resetCurrentBounds();
2489 }
Lucas Dupin90a38dd2018-09-05 09:37:37 -07002490 invalidate();
Selim Cinek614576e2016-01-20 10:54:09 -08002491 }
2492 } else {
Selim Cinek54680902016-10-19 16:49:44 -07002493 abortBackgroundAnimators();
Selim Cinek6811d722016-01-19 17:53:12 -08002494 }
Selim Cinek614576e2016-01-20 10:54:09 -08002495 mAnimateNextBackgroundTop = false;
Gus Prevase2d6f042018-10-17 15:25:30 -04002496 mAnimateNextBackgroundBottom = false;
2497 mAnimateNextSectionBoundsChange = false;
Selim Cinek614576e2016-01-20 10:54:09 -08002498 }
2499
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002500 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek54680902016-10-19 16:49:44 -07002501 private void abortBackgroundAnimators() {
Gus Prevase2d6f042018-10-17 15:25:30 -04002502 for (NotificationSection section : mSections) {
2503 section.cancelAnimators();
Selim Cinek54680902016-10-19 16:49:44 -07002504 }
2505 }
2506
Gus Prevase2d6f042018-10-17 15:25:30 -04002507 private boolean didSectionBoundsChange() {
2508 for (NotificationSection section : mSections) {
2509 if (section.didBoundsChange()) {
2510 return true;
2511 }
2512 }
2513 return false;
2514 }
2515
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002516 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Gus Prevase2d6f042018-10-17 15:25:30 -04002517 private boolean areSectionBoundsAnimating() {
2518 for (NotificationSection section : mSections) {
2519 if (section.areBoundsAnimating()) {
2520 return true;
2521 }
2522 }
2523 return false;
Selim Cinek614576e2016-01-20 10:54:09 -08002524 }
2525
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002526 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek614576e2016-01-20 10:54:09 -08002527 private void startBackgroundAnimation() {
Gus Prevase2d6f042018-10-17 15:25:30 -04002528 // TODO(kprevas): do we still need separate fields for top/bottom?
2529 // or can each section manage its own animation state?
2530 NotificationSection firstVisibleSection = getFirstVisibleSection();
2531 NotificationSection lastVisibleSection = getLastVisibleSection();
2532 for (NotificationSection section : mSections) {
2533 section.startBackgroundAnimation(
2534 section == firstVisibleSection
2535 ? mAnimateNextBackgroundTop
2536 : mAnimateNextSectionBoundsChange,
2537 section == lastVisibleSection
2538 ? mAnimateNextBackgroundBottom
2539 : mAnimateNextSectionBoundsChange);
Selim Cinek614576e2016-01-20 10:54:09 -08002540 }
Selim Cinek6811d722016-01-19 17:53:12 -08002541 }
2542
2543 /**
2544 * Update the background bounds to the new desired bounds
2545 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002546 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek6811d722016-01-19 17:53:12 -08002547 private void updateBackgroundBounds() {
shawnlin27716722019-04-17 20:09:45 +08002548 int left = mSidePaddings;
2549 int right = getWidth() - mSidePaddings;
Gus Prevase2d6f042018-10-17 15:25:30 -04002550 for (NotificationSection section : mSections) {
2551 section.getBounds().left = left;
2552 section.getBounds().right = right;
2553 }
shawnlin3a2a2e22018-05-04 17:09:50 +08002554
Selim Cinek614576e2016-01-20 10:54:09 -08002555 if (!mIsExpanded) {
Gus Prevase2d6f042018-10-17 15:25:30 -04002556 for (NotificationSection section : mSections) {
2557 section.getBounds().top = 0;
2558 section.getBounds().bottom = 0;
2559 }
Selim Cinek1791f502016-10-07 17:38:03 -04002560 return;
Selim Cinek614576e2016-01-20 10:54:09 -08002561 }
Selim Cinek893a9df2019-07-19 17:30:16 -07002562 int minTopPosition;
Gus Prevase2d6f042018-10-17 15:25:30 -04002563 NotificationSection lastSection = getLastVisibleSection();
Selim Cinekc3fec682019-06-06 18:11:07 -07002564 boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
2565 if (!onKeyguard) {
Selim Cinek3fe7e7e2019-02-15 18:40:53 -08002566 minTopPosition = (int) (mTopPadding + mStackTranslation);
2567 } else if (lastSection == null) {
2568 minTopPosition = mTopPadding;
Selim Cinek893a9df2019-07-19 17:30:16 -07002569 } else {
2570 // The first sections could be empty while there could still be elements in later
2571 // sections. The position of these first few sections is determined by the position of
2572 // the first visible section.
2573 NotificationSection firstVisibleSection = getFirstVisibleSection();
2574 firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
2575 false /* shiftPulsingWithFirst */);
2576 minTopPosition = firstVisibleSection.getBounds().top;
Selim Cinek3776fe02016-02-04 13:32:43 -08002577 }
Selim Cinekc3fec682019-06-06 18:11:07 -07002578 boolean shiftPulsingWithFirst = mHeadsUpManager.getAllEntries().count() <= 1
2579 && (mAmbientState.isDozing()
2580 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard));
Selim Cinek3fe7e7e2019-02-15 18:40:53 -08002581 for (NotificationSection section : mSections) {
2582 int minBottomPosition = minTopPosition;
2583 if (section == lastSection) {
2584 // We need to make sure the section goes all the way to the shelf
Selim Cinek3a1d2742019-03-11 18:38:29 -07002585 minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
2586 + mShelf.getIntrinsicHeight());
Gus Prevase83700cb2018-12-14 11:42:51 -05002587 }
Selim Cinekae55d832019-02-22 17:43:43 -08002588 minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
2589 shiftPulsingWithFirst);
2590 shiftPulsingWithFirst = false;
Gus Prevase83700cb2018-12-14 11:42:51 -05002591 }
Selim Cinek614576e2016-01-20 10:54:09 -08002592 }
2593
Gus Prevase2d6f042018-10-17 15:25:30 -04002594 private NotificationSection getFirstVisibleSection() {
2595 for (NotificationSection section : mSections) {
2596 if (section.getFirstVisibleChild() != null) {
2597 return section;
2598 }
2599 }
2600 return null;
2601 }
2602
2603 private NotificationSection getLastVisibleSection() {
2604 for (int i = mSections.length - 1; i >= 0; i--) {
2605 NotificationSection section = mSections[i];
2606 if (section.getLastVisibleChild() != null) {
2607 return section;
2608 }
2609 }
2610 return null;
2611 }
2612
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002613 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek614576e2016-01-20 10:54:09 -08002614 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002615 int childCount = getChildCount();
2616 for (int i = childCount - 1; i >= 0; i--) {
2617 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002618 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2619 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002620 return (ActivatableNotificationView) child;
2621 }
2622 }
2623 return null;
2624 }
2625
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002626 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek614576e2016-01-20 10:54:09 -08002627 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002628 int childCount = getChildCount();
2629 for (int i = 0; i < childCount; i++) {
2630 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002631 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2632 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002633 return (ActivatableNotificationView) child;
2634 }
2635 }
2636 return null;
2637 }
2638
Selim Cinek67b22602014-03-10 15:40:16 +01002639 /**
2640 * Fling the scroll view
2641 *
2642 * @param velocityY The initial velocity in the Y direction. Positive
2643 * numbers mean that the finger/cursor is moving down the screen,
2644 * which means we want to scroll towards the top.
2645 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002646 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Muyuan Li26e30ae2016-04-11 17:31:42 -07002647 protected void fling(int velocityY) {
Selim Cinek67b22602014-03-10 15:40:16 +01002648 if (getChildCount() > 0) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002649 float topAmount = getCurrentOverScrollAmount(true);
2650 float bottomAmount = getCurrentOverScrollAmount(false);
2651 if (velocityY < 0 && topAmount > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002652 if (ANCHOR_SCROLLING) {
2653 mScrollAnchorViewY += topAmount;
2654 } else {
2655 setOwnScrollY(mOwnScrollY - (int) topAmount);
2656 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002657 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002658 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002659 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002660 * mOverflingDistance + topAmount;
2661 } else if (velocityY > 0 && bottomAmount > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002662 if (ANCHOR_SCROLLING) {
2663 mScrollAnchorViewY -= bottomAmount;
2664 } else {
2665 setOwnScrollY((int) (mOwnScrollY + bottomAmount));
2666 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002667 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002668 mMaxOverScroll = Math.abs(velocityY) / 1000f
2669 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
Jason Monke59dc402018-08-16 12:05:01 -04002670 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002671 } else {
2672 // it will be set once we reach the boundary
2673 mMaxOverScroll = 0.0f;
2674 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002675 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05002676 flingScroller(velocityY);
Gus Prevas0fa58d62019-01-11 13:58:40 -05002677 } else {
2678 int scrollRange = getScrollRange();
2679 int minScrollY = Math.max(0, scrollRange);
2680 if (mExpandedInThisMotion) {
2681 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2682 }
2683 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
2684 mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek94ab18c2016-02-25 12:35:51 -08002685 }
Selim Cinek67b22602014-03-10 15:40:16 +01002686
Selim Cinek9212de82017-02-06 16:04:28 -08002687 animateScroll();
Selim Cinek67b22602014-03-10 15:40:16 +01002688 }
2689 }
2690
Selim Cinek1408eb52014-06-02 14:45:38 +02002691 /**
Gus Prevascdc98342019-01-14 14:29:44 -05002692 * Flings the overscroller with the given velocity (anchor-based scrolling).
2693 *
2694 * Because anchor-based scrolling can't track the current scroll position, the overscroller is
2695 * always started at startY = 0, and we interpret the positions it computes as relative to the
2696 * start of the scroll.
2697 */
2698 private void flingScroller(int velocityY) {
2699 assert ANCHOR_SCROLLING;
2700 mIsScrollerBoundSet = false;
2701 maybeFlingScroller(velocityY, true /* always fling */);
2702 }
2703
2704 private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
2705 assert ANCHOR_SCROLLING;
2706 // Attempt to determine the maximum amount to scroll before we reach the end.
2707 // If the first view is not materialized (for an upwards scroll) or the last view is either
2708 // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
2709 // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
2710 // the scroller once we approach the start/end of the list.
2711 int minY = Integer.MIN_VALUE;
2712 int maxY = Integer.MAX_VALUE;
2713 if (velocityY < 0) {
2714 minY = getMaxNegativeScrollAmount();
2715 if (minY > Integer.MIN_VALUE) {
2716 mIsScrollerBoundSet = true;
2717 }
2718 } else {
2719 maxY = getMaxPositiveScrollAmount();
2720 if (maxY < Integer.MAX_VALUE) {
2721 mIsScrollerBoundSet = true;
2722 }
2723 }
2724 if (mIsScrollerBoundSet || alwaysFling) {
2725 mLastScrollerY = 0;
2726 // x velocity is set to 1 to avoid overscroller bug
2727 mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
2728 mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
2729 }
2730 }
2731
2732 /**
2733 * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
2734 * before reaching the bottom of the list (discounting overscroll).
2735 *
2736 * If the return value is negative then we have overscrolled; this is a transient state which
2737 * should immediately be handled by adjusting the anchor position and adding the extra space to
2738 * the bottom overscroll amount.
2739 *
2740 * If we don't know how many pixels we have left to scroll (because the last row has not been
2741 * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
2742 * {@link Integer#MAX_VALUE}.
2743 */
2744 private int getMaxPositiveScrollAmount() {
2745 assert ANCHOR_SCROLLING;
2746 // TODO: once we're recycling we need to check the adapter position of the last child.
2747 ExpandableNotificationRow lastRow = getLastRowNotGone();
2748 if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
2749 // distance from bottom of last child to bottom of notifications area is:
2750 // distance from bottom of last child
2751 return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
2752 // to top of anchor view
2753 - mScrollAnchorView.getTranslationY()
2754 // plus distance from anchor view to top of notifications area
2755 + mScrollAnchorViewY
2756 // minus height of notifications area.
2757 - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
2758 } else {
2759 return Integer.MAX_VALUE;
2760 }
2761 }
2762
2763 /**
2764 * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
2765 * direction (upwards) before reaching the top of the list (discounting overscroll).
2766 *
2767 * If the return value is positive then we have overscrolled; this is a transient state which
2768 * should immediately be handled by adjusting the anchor position and adding the extra space to
2769 * the top overscroll amount.
2770 *
2771 * If we don't know how many pixels we have left to scroll (because the first row has not been
2772 * materialized), we return {@link Integer#MIN_VALUE}.
2773 */
2774 private int getMaxNegativeScrollAmount() {
2775 assert ANCHOR_SCROLLING;
2776 // TODO: once we're recycling we need to check the adapter position of the first child.
2777 ExpandableView firstChild = getFirstChildNotGone();
2778 if (mScrollAnchorView != null && firstChild != null) {
2779 // distance from top of first child to top of notifications area is:
2780 // distance from top of anchor view
2781 return (int) -(mScrollAnchorView.getTranslationY()
2782 // to top of first child
2783 - firstChild.getTranslationY()
2784 // minus distance from top of anchor view to top of notifications area.
2785 - mScrollAnchorViewY);
2786 } else {
2787 return Integer.MIN_VALUE;
2788 }
2789 }
2790
2791 /**
2792 * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
2793 * not being materialized or being pinned to the shelf, we need to check on every frame if we're
2794 * able to set the bounds. If we are, we fling the scroller again with the newly computed
2795 * bounds.
2796 */
2797 private void maybeReflingScroller() {
2798 if (!mIsScrollerBoundSet) {
2799 // Because mScroller is a flywheel scroller, we fling with the minimum possible
2800 // velocity to establish direction, so as not to perceptibly affect the velocity.
2801 maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
2802 false /* alwaysFling */);
2803 }
2804 }
2805
2806 /**
Selim Cinek1408eb52014-06-02 14:45:38 +02002807 * @return Whether a fling performed on the top overscroll edge lead to the expanded
2808 * overScroll view (i.e QS).
2809 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002810 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02002811 private boolean shouldOverScrollFling(int initialVelocity) {
2812 float topOverScroll = getCurrentOverScrollAmount(true);
2813 return mScrolledToTopOnFirstDown
2814 && !mExpandedInThisMotion
2815 && topOverScroll > mMinTopOverScrollToEscape
2816 && initialVelocity > 0;
2817 }
2818
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002819 /**
2820 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2821 * account.
2822 *
Jason Monke59dc402018-08-16 12:05:01 -04002823 * @param qsHeight the top padding imposed by the quick settings panel
2824 * @param animate whether to animate the change
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002825 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002826 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekdb6d6df2019-05-22 20:39:59 -07002827 public void updateTopPadding(float qsHeight, boolean animate) {
Selim Cinekbc243a92016-09-27 16:35:13 -07002828 int topPadding = (int) qsHeight;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002829 int minStackHeight = getLayoutMinHeight();
Selim Cinekbc243a92016-09-27 16:35:13 -07002830 if (topPadding + minStackHeight > getHeight()) {
2831 mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
Selim Cinek1408eb52014-06-02 14:45:38 +02002832 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002833 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02002834 }
Selim Cinekb0fada62019-06-17 19:03:59 -07002835 setTopPadding(topPadding, animate && !mKeyguardBypassController.getBypassEnabled());
Selim Cinekbc243a92016-09-27 16:35:13 -07002836 setExpandedHeight(mExpandedHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02002837 }
2838
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002839 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
shawnlin8e4e92c2018-04-12 18:47:24 +08002840 public void setMaxTopPadding(int maxTopPadding) {
2841 mMaxTopPadding = maxTopPadding;
2842 }
2843
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002844 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002845 public int getLayoutMinHeight() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08002846 if (isHeadsUpTransition()) {
2847 return getTopHeadsUpPinnedHeight();
2848 }
Anthony Chen9e05d462017-04-07 10:10:21 -07002849 return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07002850 }
2851
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002852 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002853 public float getTopPaddingOverflow() {
2854 return mTopPaddingOverflow;
2855 }
2856
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002857 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002858 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002859 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek567e8452016-03-24 10:54:56 -07002860 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08002861 : mCollapsedSize;
Selim Cinekdb167372016-11-17 15:41:17 -08002862 int shelfHeight = 0;
Gus Prevase2d6f042018-10-17 15:25:30 -04002863 if (getLastVisibleSection() != null && mShelf.getVisibility() != GONE) {
Selim Cinekdb167372016-11-17 15:41:17 -08002864 shelfHeight = mShelf.getIntrinsicHeight();
2865 }
2866 return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002867 }
2868
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002869 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1408eb52014-06-02 14:45:38 +02002870 private int clampPadding(int desiredPadding) {
2871 return Math.max(desiredPadding, mIntrinsicPadding);
2872 }
2873
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002874 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfed1ab62014-06-17 14:10:33 -07002875 private float getRubberBandFactor(boolean onTop) {
2876 if (!onTop) {
2877 return RUBBER_BAND_FACTOR_NORMAL;
2878 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002879 if (mExpandedInThisMotion) {
2880 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002881 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002882 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2883 } else if (mScrolledToTopOnFirstDown) {
2884 return 1.0f;
2885 }
2886 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02002887 }
2888
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002889 /**
2890 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2891 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2892 * overscroll view (e.g. expand QS).
2893 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002894 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002895 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002896 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002897 || !mScrolledToTopOnFirstDown;
2898 }
2899
Selim Cinek67b22602014-03-10 15:40:16 +01002900
Selim Cinek67b22602014-03-10 15:40:16 +01002901
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002902 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekef5127e2015-12-21 16:55:58 -08002903 public void setChildTransferInProgress(boolean childTransferInProgress) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04002904 Assert.isMainThread();
Selim Cinekef5127e2015-12-21 16:55:58 -08002905 mChildTransferInProgress = childTransferInProgress;
2906 }
2907
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002908 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Christoph Studer068f5922014-04-08 17:43:07 -04002909 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002910 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04002911 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002912 // we only call our internal methods if this is actually a removal and not just a
2913 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08002914 if (!mChildTransferInProgress) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002915 onViewRemovedInternal((ExpandableView) child, this);
Selim Cinekb5605e52015-02-20 18:21:41 +01002916 }
2917 }
2918
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002919 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09002920 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -05002921 public void cleanUpViewStateForEntry(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04002922 View child = entry.getRow();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04002923 if (child == mSwipeHelper.getTranslatingParentView()) {
2924 mSwipeHelper.clearTranslatingParentView();
Mady Mellor4c97b0a2017-02-15 11:16:13 -08002925 }
Mady Mellor4c97b0a2017-02-15 11:16:13 -08002926 }
2927
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002928 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002929 private void onViewRemovedInternal(ExpandableView child, ViewGroup container) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002930 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002931 // This is only a position change, don't do anything special
2932 return;
2933 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002934 child.setOnHeightChangedListener(null);
2935 updateScrollStateForRemovedChild(child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002936 boolean animationGenerated = generateRemoveAnimation(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002937 if (animationGenerated) {
Rohan Shah524cf7b2018-03-15 14:40:02 -07002938 if (!mSwipedOutViews.contains(child)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002939 || Math.abs(child.getTranslation()) != child.getWidth()) {
Selim Cineka5703182016-05-11 21:23:16 -04002940 container.addTransientView(child, 0);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002941 child.setTransientContainer(container);
Selim Cinekd1395642016-04-28 12:22:42 -07002942 }
2943 } else {
2944 mSwipedOutViews.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002945 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002946 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002947
Selim Cineke9bad242016-06-15 11:46:37 -07002948 focusNextViewIfFocused(child);
2949 }
2950
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002951 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineke9bad242016-06-15 11:46:37 -07002952 private void focusNextViewIfFocused(View view) {
2953 if (view instanceof ExpandableNotificationRow) {
2954 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2955 if (row.shouldRefocusOnDismiss()) {
2956 View nextView = row.getChildAfterViewWhenDismissed();
2957 if (nextView == null) {
2958 View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
2959 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
2960 ? groupParentWhenDismissed.getTranslationY()
Selim Cinekef8c2252017-02-10 14:52:18 -08002961 : view.getTranslationY(), true /* ignoreChildren */);
Selim Cineke9bad242016-06-15 11:46:37 -07002962 }
2963 if (nextView != null) {
2964 nextView.requestAccessibilityFocus();
2965 }
2966 }
2967 }
2968
Selim Cinekc27437b2014-05-14 10:23:33 +02002969 }
2970
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002971 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekb5605e52015-02-20 18:21:41 +01002972 private boolean isChildInGroup(View child) {
2973 return child instanceof ExpandableNotificationRow
2974 && mGroupManager.isChildInGroupWithSummary(
Jason Monke59dc402018-08-16 12:05:01 -04002975 ((ExpandableNotificationRow) child).getStatusBarNotification());
Selim Cinekb5605e52015-02-20 18:21:41 +01002976 }
2977
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002978 /**
2979 * Generate a remove animation for a child view.
2980 *
2981 * @param child The view to generate the remove animation for.
2982 * @return Whether an animation was generated.
2983 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002984 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002985 private boolean generateRemoveAnimation(ExpandableView child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002986 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002987 mAddedHeadsUpChildren.remove(child);
2988 return false;
2989 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002990 if (isClickedHeadsUp(child)) {
Selim Cinek9dd0d042018-05-14 18:12:42 -07002991 // An animation is already running, add it transiently
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002992 mClearTransientViewsWhenFinished.add(child);
Selim Cinek0fccc722015-07-29 17:04:36 -07002993 return true;
2994 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002995 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002996 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002997 // Generate Animations
2998 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002999 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003000 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02003001 } else {
3002 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003003 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003004 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02003005 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003006 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003007 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02003008 }
3009
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003010 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek0fccc722015-07-29 17:04:36 -07003011 private boolean isClickedHeadsUp(View child) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09003012 return HeadsUpUtil.isClickedHeadsUpNotification(child);
Selim Cinek0fccc722015-07-29 17:04:36 -07003013 }
3014
Selim Cineke0890e52015-06-17 11:17:08 -07003015 /**
3016 * Remove a removed child view from the heads up animations if it was just added there
3017 *
3018 * @return whether any child was removed from the list to animate
3019 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003020 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineke0890e52015-06-17 11:17:08 -07003021 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
3022 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003023 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
3024 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07003025 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003026 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07003027 mTmpList.add(eventPair);
3028 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003029 }
3030 }
Selim Cineke0890e52015-06-17 11:17:08 -07003031 if (hasAddEvent) {
3032 // This child was just added lets remove all events.
3033 mHeadsUpChangeAnimations.removeAll(mTmpList);
Jason Monke59dc402018-08-16 12:05:01 -04003034 ((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
Selim Cineke0890e52015-06-17 11:17:08 -07003035 }
3036 mTmpList.clear();
3037 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003038 }
3039
Selim Cinek572bbd42014-04-25 16:43:27 +02003040 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01003041 * @param child the child to query
3042 * @return whether a view is not a top level child but a child notification and that group is
Jason Monke59dc402018-08-16 12:05:01 -04003043 * not expanded
Selim Cinekb5605e52015-02-20 18:21:41 +01003044 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003045 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekb5605e52015-02-20 18:21:41 +01003046 private boolean isChildInInvisibleGroup(View child) {
3047 if (child instanceof ExpandableNotificationRow) {
3048 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Ned Burnsf81c4c42019-01-07 14:10:43 -05003049 NotificationEntry groupSummary =
Selim Cinekb5605e52015-02-20 18:21:41 +01003050 mGroupManager.getGroupSummary(row.getStatusBarNotification());
Evan Laird94492852018-10-25 13:43:01 -04003051 if (groupSummary != null && groupSummary.getRow() != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07003052 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01003053 }
3054 }
3055 return false;
3056 }
3057
3058 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02003059 * Updates the scroll position when a child was removed
3060 *
3061 * @param removedChild the removed child
3062 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003063 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek61633a82016-01-25 15:54:10 -08003064 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003065 if (ANCHOR_SCROLLING) {
3066 if (removedChild == mScrollAnchorView) {
3067 ExpandableView firstChild = getFirstChildNotGone();
3068 if (firstChild != null) {
3069 mScrollAnchorView = firstChild;
3070 } else {
3071 mScrollAnchorView = mShelf;
3072 }
3073 // Adjust anchor view Y by the distance between the old and new anchors
3074 // so that there's no visible change.
3075 mScrollAnchorViewY +=
3076 mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
3077 }
3078 updateScrollAnchor();
3079 // TODO: once we're recycling this will need to check the adapter position of the child
3080 if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
3081 mScrollAnchorViewY = 0;
3082 }
3083 updateOnScrollChange();
Selim Cineka7ed2c12017-01-23 20:47:24 -08003084 } else {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003085 int startingPosition = getPositionInLinearLayout(removedChild);
3086 float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
3087 int padding;
3088 if (increasedPaddingAmount >= 0) {
3089 padding = (int) NotificationUtils.interpolate(
3090 mPaddingBetweenElements,
3091 mIncreasedPaddingBetweenElements,
3092 increasedPaddingAmount);
3093 } else {
3094 padding = (int) NotificationUtils.interpolate(
3095 0,
3096 mPaddingBetweenElements,
3097 1.0f + increasedPaddingAmount);
3098 }
3099 int childHeight = getIntrinsicHeight(removedChild) + padding;
3100 int endPosition = startingPosition + childHeight;
3101 if (endPosition <= mOwnScrollY) {
3102 // This child is fully scrolled of the top, so we have to deduct its height from the
3103 // scrollPosition
3104 setOwnScrollY(mOwnScrollY - childHeight);
3105 } else if (startingPosition < mOwnScrollY) {
3106 // This child is currently being scrolled into, set the scroll position to the
3107 // start of this child
3108 setOwnScrollY(startingPosition);
3109 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003110 }
3111 }
3112
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003113 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd7c4e002014-07-04 18:36:42 +02003114 private int getIntrinsicHeight(View view) {
3115 if (view instanceof ExpandableView) {
3116 ExpandableView expandableView = (ExpandableView) view;
3117 return expandableView.getIntrinsicHeight();
3118 }
3119 return view.getHeight();
3120 }
3121
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003122 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1f624952017-06-08 19:11:50 -07003123 public int getPositionInLinearLayout(View requestedView) {
Adrian Roos4a579672016-05-24 16:54:37 -07003124 ExpandableNotificationRow childInGroup = null;
3125 ExpandableNotificationRow requestedRow = null;
3126 if (isChildInGroup(requestedView)) {
3127 // We're asking for a child in a group. Calculate the position of the parent first,
3128 // then within the parent.
3129 childInGroup = (ExpandableNotificationRow) requestedView;
3130 requestedView = requestedRow = childInGroup.getNotificationParent();
3131 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003132 int position = 0;
Selim Cineka7ed2c12017-01-23 20:47:24 -08003133 float previousPaddingRequest = mPaddingBetweenElements;
3134 float previousPaddingAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02003135 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08003136 ExpandableView child = (ExpandableView) getChildAt(i);
3137 boolean notGone = child.getVisibility() != View.GONE;
Selim Cinek281c2022016-10-13 19:14:43 -07003138 if (notGone && !child.hasNoContentHeight()) {
Selim Cinek42357e02016-02-24 18:48:01 -08003139 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cineka7ed2c12017-01-23 20:47:24 -08003140 float padding;
3141 if (increasedPaddingAmount >= 0.0f) {
3142 padding = (int) NotificationUtils.interpolate(
3143 previousPaddingRequest,
3144 mIncreasedPaddingBetweenElements,
3145 increasedPaddingAmount);
3146 previousPaddingRequest = (int) NotificationUtils.interpolate(
Selim Cinek42357e02016-02-24 18:48:01 -08003147 mPaddingBetweenElements,
3148 mIncreasedPaddingBetweenElements,
Selim Cineka7ed2c12017-01-23 20:47:24 -08003149 increasedPaddingAmount);
3150 } else {
3151 int ownPadding = (int) NotificationUtils.interpolate(
3152 0,
3153 mPaddingBetweenElements,
3154 1.0f + increasedPaddingAmount);
3155 if (previousPaddingAmount > 0.0f) {
3156 padding = (int) NotificationUtils.interpolate(
3157 ownPadding,
3158 mIncreasedPaddingBetweenElements,
3159 previousPaddingAmount);
3160 } else {
3161 padding = ownPadding;
3162 }
3163 previousPaddingRequest = ownPadding;
Selim Cinek61633a82016-01-25 15:54:10 -08003164 }
Selim Cineka7ed2c12017-01-23 20:47:24 -08003165 if (position != 0) {
3166 position += padding;
3167 }
3168 previousPaddingAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08003169 }
Adrian Roos4a579672016-05-24 16:54:37 -07003170 if (child == requestedView) {
3171 if (requestedRow != null) {
3172 position += requestedRow.getPositionOfChild(childInGroup);
3173 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003174 return position;
3175 }
Selim Cinek61633a82016-01-25 15:54:10 -08003176 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02003177 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02003178 }
3179 }
3180 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02003181 }
3182
3183 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003184 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adam Powell6690d012015-06-17 16:41:56 -07003185 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02003186 super.onViewAdded(child);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003187 onViewAddedInternal((ExpandableView) child);
Selim Cinekb5605e52015-02-20 18:21:41 +01003188 }
3189
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003190 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek614576e2016-01-20 10:54:09 -08003191 private void updateFirstAndLastBackgroundViews() {
Gus Prevase2d6f042018-10-17 15:25:30 -04003192 NotificationSection firstSection = getFirstVisibleSection();
3193 NotificationSection lastSection = getLastVisibleSection();
Gus Prevasda13cfa2018-11-20 14:58:48 -05003194 ActivatableNotificationView previousFirstChild =
3195 firstSection == null ? null : firstSection.getFirstVisibleChild();
3196 ActivatableNotificationView previousLastChild =
3197 lastSection == null ? null : lastSection.getLastVisibleChild();
Gus Prevase2d6f042018-10-17 15:25:30 -04003198
Selim Cinek614576e2016-01-20 10:54:09 -08003199 ActivatableNotificationView firstChild = getFirstChildWithBackground();
3200 ActivatableNotificationView lastChild = getLastChildWithBackground();
Ned Burns9eb06332019-04-23 16:02:12 -04003201 boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsInSections(
Gus Prevase2d6f042018-10-17 15:25:30 -04003202 mSections[0], mSections[1], firstChild, lastChild);
3203
Selim Cinek614576e2016-01-20 10:54:09 -08003204 if (mAnimationsEnabled && mIsExpanded) {
Gus Prevasda13cfa2018-11-20 14:58:48 -05003205 mAnimateNextBackgroundTop = firstChild != previousFirstChild;
Selim Cinek6f0a62a2019-04-09 18:40:12 -07003206 mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
Gus Prevase2d6f042018-10-17 15:25:30 -04003207 mAnimateNextSectionBoundsChange = sectionViewsChanged;
Selim Cinek614576e2016-01-20 10:54:09 -08003208 } else {
3209 mAnimateNextBackgroundTop = false;
3210 mAnimateNextBackgroundBottom = false;
Gus Prevase2d6f042018-10-17 15:25:30 -04003211 mAnimateNextSectionBoundsChange = false;
Selim Cinek614576e2016-01-20 10:54:09 -08003212 }
Selim Cinekdb167372016-11-17 15:41:17 -08003213 mAmbientState.setLastVisibleBackgroundChild(lastChild);
Gus Prevase2d6f042018-10-17 15:25:30 -04003214 mRoundnessManager.updateRoundedChildren(mSections);
Selim Cinek6f0a62a2019-04-09 18:40:12 -07003215 mAnimateBottomOnLayout = false;
Selim Cinek515b2032017-11-15 10:20:19 -08003216 invalidate();
Selim Cinek614576e2016-01-20 10:54:09 -08003217 }
3218
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003219 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003220 private void onViewAddedInternal(ExpandableView child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07003221 updateHideSensitiveForChild(child);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003222 child.setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00003223 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02003224 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02003225 updateChronometerForChild(child);
Gus Prevasa18dc572019-01-14 16:11:22 -05003226 if (child instanceof ExpandableNotificationRow) {
3227 ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
3228 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05003229 if (ANCHOR_SCROLLING) {
3230 // TODO: once we're recycling this will need to check the adapter position of the child
3231 if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
3232 // New child was added at the top while we're scrolled to the top;
3233 // make it the new anchor view so that we stay at the top.
3234 mScrollAnchorView = child;
3235 }
3236 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003237 }
3238
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003239 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003240 private void updateHideSensitiveForChild(ExpandableView child) {
3241 child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
Selim Cinekd06c41c2015-07-06 14:51:36 -07003242 }
3243
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003244 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003245 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003246 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
Selim Cinekd1395642016-04-28 12:22:42 -07003247 onViewRemovedInternal(row, childrenContainer);
Selim Cinekb5605e52015-02-20 18:21:41 +01003248 }
3249
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003250 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003251 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003252 public void notifyGroupChildAdded(ExpandableView row) {
Selim Cinekb5605e52015-02-20 18:21:41 +01003253 onViewAddedInternal(row);
3254 }
3255
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003256 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi75c95042014-05-16 19:09:59 +02003257 public void setAnimationsEnabled(boolean animationsEnabled) {
3258 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02003259 updateNotificationAnimationStates();
Rohan Shah8ee53652018-04-05 11:13:50 -07003260 if (!animationsEnabled) {
3261 mSwipedOutViews.clear();
3262 mChildrenToRemoveAnimated.clear();
3263 clearTemporaryViewsInGroup(this);
3264 }
Selim Cinekcab4a602014-09-03 14:47:57 +02003265 }
3266
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003267 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcab4a602014-09-03 14:47:57 +02003268 private void updateNotificationAnimationStates() {
Selim Cinekbe2c4432017-05-30 12:11:09 -07003269 boolean running = mAnimationsEnabled || hasPulsingNotifications();
Selim Cinek09bd29d2017-02-03 15:30:28 -08003270 mShelf.setAnimationsEnabled(running);
Selim Cinekcab4a602014-09-03 14:47:57 +02003271 int childCount = getChildCount();
3272 for (int i = 0; i < childCount; i++) {
3273 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07003274 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02003275 updateAnimationState(running, child);
3276 }
3277 }
3278
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003279 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek51ae05d2014-09-09 15:51:38 +02003280 private void updateAnimationState(View child) {
Selim Cinekbe2c4432017-05-30 12:11:09 -07003281 updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003282 && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02003283 }
3284
Selim Cinek2627d722018-01-19 12:16:49 -08003285 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003286 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek2627d722018-01-19 12:16:49 -08003287 public void setExpandingNotification(ExpandableNotificationRow row) {
3288 mAmbientState.setExpandingNotification(row);
3289 requestChildrenUpdate();
3290 }
3291
3292 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003293 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek8875de12018-03-22 10:14:32 -07003294 public void bindRow(ExpandableNotificationRow row) {
Selim Cinekf0c79e12018-05-14 17:17:31 -07003295 row.setHeadsUpAnimatingAwayListener(animatingAway -> {
3296 mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway);
3297 mHeadsUpAppearanceController.updateHeader(row.getEntry());
3298 });
Selim Cinek8875de12018-03-22 10:14:32 -07003299 }
3300
3301 @Override
Selim Cinekfdf80332019-03-07 17:29:55 -08003302 public boolean containsView(View v) {
3303 return v.getParent() == this;
3304 }
3305
3306 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003307 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek2627d722018-01-19 12:16:49 -08003308 public void applyExpandAnimationParams(ExpandAnimationParameters params) {
3309 mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
3310 requestChildrenUpdate();
3311 }
Selim Cinek51ae05d2014-09-09 15:51:38 +02003312
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003313 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcab4a602014-09-03 14:47:57 +02003314 private void updateAnimationState(boolean running, View child) {
3315 if (child instanceof ExpandableNotificationRow) {
3316 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3317 row.setIconAnimationRunning(running);
3318 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02003319 }
3320
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003321 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi75c95042014-05-16 19:09:59 +02003322 public boolean isAddOrRemoveAnimationPending() {
3323 return mNeedsAnimation
3324 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
3325 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003326
3327 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003328 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003329 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
Selim Cinek820ba2d2019-06-18 18:59:09 -07003330 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003331 // Generate Animations
3332 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00003333 if (fromMoreCard) {
3334 mFromMoreCardAdditions.add(child);
3335 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003336 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02003337 }
Selim Cinek820ba2d2019-06-18 18:59:09 -07003338 if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress
3339 && !isFullyHidden()) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003340 mAddedHeadsUpChildren.add(child);
3341 mChildrenToAddAnimated.remove(child);
3342 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003343 }
3344
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003345 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003346 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003347 public void changeViewPosition(ExpandableView child, int newIndex) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04003348 Assert.isMainThread();
3349 if (mChangePositionInProgress) {
3350 throw new IllegalStateException("Reentrant call to changeViewPosition");
3351 }
3352
Dan Sandlereceda3d2014-07-21 15:35:01 -04003353 int currentIndex = indexOfChild(child);
Rohan Shah8ee53652018-04-05 11:13:50 -07003354
3355 if (currentIndex == -1) {
3356 boolean isTransient = false;
3357 if (child instanceof ExpandableNotificationRow
Jason Monke59dc402018-08-16 12:05:01 -04003358 && ((ExpandableNotificationRow) child).getTransientContainer() != null) {
Rohan Shah8ee53652018-04-05 11:13:50 -07003359 isTransient = true;
3360 }
3361 Log.e(TAG, "Attempting to re-position "
3362 + (isTransient ? "transient" : "")
3363 + " view {"
3364 + child
3365 + "}");
3366 return;
3367 }
3368
Dan Sandlereceda3d2014-07-21 15:35:01 -04003369 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003370 mChangePositionInProgress = true;
Jason Monke59dc402018-08-16 12:05:01 -04003371 ((ExpandableView) child).setChangingPosition(true);
Selim Cinekc27437b2014-05-14 10:23:33 +02003372 removeView(child);
3373 addView(child, newIndex);
Jason Monke59dc402018-08-16 12:05:01 -04003374 ((ExpandableView) child).setChangingPosition(false);
Selim Cinek159ffdb2014-06-04 22:24:18 +02003375 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04003376 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003377 mChildrenChangingPositions.add(child);
3378 mNeedsAnimation = true;
3379 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003380 }
3381 }
3382
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003383 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekf4c19962014-05-01 21:55:31 +02003384 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003385 if (mNeedsAnimation) {
Rohan Shahb9d500a2018-06-25 16:27:16 -07003386 generateAllAnimationEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003387 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02003388 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003389 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Selim Cinekea66ca02016-05-24 13:33:47 -07003390 setAnimationRunning(true);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003391 mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003392 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08003393 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08003394 updateViewShadows();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08003395 updateClippingToTopRoundedCorner();
Selim Cinekf4c19962014-05-01 21:55:31 +02003396 } else {
3397 applyCurrentState();
3398 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003399 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02003400 }
3401
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003402 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Rohan Shahb9d500a2018-06-25 16:27:16 -07003403 private void generateAllAnimationEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07003404 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02003405 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003406 generateChildAdditionEvents();
3407 generatePositionChangeEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003408 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003409 generateActivateEvent();
3410 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02003411 generateHideSensitiveEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003412 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02003413 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01003414 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02003415 generateAnimateEverythingEvent();
Selim Cinek572bbd42014-04-25 16:43:27 +02003416 }
3417
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003418 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003419 private void generateHeadsUpAnimationEvents() {
3420 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003421 ExpandableNotificationRow row = eventPair.first;
3422 boolean isHeadsUp = eventPair.second;
Selim Cineke3c6e462019-06-24 19:37:06 -07003423 if (isHeadsUp != row.isHeadsUp()) {
3424 // For cases where we have a heads up showing and appearing again we shouldn't
3425 // do the animations at all.
3426 continue;
3427 }
Selim Cineka59ecc32015-04-07 10:51:49 -07003428 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
3429 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07003430 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cineke3c6e462019-06-24 19:37:06 -07003431 boolean performDisappearAnimation = !mIsExpanded
3432 // Only animate if we still have pinned heads up, otherwise we just have the
3433 // regular collapse animation of the lock screen
3434 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard()
3435 && mHeadsUpManager.hasPinnedHeadsUp());
3436 if (performDisappearAnimation && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003437 type = row.wasJustClicked()
3438 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3439 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinek76e813c2016-07-14 11:16:58 -07003440 if (row.isChildInGroup()) {
3441 // We can otherwise get stuck in there if it was just isolated
Selim Cinekcafa87f2016-10-26 17:00:17 -07003442 row.setHeadsUpAnimatingAway(false);
Selim Cinekf93bf3e2018-05-08 14:43:21 -07003443 continue;
Selim Cinek76e813c2016-07-14 11:16:58 -07003444 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04003445 } else {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003446 ExpandableViewState viewState = row.getViewState();
Selim Cinekeaee9c02015-06-25 11:04:20 -04003447 if (viewState == null) {
3448 // A view state was never generated for this view, so we don't need to animate
3449 // this. This may happen with notification children.
3450 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07003451 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04003452 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
3453 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
3454 // Our custom add animation
3455 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
3456 } else {
3457 // Normal add animation
3458 type = AnimationEvent.ANIMATION_TYPE_ADD;
3459 }
3460 onBottom = !pinnedAndClosed;
3461 }
Selim Cineka59ecc32015-04-07 10:51:49 -07003462 }
3463 AnimationEvent event = new AnimationEvent(row, type);
3464 event.headsUpFromBottom = onBottom;
3465 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003466 }
3467 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07003468 mAddedHeadsUpChildren.clear();
3469 }
3470
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003471 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekbbcebde2016-11-09 18:28:20 -08003472 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003473 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
3474 return false;
3475 }
3476 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003477 }
3478
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003479 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb5605e52015-02-20 18:21:41 +01003480 private void generateGroupExpansionEvent() {
3481 // Generate a group expansion/collapsing event if there is such a group at all
3482 if (mExpandedGroupView != null) {
3483 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
3484 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
3485 mExpandedGroupView = null;
3486 }
3487 }
3488
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003489 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineka5e211b2014-08-11 17:35:48 +02003490 private void generateViewResizeEvent() {
3491 if (mNeedViewResizeAnimation) {
felkachangd7835b02018-07-17 18:18:13 +08003492 boolean hasDisappearAnimation = false;
3493 for (AnimationEvent animationEvent : mAnimationEvents) {
3494 final int type = animationEvent.animationType;
3495 if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
Jason Monke59dc402018-08-16 12:05:01 -04003496 || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
felkachangd7835b02018-07-17 18:18:13 +08003497 hasDisappearAnimation = true;
3498 break;
3499 }
3500 }
3501
3502 if (!hasDisappearAnimation) {
3503 mAnimationEvents.add(
3504 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
3505 }
Selim Cineka5e211b2014-08-11 17:35:48 +02003506 }
3507 mNeedViewResizeAnimation = false;
3508 }
3509
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003510 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02003511 private void generateChildRemovalEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003512 for (ExpandableView child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003513 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003514
3515 // we need to know the view after this one
Selim Cinekef8c2252017-02-10 14:52:18 -08003516 float removedTranslation = child.getTranslationY();
3517 boolean ignoreChildren = true;
3518 if (child instanceof ExpandableNotificationRow) {
3519 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3520 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
3521 removedTranslation = row.getTranslationWhenRemoved();
3522 ignoreChildren = false;
3523 }
Selim Cinek51052042017-07-04 12:07:55 +02003524 childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth();
Selim Cinekef8c2252017-02-10 14:52:18 -08003525 }
Selim Cinek51052042017-07-04 12:07:55 +02003526 if (!childWasSwipedOut) {
3527 Rect clipBounds = child.getClipBounds();
Selim Cineke5832ee2017-11-06 14:46:48 -08003528 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
Rohan Shaha7594962018-05-22 10:59:30 -07003529
3530 if (childWasSwipedOut && child instanceof ExpandableView) {
3531 // Clean up any potential transient views if the child has already been swiped
3532 // out, as we won't be animating it further (due to its height already being
3533 // clipped to 0.
3534 ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer();
3535 if (transientContainer != null) {
3536 transientContainer.removeTransientView(child);
3537 }
3538 }
Selim Cinek51052042017-07-04 12:07:55 +02003539 }
3540 int animationType = childWasSwipedOut
3541 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
3542 : AnimationEvent.ANIMATION_TYPE_REMOVE;
3543 AnimationEvent event = new AnimationEvent(child, animationType);
Selim Cinekef8c2252017-02-10 14:52:18 -08003544 event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation,
3545 ignoreChildren);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003546 mAnimationEvents.add(event);
Selim Cinekd1395642016-04-28 12:22:42 -07003547 mSwipedOutViews.remove(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02003548 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003549 mChildrenToRemoveAnimated.clear();
3550 }
3551
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003552 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003553 private void generatePositionChangeEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003554 for (ExpandableView child : mChildrenChangingPositions) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003555 mAnimationEvents.add(new AnimationEvent(child,
3556 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3557 }
3558 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01003559 if (mGenerateChildOrderChangedEvent) {
3560 mAnimationEvents.add(new AnimationEvent(null,
3561 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3562 mGenerateChildOrderChangedEvent = false;
3563 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003564 }
3565
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003566 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02003567 private void generateChildAdditionEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003568 for (ExpandableView child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003569 if (mFromMoreCardAdditions.contains(child)) {
3570 mAnimationEvents.add(new AnimationEvent(child,
3571 AnimationEvent.ANIMATION_TYPE_ADD,
3572 StackStateAnimator.ANIMATION_DURATION_STANDARD));
3573 } else {
3574 mAnimationEvents.add(new AnimationEvent(child,
3575 AnimationEvent.ANIMATION_TYPE_ADD));
3576 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003577 }
3578 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003579 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04003580 }
3581
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003582 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003583 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02003584 if (mTopPaddingNeedsAnimation) {
Lucas Dupinc445dcc2018-05-07 16:00:11 -07003585 AnimationEvent event;
Selim Cinekc1d9ab22019-05-21 18:08:30 -07003586 if (mAmbientState.isDozing()) {
Lucas Dupinc445dcc2018-05-07 16:00:11 -07003587 event = new AnimationEvent(null /* view */,
3588 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED,
3589 KeyguardSliceView.DEFAULT_ANIM_DURATION);
3590 } else {
3591 event = new AnimationEvent(null /* view */,
3592 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED);
3593 }
3594 mAnimationEvents.add(event);
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02003595 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003596 mTopPaddingNeedsAnimation = false;
3597 }
3598
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003599 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003600 private void generateActivateEvent() {
3601 if (mActivateNeedsAnimation) {
3602 mAnimationEvents.add(
3603 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
3604 }
3605 mActivateNeedsAnimation = false;
3606 }
3607
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003608 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekd9acca52014-09-01 22:33:25 +02003609 private void generateAnimateEverythingEvent() {
3610 if (mEverythingNeedsAnimation) {
3611 mAnimationEvents.add(
3612 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
3613 }
3614 mEverythingNeedsAnimation = false;
3615 }
3616
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003617 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003618 private void generateDimmedEvent() {
3619 if (mDimmedNeedsAnimation) {
3620 mAnimationEvents.add(
3621 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
3622 }
3623 mDimmedNeedsAnimation = false;
3624 }
3625
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003626 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggiae441282014-08-01 02:45:18 +02003627 private void generateHideSensitiveEvent() {
3628 if (mHideSensitiveNeedsAnimation) {
3629 mAnimationEvents.add(
3630 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
3631 }
3632 mHideSensitiveNeedsAnimation = false;
3633 }
3634
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003635 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003636 private void generateGoToFullShadeEvent() {
3637 if (mGoToFullShadeNeedsAnimation) {
3638 mAnimationEvents.add(
3639 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
3640 }
3641 mGoToFullShadeNeedsAnimation = false;
3642 }
3643
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003644 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
3645 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003646 return new StackScrollAlgorithm(context, this);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003647 }
3648
3649 /**
3650 * @return Whether a y coordinate is inside the content.
3651 */
3652 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3653 public boolean isInContentBounds(float y) {
3654 return y < getHeight() - getEmptyBottomMargin();
3655 }
3656
3657 @ShadeViewRefactor(RefactorComponent.INPUT)
3658 public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
3659 mLongPressListener = listener;
3660 }
3661
3662 @Override
3663 @ShadeViewRefactor(RefactorComponent.INPUT)
3664 public boolean onTouchEvent(MotionEvent ev) {
3665 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
3666 || ev.getActionMasked() == MotionEvent.ACTION_UP;
3667 handleEmptySpaceClick(ev);
3668 boolean expandWantsIt = false;
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003669 boolean swipingInProgress = mSwipingInProgress;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003670 if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) {
3671 if (isCancelOrUp) {
3672 mExpandHelper.onlyObserveMovements(false);
3673 }
3674 boolean wasExpandingBefore = mExpandingNotification;
3675 expandWantsIt = mExpandHelper.onTouchEvent(ev);
3676 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
3677 && !mDisallowScrollingInThisMotion) {
3678 dispatchDownEventToScroller(ev);
3679 }
3680 }
3681 boolean scrollerWantsIt = false;
3682 if (mIsExpanded && !swipingInProgress && !mExpandingNotification
3683 && !mDisallowScrollingInThisMotion) {
3684 scrollerWantsIt = onScrollTouch(ev);
3685 }
3686 boolean horizontalSwipeWantsIt = false;
3687 if (!mIsBeingDragged
3688 && !mExpandingNotification
3689 && !mExpandedInThisMotion
3690 && !mOnlyScrollingInThisMotion
3691 && !mDisallowDismissInThisMotion) {
3692 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
3693 }
3694
3695 // Check if we need to clear any snooze leavebehinds
3696 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
3697 if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
3698 && guts.getGutsContent() instanceof NotificationSnooze) {
3699 NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
3700 if ((ns.isExpanded() && isCancelOrUp)
3701 || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
3702 // If the leavebehind is expanded we clear it on the next up event, otherwise we
3703 // clear it on the next non-horizontal swipe or expand event.
3704 checkSnoozeLeavebehind();
3705 }
3706 }
3707 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
3708 mCheckForLeavebehind = true;
3709 }
3710 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
3711 }
3712
3713 @ShadeViewRefactor(RefactorComponent.INPUT)
3714 private void dispatchDownEventToScroller(MotionEvent ev) {
3715 MotionEvent downEvent = MotionEvent.obtain(ev);
3716 downEvent.setAction(MotionEvent.ACTION_DOWN);
3717 onScrollTouch(downEvent);
3718 downEvent.recycle();
3719 }
3720
3721 @Override
3722 @ShadeViewRefactor(RefactorComponent.INPUT)
3723 public boolean onGenericMotionEvent(MotionEvent event) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003724 if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003725 || mDisallowScrollingInThisMotion) {
3726 return false;
3727 }
3728 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3729 switch (event.getAction()) {
3730 case MotionEvent.ACTION_SCROLL: {
3731 if (!mIsBeingDragged) {
3732 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3733 if (vscroll != 0) {
3734 final int delta = (int) (vscroll * getVerticalScrollFactor());
Gus Prevas0fa58d62019-01-11 13:58:40 -05003735 if (ANCHOR_SCROLLING) {
3736 mScrollAnchorViewY -= delta;
3737 updateScrollAnchor();
3738 clampScrollPosition();
3739 updateOnScrollChange();
3740 } else {
3741 final int range = getScrollRange();
3742 int oldScrollY = mOwnScrollY;
3743 int newScrollY = oldScrollY - delta;
3744 if (newScrollY < 0) {
3745 newScrollY = 0;
3746 } else if (newScrollY > range) {
3747 newScrollY = range;
3748 }
3749 if (newScrollY != oldScrollY) {
3750 setOwnScrollY(newScrollY);
3751 return true;
3752 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003753 }
3754 }
3755 }
3756 }
3757 }
3758 }
3759 return super.onGenericMotionEvent(event);
3760 }
3761
3762 @ShadeViewRefactor(RefactorComponent.INPUT)
3763 private boolean onScrollTouch(MotionEvent ev) {
3764 if (!isScrollingEnabled()) {
3765 return false;
3766 }
3767 if (isInsideQsContainer(ev) && !mIsBeingDragged) {
3768 return false;
3769 }
3770 mForcedScroll = null;
3771 initVelocityTrackerIfNotExists();
3772 mVelocityTracker.addMovement(ev);
3773
3774 final int action = ev.getAction();
3775
3776 switch (action & MotionEvent.ACTION_MASK) {
3777 case MotionEvent.ACTION_DOWN: {
3778 if (getChildCount() == 0 || !isInContentBounds(ev)) {
3779 return false;
3780 }
3781 boolean isBeingDragged = !mScroller.isFinished();
3782 setIsBeingDragged(isBeingDragged);
3783 /*
3784 * If being flinged and user touches, stop the fling. isFinished
3785 * will be false if being flinged.
3786 */
3787 if (!mScroller.isFinished()) {
3788 mScroller.forceFinished(true);
3789 }
3790
3791 // Remember where the motion event started
3792 mLastMotionY = (int) ev.getY();
3793 mDownX = (int) ev.getX();
3794 mActivePointerId = ev.getPointerId(0);
3795 break;
3796 }
3797 case MotionEvent.ACTION_MOVE:
3798 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
3799 if (activePointerIndex == -1) {
3800 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
3801 break;
3802 }
3803
3804 final int y = (int) ev.getY(activePointerIndex);
3805 final int x = (int) ev.getX(activePointerIndex);
3806 int deltaY = mLastMotionY - y;
3807 final int xDiff = Math.abs(x - mDownX);
3808 final int yDiff = Math.abs(deltaY);
3809 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
3810 setIsBeingDragged(true);
3811 if (deltaY > 0) {
3812 deltaY -= mTouchSlop;
3813 } else {
3814 deltaY += mTouchSlop;
3815 }
3816 }
3817 if (mIsBeingDragged) {
3818 // Scroll to follow the motion event
3819 mLastMotionY = y;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003820 float scrollAmount;
Gus Prevas0fa58d62019-01-11 13:58:40 -05003821 int range;
3822 if (ANCHOR_SCROLLING) {
3823 range = 0; // unused in the methods it's being passed to
3824 } else {
3825 range = getScrollRange();
3826 if (mExpandedInThisMotion) {
3827 range = Math.min(range, mMaxScrollAfterExpand);
3828 }
3829 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003830 if (deltaY < 0) {
3831 scrollAmount = overScrollDown(deltaY);
3832 } else {
3833 scrollAmount = overScrollUp(deltaY, range);
3834 }
3835
3836 // Calling customOverScrollBy will call onCustomOverScrolled, which
3837 // sets the scrolling if applicable.
3838 if (scrollAmount != 0.0f) {
3839 // The scrolling motion could not be compensated with the
3840 // existing overScroll, we have to scroll the view
3841 customOverScrollBy((int) scrollAmount, mOwnScrollY,
3842 range, getHeight() / 2);
3843 // If we're scrolling, leavebehinds should be dismissed
3844 checkSnoozeLeavebehind();
3845 }
3846 }
3847 break;
3848 case MotionEvent.ACTION_UP:
3849 if (mIsBeingDragged) {
3850 final VelocityTracker velocityTracker = mVelocityTracker;
3851 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3852 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3853
3854 if (shouldOverScrollFling(initialVelocity)) {
3855 onOverScrollFling(true, initialVelocity);
3856 } else {
3857 if (getChildCount() > 0) {
3858 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
3859 float currentOverScrollTop = getCurrentOverScrollAmount(true);
3860 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
3861 fling(-initialVelocity);
3862 } else {
3863 onOverScrollFling(false, initialVelocity);
3864 }
3865 } else {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003866 if (ANCHOR_SCROLLING) {
3867 // TODO
3868 } else {
3869 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
3870 getScrollRange())) {
3871 animateScroll();
3872 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003873 }
3874 }
3875 }
3876 }
3877 mActivePointerId = INVALID_POINTER;
3878 endDrag();
3879 }
3880
3881 break;
3882 case MotionEvent.ACTION_CANCEL:
3883 if (mIsBeingDragged && getChildCount() > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003884 if (ANCHOR_SCROLLING) {
3885 // TODO
3886 } else {
3887 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
3888 getScrollRange())) {
3889 animateScroll();
3890 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003891 }
3892 mActivePointerId = INVALID_POINTER;
3893 endDrag();
3894 }
3895 break;
3896 case MotionEvent.ACTION_POINTER_DOWN: {
3897 final int index = ev.getActionIndex();
3898 mLastMotionY = (int) ev.getY(index);
3899 mDownX = (int) ev.getX(index);
3900 mActivePointerId = ev.getPointerId(index);
3901 break;
3902 }
3903 case MotionEvent.ACTION_POINTER_UP:
3904 onSecondaryPointerUp(ev);
3905 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
3906 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
3907 break;
3908 }
3909 return true;
3910 }
3911
3912 @ShadeViewRefactor(RefactorComponent.INPUT)
3913 protected boolean isInsideQsContainer(MotionEvent ev) {
3914 return ev.getY() < mQsContainer.getBottom();
3915 }
3916
3917 @ShadeViewRefactor(RefactorComponent.INPUT)
3918 private void onOverScrollFling(boolean open, int initialVelocity) {
3919 if (mOverscrollTopChangedListener != null) {
3920 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
3921 }
3922 mDontReportNextOverScroll = true;
3923 setOverScrollAmount(0.0f, true, false);
3924 }
3925
3926
3927 @ShadeViewRefactor(RefactorComponent.INPUT)
3928 private void onSecondaryPointerUp(MotionEvent ev) {
3929 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3930 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3931 final int pointerId = ev.getPointerId(pointerIndex);
3932 if (pointerId == mActivePointerId) {
3933 // This was our active pointer going up. Choose a new
3934 // active pointer and adjust accordingly.
3935 // TODO: Make this decision more intelligent.
3936 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3937 mLastMotionY = (int) ev.getY(newPointerIndex);
3938 mActivePointerId = ev.getPointerId(newPointerIndex);
3939 if (mVelocityTracker != null) {
3940 mVelocityTracker.clear();
3941 }
3942 }
3943 }
3944
3945 @ShadeViewRefactor(RefactorComponent.INPUT)
3946 private void endDrag() {
3947 setIsBeingDragged(false);
3948
3949 recycleVelocityTracker();
3950
3951 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
3952 setOverScrollAmount(0, true /* onTop */, true /* animate */);
3953 }
3954 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
3955 setOverScrollAmount(0, false /* onTop */, true /* animate */);
3956 }
3957 }
3958
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003959 @Override
3960 @ShadeViewRefactor(RefactorComponent.INPUT)
3961 public boolean onInterceptTouchEvent(MotionEvent ev) {
3962 initDownStates(ev);
3963 handleEmptySpaceClick(ev);
3964 boolean expandWantsIt = false;
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003965 boolean swipingInProgress = mSwipingInProgress;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003966 if (!swipingInProgress && !mOnlyScrollingInThisMotion) {
3967 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
3968 }
3969 boolean scrollWantsIt = false;
3970 if (!swipingInProgress && !mExpandingNotification) {
3971 scrollWantsIt = onInterceptTouchEventScroll(ev);
3972 }
3973 boolean swipeWantsIt = false;
3974 if (!mIsBeingDragged
3975 && !mExpandingNotification
3976 && !mExpandedInThisMotion
3977 && !mOnlyScrollingInThisMotion
3978 && !mDisallowDismissInThisMotion) {
3979 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
3980 }
3981 // Check if we need to clear any snooze leavebehinds
3982 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
3983 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
3984 if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
3985 !expandWantsIt && !scrollWantsIt) {
3986 mCheckForLeavebehind = false;
3987 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
3988 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
3989 false /* resetMenu */);
3990 }
3991 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
3992 mCheckForLeavebehind = true;
3993 }
3994 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
3995 }
3996
3997 @ShadeViewRefactor(RefactorComponent.INPUT)
3998 private void handleEmptySpaceClick(MotionEvent ev) {
3999 switch (ev.getActionMasked()) {
4000 case MotionEvent.ACTION_MOVE:
4001 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
4002 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop)) {
4003 mTouchIsClick = false;
4004 }
4005 break;
4006 case MotionEvent.ACTION_UP:
4007 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
4008 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
4009 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
4010 }
4011 break;
4012 }
4013 }
4014
4015 @ShadeViewRefactor(RefactorComponent.INPUT)
4016 private void initDownStates(MotionEvent ev) {
4017 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
4018 mExpandedInThisMotion = false;
4019 mOnlyScrollingInThisMotion = !mScroller.isFinished();
4020 mDisallowScrollingInThisMotion = false;
4021 mDisallowDismissInThisMotion = false;
4022 mTouchIsClick = true;
4023 mInitialTouchX = ev.getX();
4024 mInitialTouchY = ev.getY();
4025 }
4026 }
4027
4028 @Override
4029 @ShadeViewRefactor(RefactorComponent.INPUT)
4030 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4031 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4032 if (disallowIntercept) {
4033 cancelLongPress();
4034 }
4035 }
4036
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004037 @ShadeViewRefactor(RefactorComponent.INPUT)
Selim Cinek67b22602014-03-10 15:40:16 +01004038 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02004039 if (!isScrollingEnabled()) {
4040 return false;
4041 }
Selim Cinek67b22602014-03-10 15:40:16 +01004042 /*
4043 * This method JUST determines whether we want to intercept the motion.
4044 * If we return true, onMotionEvent will be called and we do the actual
4045 * scrolling there.
4046 */
4047
4048 /*
Jason Monke59dc402018-08-16 12:05:01 -04004049 * Shortcut the most recurring case: the user is in the dragging
4050 * state and is moving their finger. We want to intercept this
4051 * motion.
4052 */
Selim Cinek67b22602014-03-10 15:40:16 +01004053 final int action = ev.getAction();
4054 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
4055 return true;
4056 }
4057
Selim Cinek67b22602014-03-10 15:40:16 +01004058 switch (action & MotionEvent.ACTION_MASK) {
4059 case MotionEvent.ACTION_MOVE: {
4060 /*
4061 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04004062 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01004063 */
4064
4065 /*
Jason Monke59dc402018-08-16 12:05:01 -04004066 * Locally do absolute value. mLastMotionY is set to the y value
4067 * of the down event.
4068 */
Selim Cinek67b22602014-03-10 15:40:16 +01004069 final int activePointerId = mActivePointerId;
4070 if (activePointerId == INVALID_POINTER) {
4071 // If we don't have a valid id, the touch down wasn't on content.
4072 break;
4073 }
4074
4075 final int pointerIndex = ev.findPointerIndex(activePointerId);
4076 if (pointerIndex == -1) {
4077 Log.e(TAG, "Invalid pointerId=" + activePointerId
4078 + " in onInterceptTouchEvent");
4079 break;
4080 }
4081
4082 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02004083 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01004084 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02004085 final int xDiff = Math.abs(x - mDownX);
4086 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01004087 setIsBeingDragged(true);
4088 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02004089 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01004090 initVelocityTrackerIfNotExists();
4091 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01004092 }
4093 break;
4094 }
4095
4096 case MotionEvent.ACTION_DOWN: {
4097 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05304098 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek34ed7c02017-09-08 15:03:12 -07004099 if (getChildAtPosition(ev.getX(), y, false /* requireMinHeight */) == null) {
Selim Cinek67b22602014-03-10 15:40:16 +01004100 setIsBeingDragged(false);
4101 recycleVelocityTracker();
4102 break;
4103 }
4104
4105 /*
4106 * Remember location of down touch.
4107 * ACTION_DOWN always refers to pointer index 0.
4108 */
4109 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02004110 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01004111 mActivePointerId = ev.getPointerId(0);
4112
4113 initOrResetVelocityTracker();
4114 mVelocityTracker.addMovement(ev);
4115 /*
Jason Monke59dc402018-08-16 12:05:01 -04004116 * If being flinged and user touches the screen, initiate drag;
4117 * otherwise don't. mScroller.isFinished should be false when
4118 * being flinged.
4119 */
Selim Cinek67b22602014-03-10 15:40:16 +01004120 boolean isBeingDragged = !mScroller.isFinished();
4121 setIsBeingDragged(isBeingDragged);
4122 break;
4123 }
4124
4125 case MotionEvent.ACTION_CANCEL:
4126 case MotionEvent.ACTION_UP:
4127 /* Release the drag */
4128 setIsBeingDragged(false);
4129 mActivePointerId = INVALID_POINTER;
4130 recycleVelocityTracker();
Gus Prevas0fa58d62019-01-11 13:58:40 -05004131 if (ANCHOR_SCROLLING) {
4132 // TODO
4133 } else {
4134 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
4135 animateScroll();
4136 }
Selim Cinek67b22602014-03-10 15:40:16 +01004137 }
4138 break;
4139 case MotionEvent.ACTION_POINTER_UP:
4140 onSecondaryPointerUp(ev);
4141 break;
4142 }
4143
4144 /*
Jason Monke59dc402018-08-16 12:05:01 -04004145 * The only time we want to intercept motion events is if we are in the
4146 * drag mode.
4147 */
Selim Cinek67b22602014-03-10 15:40:16 +01004148 return mIsBeingDragged;
4149 }
4150
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004151 /**
4152 * @return Whether the specified motion event is actually happening over the content.
4153 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004154 @ShadeViewRefactor(RefactorComponent.INPUT)
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004155 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01004156 return isInContentBounds(event.getY());
4157 }
4158
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004159
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004160 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004161 @ShadeViewRefactor(RefactorComponent.INPUT)
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004162 void setIsBeingDragged(boolean isDragged) {
Selim Cinek67b22602014-03-10 15:40:16 +01004163 mIsBeingDragged = isDragged;
4164 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004165 requestDisallowInterceptTouchEvent(true);
Geoffrey Pitsch409db272017-08-28 14:51:52 +00004166 cancelLongPress();
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004167 resetExposedMenuView(true /* animate */, true /* force */);
Selim Cinek67b22602014-03-10 15:40:16 +01004168 }
4169 }
4170
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004171 @ShadeViewRefactor(RefactorComponent.INPUT)
4172 public void requestDisallowLongPress() {
4173 cancelLongPress();
4174 }
4175
4176 @ShadeViewRefactor(RefactorComponent.INPUT)
4177 public void requestDisallowDismiss() {
4178 mDisallowDismissInThisMotion = true;
4179 }
4180
4181 @ShadeViewRefactor(RefactorComponent.INPUT)
4182 public void cancelLongPress() {
4183 mSwipeHelper.cancelLongPress();
4184 }
4185
4186 @ShadeViewRefactor(RefactorComponent.INPUT)
4187 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
4188 mOnEmptySpaceClickListener = listener;
4189 }
4190
4191 /** @hide */
4192 @Override
4193 @ShadeViewRefactor(RefactorComponent.INPUT)
4194 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4195 if (super.performAccessibilityActionInternal(action, arguments)) {
4196 return true;
4197 }
4198 if (!isEnabled()) {
4199 return false;
4200 }
4201 int direction = -1;
4202 switch (action) {
4203 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
4204 // fall through
4205 case android.R.id.accessibilityActionScrollDown:
4206 direction = 1;
4207 // fall through
4208 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
4209 // fall through
4210 case android.R.id.accessibilityActionScrollUp:
Gus Prevas0fa58d62019-01-11 13:58:40 -05004211 if (ANCHOR_SCROLLING) {
4212 // TODO
4213 } else {
4214 final int viewportHeight =
4215 getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
4216 - mShelf.getIntrinsicHeight();
4217 final int targetScrollY = Math.max(0,
4218 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
4219 if (targetScrollY != mOwnScrollY) {
4220 mScroller.startScroll(mScrollX, mOwnScrollY, 0,
4221 targetScrollY - mOwnScrollY);
4222 animateScroll();
4223 return true;
4224 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004225 }
4226 break;
4227 }
4228 return false;
4229 }
4230
4231 @ShadeViewRefactor(RefactorComponent.INPUT)
4232 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4233 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
4234 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
4235 View translatingParentView = mSwipeHelper.getTranslatingParentView();
4236 View view = null;
4237 if (guts != null && !guts.getGutsContent().isLeavebehind()) {
4238 // Only close visible guts if they're not a leavebehind.
4239 view = guts;
4240 } else if (menuRow != null && menuRow.isMenuVisible()
4241 && translatingParentView != null) {
4242 // Checking menu
4243 view = translatingParentView;
4244 }
4245 if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
4246 // Touch was outside visible guts / menu notification, close what's visible
4247 mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
4248 false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
4249 false /* resetMenu */);
4250 resetExposedMenuView(true /* animate */, true /* force */);
4251 }
4252 }
4253
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05004254 @ShadeViewRefactor(RefactorComponent.INPUT)
4255 private void setSwipingInProgress(boolean swiping) {
4256 mSwipingInProgress = swiping;
4257 if (swiping) {
4258 requestDisallowInterceptTouchEvent(true);
4259 }
4260 }
4261
Selim Cinek67b22602014-03-10 15:40:16 +01004262 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004263 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01004264 public void onWindowFocusChanged(boolean hasWindowFocus) {
4265 super.onWindowFocusChanged(hasWindowFocus);
4266 if (!hasWindowFocus) {
Geoffrey Pitsch409db272017-08-28 14:51:52 +00004267 cancelLongPress();
Selim Cinek67b22602014-03-10 15:40:16 +01004268 }
4269 }
Selim Cinekfab078b2014-03-27 22:45:58 +01004270
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07004271 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004272 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04004273 public void clearChildFocus(View child) {
4274 super.clearChildFocus(child);
4275 if (mForcedScroll == child) {
4276 mForcedScroll = null;
4277 }
4278 }
4279
Selim Cinekfab078b2014-03-27 22:45:58 +01004280 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004281 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekfab078b2014-03-27 22:45:58 +01004282 public boolean isScrolledToTop() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004283 if (ANCHOR_SCROLLING) {
4284 updateScrollAnchor();
4285 // TODO: once we're recycling this will need to check the adapter position of the child
4286 return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
4287 } else {
4288 return mOwnScrollY == 0;
4289 }
Selim Cinekfab078b2014-03-27 22:45:58 +01004290 }
4291
4292 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004293 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004294 public boolean isScrolledToBottom() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004295 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05004296 return getMaxPositiveScrollAmount() <= 0;
Gus Prevas0fa58d62019-01-11 13:58:40 -05004297 } else {
4298 return mOwnScrollY >= getScrollRange();
4299 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004300 }
4301
4302 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004303 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfab078b2014-03-27 22:45:58 +01004304 public View getHostView() {
4305 return this;
4306 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02004307
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004308 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004309 public int getEmptyBottomMargin() {
Selim Cinekdb167372016-11-17 15:41:17 -08004310 return Math.max(mMaxLayoutHeight - mContentHeight, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004311 }
4312
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004313 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellorc2dbe492017-03-30 13:22:03 -07004314 public void checkSnoozeLeavebehind() {
4315 if (mCheckForLeavebehind) {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04004316 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
Eliot Courtney47098cb2017-10-18 17:30:30 +09004317 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
4318 false /* resetMenu */);
Mady Mellorc2dbe492017-03-30 13:22:03 -07004319 mCheckForLeavebehind = false;
4320 }
4321 }
4322
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004323 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellorc2dbe492017-03-30 13:22:03 -07004324 public void resetCheckSnoozeLeavebehind() {
4325 mCheckForLeavebehind = true;
4326 }
4327
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004328 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek1685e632014-04-08 02:27:49 +02004329 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02004330 mIsExpansionChanging = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004331 mAmbientState.setExpansionChanging(true);
Mady Mellorc2dbe492017-03-30 13:22:03 -07004332 checkSnoozeLeavebehind();
Selim Cinek1685e632014-04-08 02:27:49 +02004333 }
4334
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004335 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek1685e632014-04-08 02:27:49 +02004336 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02004337 mIsExpansionChanging = false;
Mady Mellorc2dbe492017-03-30 13:22:03 -07004338 resetCheckSnoozeLeavebehind();
Selim Cinekd5ab6452016-12-08 16:34:00 -08004339 mAmbientState.setExpansionChanging(false);
Selim Cinek4fe3e472014-07-03 16:32:54 +02004340 if (!mIsExpanded) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004341 resetScrollPosition();
Jason Monk2a6ea9c2017-01-26 11:14:51 -05004342 mStatusBar.resetUserExpandedStates();
Selim Cinek5b1591a2017-07-03 17:05:01 +02004343 clearTemporaryViews();
4344 clearUserLockedViews();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05004345 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
Selim Cinekd4c32302018-11-19 19:43:14 -08004346 if (draggedViews.size() > 0) {
4347 draggedViews.clear();
4348 updateContinuousShadowDrawing();
4349 }
Selim Cinek5b1591a2017-07-03 17:05:01 +02004350 }
4351 }
Selim Cinekf336f4c2014-11-12 16:58:16 +01004352
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004353 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5b1591a2017-07-03 17:05:01 +02004354 private void clearUserLockedViews() {
4355 for (int i = 0; i < getChildCount(); i++) {
4356 ExpandableView child = (ExpandableView) getChildAt(i);
4357 if (child instanceof ExpandableNotificationRow) {
4358 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4359 row.setUserLocked(false);
4360 }
4361 }
4362 }
4363
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004364 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5b1591a2017-07-03 17:05:01 +02004365 private void clearTemporaryViews() {
Selim Cinek9dd0d042018-05-14 18:12:42 -07004366 // lets make sure nothing is transient anymore
Rohan Shah8ee53652018-04-05 11:13:50 -07004367 clearTemporaryViewsInGroup(this);
Selim Cinek5b1591a2017-07-03 17:05:01 +02004368 for (int i = 0; i < getChildCount(); i++) {
4369 ExpandableView child = (ExpandableView) getChildAt(i);
4370 if (child instanceof ExpandableNotificationRow) {
4371 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Rohan Shah8ee53652018-04-05 11:13:50 -07004372 clearTemporaryViewsInGroup(row.getChildrenContainer());
Selim Cinekd1395642016-04-28 12:22:42 -07004373 }
Selim Cinek4fe3e472014-07-03 16:32:54 +02004374 }
Selim Cinek1685e632014-04-08 02:27:49 +02004375 }
4376
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004377 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Rohan Shah8ee53652018-04-05 11:13:50 -07004378 private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
Selim Cinekd1395642016-04-28 12:22:42 -07004379 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
Selim Cinek81f26d32016-05-09 18:54:10 -04004380 viewGroup.removeTransientView(viewGroup.getTransientView(0));
Selim Cinekd1395642016-04-28 12:22:42 -07004381 }
4382 }
4383
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004384 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004385 public void onPanelTrackingStarted() {
4386 mPanelTracking = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004387 mAmbientState.setPanelTracking(true);
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004388 resetExposedMenuView(true /* animate */, true /* force */);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004389 }
Jason Monke59dc402018-08-16 12:05:01 -04004390
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004391 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004392 public void onPanelTrackingStopped() {
4393 mPanelTracking = false;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004394 mAmbientState.setPanelTracking(false);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004395 }
4396
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004397 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb24e0a92015-06-09 20:17:30 -07004398 public void resetScrollPosition() {
4399 mScroller.abortAnimation();
Gus Prevas0fa58d62019-01-11 13:58:40 -05004400 if (ANCHOR_SCROLLING) {
4401 // TODO: once we're recycling this will need to modify the adapter position instead
4402 mScrollAnchorView = getFirstChildNotGone();
4403 mScrollAnchorViewY = 0;
4404 updateOnScrollChange();
4405 } else {
4406 setOwnScrollY(0);
4407 }
Selim Cinekb24e0a92015-06-09 20:17:30 -07004408 }
4409
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004410 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek3d6ae232019-01-04 14:14:33 -08004411 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02004412 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02004413 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02004414 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinek3d6ae232019-01-04 14:14:33 -08004415 mAmbientState.setShadeExpanded(isExpanded);
4416 mStateAnimator.setShadeExpanded(isExpanded);
shawnlin3a950c32019-05-15 20:06:10 +08004417 mSwipeHelper.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02004418 if (changed) {
Selim Cinekaae9ad62019-07-22 18:36:54 -07004419 mWillExpand = false;
Selim Cinek9184f9c2016-02-02 17:36:53 -08004420 if (!mIsExpanded) {
4421 mGroupManager.collapseAllGroups();
Selim Cinek5b1591a2017-07-03 17:05:01 +02004422 mExpandHelper.cancelImmediately();
Selim Cinek9184f9c2016-02-02 17:36:53 -08004423 }
Selim Cinekcab4a602014-09-03 14:47:57 +02004424 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02004425 updateChronometers();
Selim Cinekf3fa6852016-12-20 18:36:02 +01004426 requestChildrenUpdate();
Selim Cinek98713a42015-09-21 15:47:20 +02004427 }
4428 }
4429
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004430 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek98713a42015-09-21 15:47:20 +02004431 private void updateChronometers() {
4432 int childCount = getChildCount();
4433 for (int i = 0; i < childCount; i++) {
4434 updateChronometerForChild(getChildAt(i));
4435 }
4436 }
4437
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004438 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek98713a42015-09-21 15:47:20 +02004439 private void updateChronometerForChild(View child) {
4440 if (child instanceof ExpandableNotificationRow) {
4441 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4442 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02004443 }
Selim Cinek1685e632014-04-08 02:27:49 +02004444 }
4445
Jorim Jaggibe565df2014-04-28 17:51:23 +02004446 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01004447 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004448 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02004449 updateScrollPositionOnExpandInBottom(view);
4450 clampScrollPosition();
Lucas Dupin60661a62018-04-12 10:50:13 -07004451 notifyHeightChangeListener(view, needsAnimation);
Selim Cinekbc243a92016-09-27 16:35:13 -07004452 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
4453 ? (ExpandableNotificationRow) view
4454 : null;
Gus Prevase2d6f042018-10-17 15:25:30 -04004455 NotificationSection firstSection = getFirstVisibleSection();
4456 ActivatableNotificationView firstVisibleChild =
4457 firstSection == null ? null : firstSection.getFirstVisibleChild();
4458 if (row != null) {
4459 if (row == firstVisibleChild
4460 || row.getNotificationParent() == firstVisibleChild) {
4461 updateAlgorithmLayoutMinHeight();
4462 }
Selim Cinekbc243a92016-09-27 16:35:13 -07004463 }
Selim Cinekb5605e52015-02-20 18:21:41 +01004464 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08004465 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01004466 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004467 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02004468 }
4469
Selim Cineka5e211b2014-08-11 17:35:48 +02004470 @Override
4471 public void onReset(ExpandableView view) {
Selim Cinek51ae05d2014-09-09 15:51:38 +02004472 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02004473 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02004474 }
4475
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004476 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekf7a14c02014-07-07 14:01:46 +02004477 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
Selim Cinek51d21972017-07-19 17:39:20 -07004478 if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02004479 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Gus Prevas0fa58d62019-01-11 13:58:40 -05004480 // TODO: once we're recycling this will need to check the adapter position of the child
Selim Cinekcb9400a2015-06-03 16:56:13 +02004481 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08004482 if (row.isSummaryWithChildren()) {
4483 return;
4484 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02004485 // We are actually expanding this view
Selim Cinek4e4cac32016-03-11 16:45:52 -08004486 float endPosition = row.getTranslationY() + row.getActualHeight();
Selim Cinek388df6d2015-10-22 13:25:11 -07004487 if (row.isChildInGroup()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08004488 endPosition += row.getNotificationParent().getTranslationY();
Selim Cinek388df6d2015-10-22 13:25:11 -07004489 }
Selim Cinekdb167372016-11-17 15:41:17 -08004490 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
Gus Prevase2d6f042018-10-17 15:25:30 -04004491 NotificationSection lastSection = getLastVisibleSection();
4492 ActivatableNotificationView lastVisibleChild =
4493 lastSection == null ? null : lastSection.getLastVisibleChild();
4494 if (row != lastVisibleChild && mShelf.getVisibility() != GONE) {
Selim Cinekdb167372016-11-17 15:41:17 -08004495 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
4496 }
4497 if (endPosition > layoutEnd) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004498 if (ANCHOR_SCROLLING) {
4499 mScrollAnchorViewY -= (endPosition - layoutEnd);
4500 updateScrollAnchor();
4501 updateOnScrollChange();
4502 } else {
4503 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
4504 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02004505 mDisallowScrollingInThisMotion = true;
4506 }
4507 }
4508 }
4509 }
4510
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004511 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggibe565df2014-04-28 17:51:23 +02004512 public void setOnHeightChangedListener(
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004513 ExpandableView.OnHeightChangedListener onHeightChangedListener) {
4514 this.mOnHeightChangedListener = onHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01004515 }
4516
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004517 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02004518 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08004519 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02004520 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07004521 runAnimationFinishedRunnables();
Selim Cinek9dd0d042018-05-14 18:12:42 -07004522 clearTransient();
Selim Cinek8fc78752016-07-13 14:34:56 -07004523 clearHeadsUpDisappearRunning();
4524 }
4525
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004526 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8fc78752016-07-13 14:34:56 -07004527 private void clearHeadsUpDisappearRunning() {
4528 for (int i = 0; i < getChildCount(); i++) {
4529 View view = getChildAt(i);
4530 if (view instanceof ExpandableNotificationRow) {
Selim Cinek76e813c2016-07-14 11:16:58 -07004531 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcafa87f2016-10-26 17:00:17 -07004532 row.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07004533 if (row.isSummaryWithChildren()) {
4534 for (ExpandableNotificationRow child : row.getNotificationChildren()) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07004535 child.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07004536 }
4537 }
Selim Cinek8fc78752016-07-13 14:34:56 -07004538 }
4539 }
Selim Cinek0fccc722015-07-29 17:04:36 -07004540 }
4541
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004542 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek9dd0d042018-05-14 18:12:42 -07004543 private void clearTransient() {
4544 for (ExpandableView view : mClearTransientViewsWhenFinished) {
4545 StackStateAnimator.removeTransientView(view);
Selim Cinek0fccc722015-07-29 17:04:36 -07004546 }
Selim Cinek9dd0d042018-05-14 18:12:42 -07004547 mClearTransientViewsWhenFinished.clear();
Selim Cinek32a59fd32015-06-10 13:54:42 -07004548 }
4549
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004550 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek32a59fd32015-06-10 13:54:42 -07004551 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004552 for (Runnable runnable : mAnimationFinishedRunnables) {
4553 runnable.run();
4554 }
4555 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02004556 }
4557
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004558 /**
4559 * See {@link AmbientState#setDimmed}.
4560 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004561 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004562 public void setDimmed(boolean dimmed, boolean animate) {
Selim Cinek8a9308d2017-08-24 09:31:08 -07004563 dimmed &= onKeyguard();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004564 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02004565 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004566 mDimmedNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004567 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08004568 animateDimmed(dimmed);
4569 } else {
4570 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004571 }
4572 requestChildrenUpdate();
4573 }
4574
Selim Cinek8a9308d2017-08-24 09:31:08 -07004575 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004576 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8a9308d2017-08-24 09:31:08 -07004577 boolean isDimmed() {
4578 return mAmbientState.isDimmed();
4579 }
4580
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004581 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd35c2792016-01-21 13:20:57 -08004582 private void setDimAmount(float dimAmount) {
4583 mDimAmount = dimAmount;
4584 updateBackgroundDimming();
4585 }
4586
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004587 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekd35c2792016-01-21 13:20:57 -08004588 private void animateDimmed(boolean dimmed) {
4589 if (mDimAnimator != null) {
4590 mDimAnimator.cancel();
4591 }
4592 float target = dimmed ? 1.0f : 0.0f;
4593 if (target == mDimAmount) {
4594 return;
4595 }
4596 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
4597 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
4598 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
4599 mDimAnimator.addListener(mDimEndListener);
4600 mDimAnimator.addUpdateListener(mDimUpdateListener);
4601 mDimAnimator.start();
4602 }
Evan Laird91d0f102018-09-18 17:39:55 -04004603
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004604 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Evan Laird91d0f102018-09-18 17:39:55 -04004605 private void setHideSensitive(boolean hideSensitive, boolean animate) {
Jorim Jaggiae441282014-08-01 02:45:18 +02004606 if (hideSensitive != mAmbientState.isHideSensitive()) {
4607 int childCount = getChildCount();
4608 for (int i = 0; i < childCount; i++) {
4609 ExpandableView v = (ExpandableView) getChildAt(i);
4610 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
4611 }
4612 mAmbientState.setHideSensitive(hideSensitive);
4613 if (animate && mAnimationsEnabled) {
4614 mHideSensitiveNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004615 mNeedsAnimation = true;
Jorim Jaggiae441282014-08-01 02:45:18 +02004616 }
Selim Cinek0b9cf462017-12-07 16:31:03 -08004617 updateContentHeight();
Jorim Jaggiae441282014-08-01 02:45:18 +02004618 requestChildrenUpdate();
4619 }
4620 }
4621
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004622 /**
4623 * See {@link AmbientState#setActivatedChild}.
4624 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004625 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka32ab602014-06-11 15:06:01 +02004626 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004627 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02004628 if (mAnimationsEnabled) {
4629 mActivateNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004630 mNeedsAnimation = true;
Jorim Jaggi75c95042014-05-16 19:09:59 +02004631 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004632 requestChildrenUpdate();
4633 }
4634
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004635 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka32ab602014-06-11 15:06:01 +02004636 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004637 return mAmbientState.getActivatedChild();
4638 }
4639
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004640 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02004641 private void applyCurrentState() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05004642 int numChildren = getChildCount();
4643 for (int i = 0; i < numChildren; i++) {
4644 ExpandableView child = (ExpandableView) getChildAt(i);
4645 child.applyViewState();
4646 }
4647
Selim Cinekf4c19962014-05-01 21:55:31 +02004648 if (mListener != null) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09004649 mListener.onChildLocationsChanged();
Selim Cinekf4c19962014-05-01 21:55:31 +02004650 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07004651 runAnimationFinishedRunnables();
Selim Cinekea66ca02016-05-24 13:33:47 -07004652 setAnimationRunning(false);
Selim Cinek6811d722016-01-19 17:53:12 -08004653 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08004654 updateViewShadows();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08004655 updateClippingToTopRoundedCorner();
Selim Cinek33223572016-02-19 19:32:22 -08004656 }
4657
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004658 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek33223572016-02-19 19:32:22 -08004659 private void updateViewShadows() {
4660 // we need to work around an issue where the shadow would not cast between siblings when
4661 // their z difference is between 0 and 0.1
4662
4663 // Lefts first sort by Z difference
4664 for (int i = 0; i < getChildCount(); i++) {
4665 ExpandableView child = (ExpandableView) getChildAt(i);
4666 if (child.getVisibility() != GONE) {
4667 mTmpSortedChildren.add(child);
4668 }
4669 }
4670 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
4671
4672 // Now lets update the shadow for the views
4673 ExpandableView previous = null;
4674 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
4675 ExpandableView expandableView = mTmpSortedChildren.get(i);
4676 float translationZ = expandableView.getTranslationZ();
4677 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
4678 float diff = otherZ - translationZ;
4679 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
4680 // There is no fake shadow to be drawn
4681 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
4682 } else {
4683 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
Mady Mellorb0a82462016-04-30 17:31:02 -07004684 expandableView.getTranslationY() - previous.getExtraBottomPadding();
4685 expandableView.setFakeShadowIntensity(
4686 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
Selim Cinek33223572016-02-19 19:32:22 -08004687 previous.getOutlineAlpha(), (int) yLocation,
4688 previous.getOutlineTranslation());
4689 }
4690 previous = expandableView;
4691 }
4692
4693 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02004694 }
4695
Lucas Dupine17ce522017-07-17 15:45:06 -07004696 /**
4697 * Update colors of "dismiss" and "empty shade" views.
4698 *
4699 * @param lightTheme True if light theme should be used.
4700 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004701 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Lucas Dupine17ce522017-07-17 15:45:06 -07004702 public void updateDecorViews(boolean lightTheme) {
4703 if (lightTheme == mUsingLightTheme) {
4704 return;
4705 }
4706 mUsingLightTheme = lightTheme;
4707 Context context = new ContextThemeWrapper(mContext,
4708 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI);
Jason Changb4e879d2018-04-11 11:17:58 +08004709 final int textColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor);
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004710 mFooterView.setTextColor(textColor);
Lucas Dupine17ce522017-07-17 15:45:06 -07004711 mEmptyShadeView.setTextColor(textColor);
4712 }
4713
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004714 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004715 public void goToFullShade(long delay) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004716 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004717 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004718 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004719 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02004720 }
4721
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004722 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02004723 public void cancelExpandHelper() {
4724 mExpandHelper.cancel();
4725 }
4726
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004727 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1408eb52014-06-02 14:45:38 +02004728 public void setIntrinsicPadding(int intrinsicPadding) {
4729 mIntrinsicPadding = intrinsicPadding;
Selim Cinek1f624952017-06-08 19:11:50 -07004730 mAmbientState.setIntrinsicPadding(intrinsicPadding);
Selim Cinek1408eb52014-06-02 14:45:38 +02004731 }
4732
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004733 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi30c305c2014-07-01 23:34:41 +02004734 public int getIntrinsicPadding() {
4735 return mIntrinsicPadding;
4736 }
4737
Selim Cinekc0ce82d2014-06-10 13:21:15 +02004738 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004739 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc0ce82d2014-06-10 13:21:15 +02004740 public boolean shouldDelayChildPressedState() {
4741 return true;
4742 }
4743
Jorim Jaggi457cc352014-06-02 22:47:42 +02004744 /**
Selim Cinekc1d9ab22019-05-21 18:08:30 -07004745 * See {@link AmbientState#setDozing}.
John Spurlockbf370992014-06-17 13:58:31 -04004746 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004747 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc1d9ab22019-05-21 18:08:30 -07004748 public void setDozing(boolean dozing, boolean animate,
4749 @Nullable PointF touchWakeUpScreenLocation) {
4750 if (mAmbientState.isDozing() == dozing) {
Adrian Roos260c1f72017-08-07 15:52:26 +02004751 return;
4752 }
Selim Cinekc1d9ab22019-05-21 18:08:30 -07004753 mAmbientState.setDozing(dozing);
Lucas Dupin8e9fa2d2018-01-29 15:36:35 -08004754 requestChildrenUpdate();
Adrian Roos7a9551a2017-01-11 12:27:49 -08004755 notifyHeightChangeListener(mShelf);
John Spurlockbf370992014-06-17 13:58:31 -04004756 }
4757
Anthony Chen3cb3ad92016-12-01 10:58:47 -08004758 /**
Selim Cinek195dfc52019-05-30 19:35:05 -07004759 * Sets the current hide amount.
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004760 *
Selim Cinek195dfc52019-05-30 19:35:05 -07004761 * @param linearHideAmount The hide amount that follows linear interpoloation in the
Jason Monke59dc402018-08-16 12:05:01 -04004762 * animation,
4763 * i.e. animates from 0 to 1 or vice-versa in a linear manner.
Selim Cinek195dfc52019-05-30 19:35:05 -07004764 * @param interpolatedHideAmount The hide amount that follows the actual interpolation of the
Jason Monke59dc402018-08-16 12:05:01 -04004765 * animation curve.
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004766 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004767 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek195dfc52019-05-30 19:35:05 -07004768 public void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
4769 mLinearHideAmount = linearHideAmount;
4770 mInterpolatedHideAmount = interpolatedHideAmount;
4771 boolean wasFullyHidden = mAmbientState.isFullyHidden();
4772 boolean wasHiddenAtAll = mAmbientState.isHiddenAtAll();
4773 mAmbientState.setHideAmount(interpolatedHideAmount);
4774 boolean nowFullyHidden = mAmbientState.isFullyHidden();
4775 boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll();
4776 if (nowFullyHidden != wasFullyHidden) {
Selim Cinekd09ec7e2019-06-26 11:34:57 -07004777 updateVisibility();
Lucas Dupin16cfe452018-02-08 13:14:50 -08004778 }
Selim Cinek195dfc52019-05-30 19:35:05 -07004779 if (!wasHiddenAtAll && nowHiddenAtAll) {
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004780 resetExposedMenuView(true /* animate */, true /* animate */);
4781 }
Selim Cinek195dfc52019-05-30 19:35:05 -07004782 if (nowFullyHidden != wasFullyHidden || wasHiddenAtAll != nowHiddenAtAll) {
Lucas Dupin64e2f572019-03-21 14:21:14 -07004783 invalidateOutline();
4784 }
Lucas Dupin60661a62018-04-12 10:50:13 -07004785 updateAlgorithmHeightAndPadding();
Selim Cinek972123d2016-05-03 14:25:58 -07004786 updateBackgroundDimming();
Lucas Dupinb561eda2018-04-09 17:25:04 -07004787 requestChildrenUpdate();
Selim Cinek33295a42019-07-26 19:49:08 -07004788 updateOwnTranslationZ();
4789 }
4790
4791 private void updateOwnTranslationZ() {
4792 // Since we are clipping to the outline we need to make sure that the shadows aren't
4793 // clipped when pulsing
4794 float ownTranslationZ = 0;
4795 if (mKeyguardBypassController.getBypassEnabled() && mAmbientState.isHiddenAtAll()) {
4796 ExpandableView firstChildNotGone = getFirstChildNotGone();
4797 if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) {
4798 ownTranslationZ = firstChildNotGone.getTranslationZ();
4799 }
4800 }
4801 setTranslationZ(ownTranslationZ);
Selim Cinek972123d2016-05-03 14:25:58 -07004802 }
4803
Selim Cinekd09ec7e2019-06-26 11:34:57 -07004804 private void updateVisibility() {
4805 boolean shouldShow = !mAmbientState.isFullyHidden() || !onKeyguard();
4806 setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
4807 }
4808
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004809 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek195dfc52019-05-30 19:35:05 -07004810 public void notifyHideAnimationStart(boolean hide) {
4811 // We only swap the scaling factor if we're fully hidden or fully awake to avoid
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004812 // interpolation issues when playing with the power button.
Selim Cinek195dfc52019-05-30 19:35:05 -07004813 if (mInterpolatedHideAmount == 0 || mInterpolatedHideAmount == 1) {
4814 mBackgroundXFactor = hide ? 1.8f : 1.5f;
4815 mHideXInterpolator = hide
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004816 ? Interpolators.FAST_OUT_SLOW_IN_REVERSE
4817 : Interpolators.FAST_OUT_SLOW_IN;
4818 }
Selim Cinek972123d2016-05-03 14:25:58 -07004819 }
4820
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004821 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004822 private int getNotGoneIndex(View child) {
4823 int count = getChildCount();
4824 int notGoneIndex = 0;
4825 for (int i = 0; i < count; i++) {
4826 View v = getChildAt(i);
4827 if (child == v) {
4828 return notGoneIndex;
4829 }
4830 if (v.getVisibility() != View.GONE) {
4831 notGoneIndex++;
4832 }
4833 }
4834 return -1;
4835 }
4836
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004837 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004838 public void setFooterView(@NonNull FooterView footerView) {
Selim Cinek01af3342016-02-09 19:25:31 -08004839 int index = -1;
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004840 if (mFooterView != null) {
4841 index = indexOfChild(mFooterView);
4842 removeView(mFooterView);
Selim Cinek01af3342016-02-09 19:25:31 -08004843 }
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004844 mFooterView = footerView;
4845 addView(mFooterView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04004846 }
4847
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004848 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004849 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08004850 int index = -1;
4851 if (mEmptyShadeView != null) {
4852 index = indexOfChild(mEmptyShadeView);
4853 removeView(mEmptyShadeView);
4854 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004855 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08004856 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004857 }
4858
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004859 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004860 public void updateEmptyShadeView(boolean visible) {
Selim Cinekd60ef9e2018-05-16 16:01:05 -07004861 mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
Lucas Dupinc9274ff2018-05-09 17:40:20 -07004862
4863 int oldTextRes = mEmptyShadeView.getTextResource();
4864 int newTextRes = mStatusBar.areNotificationsHidden()
4865 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
4866 if (oldTextRes != newTextRes) {
4867 mEmptyShadeView.setText(newTextRes);
4868 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004869 }
4870
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004871 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004872 public void updateFooterView(boolean visible, boolean showDismissView) {
4873 if (mFooterView == null) {
Anthony Chen5e3742e2017-04-07 14:28:44 -07004874 return;
4875 }
Selim Cinekd60ef9e2018-05-16 16:01:05 -07004876 boolean animate = mIsExpanded && mAnimationsEnabled;
4877 mFooterView.setVisible(visible, animate);
4878 mFooterView.setSecondaryVisible(showDismissView, animate);
Dan Sandlereceda3d2014-07-21 15:35:01 -04004879 }
4880
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004881 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dan Sandlereceda3d2014-07-21 15:35:01 -04004882 public void setDismissAllInProgress(boolean dismissAllInProgress) {
4883 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07004884 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07004885 handleDismissAllClipping();
4886 }
4887
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004888 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek9c17b772015-07-07 20:37:09 -07004889 private void handleDismissAllClipping() {
4890 final int count = getChildCount();
4891 boolean previousChildWillBeDismissed = false;
4892 for (int i = 0; i < count; i++) {
4893 ExpandableView child = (ExpandableView) getChildAt(i);
4894 if (child.getVisibility() == GONE) {
4895 continue;
4896 }
4897 if (mDismissAllInProgress && previousChildWillBeDismissed) {
4898 child.setMinClipTopAmount(child.getClipTopAmount());
4899 } else {
4900 child.setMinClipTopAmount(0);
4901 }
Ned Burns61269442019-05-02 18:27:23 -04004902 previousChildWillBeDismissed = StackScrollAlgorithm.canChildBeDismissed(child);
Selim Cinek9c17b772015-07-07 20:37:09 -07004903 }
Selim Cineka272dfe2015-02-20 18:12:28 +01004904 }
4905
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004906 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004907 public boolean isFooterViewNotGone() {
4908 return mFooterView != null
4909 && mFooterView.getVisibility() != View.GONE
4910 && !mFooterView.willBeGone();
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02004911 }
4912
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004913 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek38475442018-05-18 11:11:46 -07004914 public boolean isFooterViewContentVisible() {
4915 return mFooterView != null && mFooterView.isContentVisible();
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02004916 }
4917
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004918 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004919 public int getFooterViewHeight() {
4920 return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02004921 }
4922
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004923 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01004924 public int getEmptyShadeViewHeight() {
4925 return mEmptyShadeView.getHeight();
4926 }
4927
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004928 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggie0640dd2014-08-05 23:12:40 +02004929 public float getBottomMostNotificationBottom() {
4930 final int count = getChildCount();
4931 float max = 0;
4932 for (int childIdx = 0; childIdx < count; childIdx++) {
4933 ExpandableView child = (ExpandableView) getChildAt(childIdx);
4934 if (child.getVisibility() == GONE) {
4935 continue;
4936 }
Selim Cineka686b2c2016-10-26 13:58:27 -07004937 float bottom = child.getTranslationY() + child.getActualHeight()
4938 - child.getClipBottomAmount();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02004939 if (bottom > max) {
4940 max = bottom;
4941 }
4942 }
Selim Cinekd2281152015-04-10 14:37:46 -07004943 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02004944 }
4945
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004946 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk2a6ea9c2017-01-26 11:14:51 -05004947 public void setStatusBar(StatusBar statusBar) {
4948 this.mStatusBar = statusBar;
Selim Cinek19c8c702014-08-25 22:09:19 +02004949 }
4950
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004951 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb5605e52015-02-20 18:21:41 +01004952 public void setGroupManager(NotificationGroupManager groupManager) {
4953 this.mGroupManager = groupManager;
Kevin01a53cb2018-11-09 18:19:54 -08004954 mGroupManager.addOnGroupChangeListener(mOnGroupChangeListener);
Selim Cinek379ff8f2015-02-20 17:03:16 +01004955 }
4956
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004957 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek379ff8f2015-02-20 17:03:16 +01004958 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02004959 if (mIsExpanded && mAnimationsEnabled) {
4960 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01004961 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02004962 requestChildrenUpdate();
4963 }
4964 }
4965
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004966 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek04fb2582015-06-02 19:58:09 +02004967 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01004968 int childCount = getChildCount();
4969 for (int i = childCount - 1; i >= 0; i--) {
4970 ExpandableView child = (ExpandableView) getChildAt(i);
4971 if (child.getVisibility() != View.GONE) {
4972 float childTop = child.getY();
4973 if (childTop > touchY) {
4974 // we are above a notification entirely let's abort
4975 return false;
4976 }
Selim Cineka686b2c2016-10-26 13:58:27 -07004977 boolean belowChild = touchY > childTop + child.getActualHeight()
4978 - child.getClipBottomAmount();
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004979 if (child == mFooterView) {
Jason Monke59dc402018-08-16 12:05:01 -04004980 if (!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(),
4981 touchY - childTop)) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01004982 // We clicked on the dismiss button
4983 return false;
4984 }
4985 } else if (child == mEmptyShadeView) {
4986 // We arrived at the empty shade view, for which we accept all clicks
4987 return true;
Jason Monke59dc402018-08-16 12:05:01 -04004988 } else if (!belowChild) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01004989 // We are on a child
4990 return false;
4991 }
4992 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01004993 }
Selim Cinek04fb2582015-06-02 19:58:09 +02004994 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01004995 }
4996
Selim Cinekc22fff62016-05-20 12:44:30 -07004997 /** @hide */
4998 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004999 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07005000 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
5001 super.onInitializeAccessibilityEventInternal(event);
5002 event.setScrollable(mScrollable);
5003 event.setScrollX(mScrollX);
Selim Cinekc22fff62016-05-20 12:44:30 -07005004 event.setMaxScrollX(mScrollX);
Gus Prevas0fa58d62019-01-11 13:58:40 -05005005 if (ANCHOR_SCROLLING) {
5006 // TODO
5007 } else {
5008 event.setScrollY(mOwnScrollY);
5009 event.setMaxScrollY(getScrollRange());
5010 }
Selim Cinekc22fff62016-05-20 12:44:30 -07005011 }
5012
5013 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005014 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07005015 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
5016 super.onInitializeAccessibilityNodeInfoInternal(info);
Selim Cinekef406062016-09-29 17:33:13 -07005017 if (mScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005018 info.setScrollable(true);
Selim Cinekef406062016-09-29 17:33:13 -07005019 if (mBackwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005020 info.addAction(
5021 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
5022 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
5023 }
Selim Cinekef406062016-09-29 17:33:13 -07005024 if (mForwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005025 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
5026 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
5027 }
5028 }
Selim Cinek41fe89a2016-06-02 15:27:56 -07005029 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
5030 info.setClassName(ScrollView.class.getName());
Selim Cinekc22fff62016-05-20 12:44:30 -07005031 }
5032
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005033 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb5605e52015-02-20 18:21:41 +01005034 public void generateChildOrderChangedEvent() {
5035 if (mIsExpanded && mAnimationsEnabled) {
5036 mGenerateChildOrderChangedEvent = true;
5037 mNeedsAnimation = true;
5038 requestChildrenUpdate();
5039 }
5040 }
5041
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005042 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005043 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005044 public int getContainerChildCount() {
5045 return getChildCount();
5046 }
5047
5048 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005049 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005050 public View getContainerChildAt(int i) {
5051 return getChildAt(i);
5052 }
5053
5054 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005055 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005056 public void removeContainerView(View v) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04005057 Assert.isMainThread();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005058 removeView(v);
5059 }
5060
5061 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005062 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005063 public void addContainerView(View v) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04005064 Assert.isMainThread();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005065 addView(v);
5066 }
5067
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005068 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek684a4422015-04-15 16:18:39 -07005069 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005070 mAnimationFinishedRunnables.add(runnable);
5071 }
5072
Ned Burnsf81c4c42019-01-07 14:10:43 -05005073 public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
Evan Laird94492852018-10-25 13:43:01 -04005074 ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
5075 generateHeadsUpAnimation(row, isHeadsUp);
5076 }
5077
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005078 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005079 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
Selim Cinek5cf1d052017-06-01 17:36:46 -07005080 if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005081 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
5082 mNeedsAnimation = true;
Selim Cinekaae9ad62019-07-22 18:36:54 -07005083 if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07005084 row.setHeadsUpAnimatingAway(true);
Selim Cinek73cf02a2016-06-17 13:08:00 -07005085 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005086 requestChildrenUpdate();
5087 }
5088 }
5089
Selim Cineka59ecc32015-04-07 10:51:49 -07005090 /**
5091 * Set the boundary for the bottom heads up position. The heads up will always be above this
5092 * position.
5093 *
Jason Monke59dc402018-08-16 12:05:01 -04005094 * @param height the height of the screen
Selim Cineka59ecc32015-04-07 10:51:49 -07005095 * @param bottomBarHeight the height of the bar on the bottom
5096 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005097 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka59ecc32015-04-07 10:51:49 -07005098 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
5099 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
5100 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005101 requestChildrenUpdate();
5102 }
5103
Selim Cinekaae9ad62019-07-22 18:36:54 -07005104 @Override
5105 public void setWillExpand(boolean willExpand) {
5106 mWillExpand = willExpand;
5107 }
5108
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005109 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005110 public void setTrackingHeadsUp(ExpandableNotificationRow row) {
5111 mTrackingHeadsUp = row != null;
5112 mRoundnessManager.setTrackingHeadsUp(row);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005113 }
5114
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005115 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaac93252015-04-14 20:04:12 -07005116 public void setScrimController(ScrimController scrimController) {
5117 mScrimController = scrimController;
Lucas Dupin8da8f2e92017-04-21 14:02:16 -07005118 mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming);
Selim Cinekaac93252015-04-14 20:04:12 -07005119 }
5120
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005121 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbbc580b2015-06-03 14:11:03 +02005122 public void forceNoOverlappingRendering(boolean force) {
5123 mForceNoOverlappingRendering = force;
5124 }
5125
5126 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005127 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbbc580b2015-06-03 14:11:03 +02005128 public boolean hasOverlappingRendering() {
5129 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
5130 }
5131
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005132 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek6811d722016-01-19 17:53:12 -08005133 public void setAnimationRunning(boolean animationRunning) {
5134 if (animationRunning != mAnimationRunning) {
5135 if (animationRunning) {
Selim Cinekc383fd02016-10-21 15:31:26 -07005136 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08005137 } else {
Selim Cinekc383fd02016-10-21 15:31:26 -07005138 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08005139 }
5140 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08005141 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08005142 }
5143 }
5144
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005145 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek3776fe02016-02-04 13:32:43 -08005146 public boolean isExpanded() {
5147 return mIsExpanded;
5148 }
5149
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005150 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupin4e023812018-04-02 21:19:23 -07005151 public void setPulsing(boolean pulsing, boolean animated) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09005152 if (!mPulsing && !pulsing) {
Adrian Roosb2a87292017-02-13 15:05:03 +01005153 return;
5154 }
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005155 mPulsing = pulsing;
Selim Cinekebf42342017-07-13 15:46:10 +02005156 mAmbientState.setPulsing(pulsing);
Selim Cinekd0b48e32019-05-24 20:49:23 -07005157 mSwipeHelper.setPulsing(pulsing);
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005158 updateNotificationAnimationStates();
Lucas Dupin6bf7b642018-01-22 18:56:24 -08005159 updateAlgorithmHeightAndPadding();
Adrian Roos7a9551a2017-01-11 12:27:49 -08005160 updateContentHeight();
Adrian Roosd83e9992017-03-16 15:17:57 -07005161 requestChildrenUpdate();
Lucas Dupin4e023812018-04-02 21:19:23 -07005162 notifyHeightChangeListener(null, animated);
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005163 }
5164
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005165 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbc243a92016-09-27 16:35:13 -07005166 public void setQsExpanded(boolean qsExpanded) {
5167 mQsExpanded = qsExpanded;
5168 updateAlgorithmLayoutMinHeight();
Riddle Hsu065c01c2018-05-10 23:14:19 +08005169 updateScrollability();
Selim Cinekbc243a92016-09-27 16:35:13 -07005170 }
5171
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005172 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
shawnlin8e4e92c2018-04-12 18:47:24 +08005173 public void setQsExpansionFraction(float qsExpansionFraction) {
5174 mQsExpansionFraction = qsExpansionFraction;
5175 }
5176
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005177 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Gus Prevas0fa58d62019-01-11 13:58:40 -05005178 private void setOwnScrollY(int ownScrollY) {
5179 assert !ANCHOR_SCROLLING;
Selim Cinekef406062016-09-29 17:33:13 -07005180 if (ownScrollY != mOwnScrollY) {
Selim Cinek9212de82017-02-06 16:04:28 -08005181 // We still want to call the normal scrolled changed for accessibility reasons
5182 onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
Selim Cinekef406062016-09-29 17:33:13 -07005183 mOwnScrollY = ownScrollY;
Gus Prevas0fa58d62019-01-11 13:58:40 -05005184 updateOnScrollChange();
5185 }
5186 }
5187
5188 private void updateOnScrollChange() {
5189 updateForwardAndBackwardScrollability();
5190 requestChildrenUpdate();
5191 }
5192
5193 private void updateScrollAnchor() {
5194 int anchorIndex = indexOfChild(mScrollAnchorView);
5195 // If the anchor view has been scrolled off the top, move to the next view.
5196 while (mScrollAnchorViewY < 0) {
5197 View nextAnchor = null;
5198 for (int i = anchorIndex + 1; i < getChildCount(); i++) {
5199 View child = getChildAt(i);
5200 if (child.getVisibility() != View.GONE
5201 && child instanceof ExpandableNotificationRow) {
5202 anchorIndex = i;
5203 nextAnchor = child;
5204 break;
5205 }
5206 }
5207 if (nextAnchor == null) {
5208 break;
5209 }
5210 mScrollAnchorViewY +=
5211 (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
5212 mScrollAnchorView = nextAnchor;
5213 }
5214 // If the view above the anchor view is fully visible, make it the anchor view.
5215 while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
5216 View prevAnchor = null;
5217 for (int i = anchorIndex - 1; i >= 0; i--) {
5218 View child = getChildAt(i);
5219 if (child.getVisibility() != View.GONE
5220 && child instanceof ExpandableNotificationRow) {
5221 anchorIndex = i;
5222 prevAnchor = child;
5223 break;
5224 }
5225 }
5226 if (prevAnchor == null) {
5227 break;
5228 }
5229 float distanceToPreviousAnchor =
5230 mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
5231 if (distanceToPreviousAnchor < mScrollAnchorViewY) {
5232 mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
5233 mScrollAnchorView = prevAnchor;
5234 }
Selim Cinekef406062016-09-29 17:33:13 -07005235 }
5236 }
5237
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005238 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek281c2022016-10-13 19:14:43 -07005239 public void setShelf(NotificationShelf shelf) {
Selim Cinek281c2022016-10-13 19:14:43 -07005240 int index = -1;
5241 if (mShelf != null) {
5242 index = indexOfChild(mShelf);
5243 removeView(mShelf);
5244 }
Selim Cinek0e8d77e2016-11-29 10:35:42 -08005245 mShelf = shelf;
Selim Cinek281c2022016-10-13 19:14:43 -07005246 addView(mShelf, index);
5247 mAmbientState.setShelf(shelf);
Selim Cinekeccb5de2016-10-28 15:04:05 -07005248 mStateAnimator.setShelf(shelf);
Selim Cinekc383fd02016-10-21 15:31:26 -07005249 shelf.bind(mAmbientState, this);
Gus Prevas0fa58d62019-01-11 13:58:40 -05005250 if (ANCHOR_SCROLLING) {
5251 mScrollAnchorView = mShelf;
5252 }
Selim Cinek281c2022016-10-13 19:14:43 -07005253 }
5254
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005255 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek281c2022016-10-13 19:14:43 -07005256 public NotificationShelf getNotificationShelf() {
5257 return mShelf;
5258 }
5259
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005260 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekad7fac02016-10-18 17:09:15 -07005261 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
5262 if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
5263 mMaxDisplayedNotifications = maxDisplayedNotifications;
5264 updateContentHeight();
5265 notifyHeightChangeListener(mShelf);
5266 }
5267 }
5268
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005269 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
shawnlin8e4e92c2018-04-12 18:47:24 +08005270 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
Jason Monke59dc402018-08-16 12:05:01 -04005271 mShouldShowShelfOnly = shouldShowShelfOnly;
shawnlin8e4e92c2018-04-12 18:47:24 +08005272 updateAlgorithmLayoutMinHeight();
5273 }
5274
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005275 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek48ff9b42016-11-09 19:31:51 -08005276 public int getMinExpansionHeight() {
Selim Cinekd127d792016-11-01 19:11:41 -07005277 return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
Selim Cinek48ff9b42016-11-09 19:31:51 -08005278 }
5279
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005280 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekcafa87f2016-10-26 17:00:17 -07005281 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
5282 mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
5283 updateClipping();
5284 }
5285
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005286 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekcafa87f2016-10-26 17:00:17 -07005287 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
5288 mHeadsUpAnimatingAway = headsUpAnimatingAway;
5289 updateClipping();
5290 }
5291
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005292 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk297c04e2018-08-23 17:16:59 -04005293 @VisibleForTesting
5294 protected void setStatusBarState(int statusBarState) {
Selim Cinek355652a2016-12-07 13:32:12 -08005295 mStatusBarState = statusBarState;
5296 mAmbientState.setStatusBarState(statusBarState);
Evan Laird91d0f102018-09-18 17:39:55 -04005297 }
5298
5299 private void onStatePostChange() {
Jason Monk1fd3fc32018-08-14 17:20:09 -04005300 boolean onKeyguard = onKeyguard();
5301 boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode();
Evan Laird91d0f102018-09-18 17:39:55 -04005302
Jason Monk1fd3fc32018-08-14 17:20:09 -04005303 if (mHeadsUpAppearanceController != null) {
Selim Cinekb2c5dc52019-06-24 15:46:52 -07005304 mHeadsUpAppearanceController.onStateChanged();
Jason Monk1fd3fc32018-08-14 17:20:09 -04005305 }
5306
Beverly8fdb5332019-02-04 14:29:49 -05005307 SysuiStatusBarStateController state = (SysuiStatusBarStateController)
5308 Dependency.get(StatusBarStateController.class);
Jason Monk1fd3fc32018-08-14 17:20:09 -04005309 setHideSensitive(publicMode, state.goingToFullShade() /* animate */);
5310 setDimmed(onKeyguard, state.fromShadeLocked() /* animate */);
5311 setExpandingEnabled(!onKeyguard);
5312 ActivatableNotificationView activatedChild = getActivatedChild();
5313 setActivatedChild(null);
5314 if (activatedChild != null) {
5315 activatedChild.makeInactive(false /* animate */);
5316 }
Jason Monke59dc402018-08-16 12:05:01 -04005317 updateFooter();
Dave Mankoff57445802018-10-10 14:47:34 -04005318 requestChildrenUpdate();
Jason Monke59dc402018-08-16 12:05:01 -04005319 onUpdateRowStates();
Evan Laird91d0f102018-09-18 17:39:55 -04005320
5321 mEntryManager.updateNotifications();
Selim Cinekd09ec7e2019-06-26 11:34:57 -07005322 updateVisibility();
Selim Cinek355652a2016-12-07 13:32:12 -08005323 }
5324
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005325 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd5ab6452016-12-08 16:34:00 -08005326 public void setExpandingVelocity(float expandingVelocity) {
5327 mAmbientState.setExpandingVelocity(expandingVelocity);
5328 }
5329
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005330 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekfcff4c62016-12-27 14:26:06 +01005331 public float getOpeningHeight() {
5332 if (mEmptyShadeView.getVisibility() == GONE) {
5333 return getMinExpansionHeight();
5334 } else {
5335 return getAppearEndPosition();
5336 }
5337 }
5338
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005339 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfcff4c62016-12-27 14:26:06 +01005340 public void setIsFullWidth(boolean isFullWidth) {
5341 mAmbientState.setPanelFullWidth(isFullWidth);
5342 }
5343
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005344 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekec29d342017-05-05 18:31:49 -07005345 public void setUnlockHintRunning(boolean running) {
5346 mAmbientState.setUnlockHintRunning(running);
5347 }
5348
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005349 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek5cf1d052017-06-01 17:36:46 -07005350 public void setQsCustomizerShowing(boolean isShowing) {
5351 mAmbientState.setQsCustomizerShowing(isShowing);
5352 requestChildrenUpdate();
5353 }
5354
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005355 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek5cf1d052017-06-01 17:36:46 -07005356 public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
5357 mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
5358 }
5359
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005360 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek707e2072017-06-30 18:32:40 +02005361 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
5362 pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
shawnlin8e4e92c2018-04-12 18:47:24 +08005363 + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s"
5364 + " qsExpandFraction=%f]",
Selim Cinek707e2072017-06-30 18:32:40 +02005365 this.getClass().getSimpleName(),
Jason Monke59dc402018-08-16 12:05:01 -04005366 mPulsing ? "T" : "f",
5367 mAmbientState.isQsCustomizerShowing() ? "T" : "f",
Selim Cinek707e2072017-06-30 18:32:40 +02005368 getVisibility() == View.VISIBLE ? "visible"
5369 : getVisibility() == View.GONE ? "gone"
5370 : "invisible",
5371 getAlpha(),
shawnlin8e4e92c2018-04-12 18:47:24 +08005372 mAmbientState.getScrollY(),
5373 mMaxTopPadding,
Jason Monke59dc402018-08-16 12:05:01 -04005374 mShouldShowShelfOnly ? "T" : "f",
shawnlin8e4e92c2018-04-12 18:47:24 +08005375 mQsExpansionFraction));
Selim Cinek30887662018-10-15 17:37:21 -07005376 int childCount = getChildCount();
5377 pw.println(" Number of children: " + childCount);
5378 pw.println();
5379
5380 for (int i = 0; i < childCount; i++) {
5381 ExpandableView child = (ExpandableView) getChildAt(i);
5382 child.dump(fd, pw, args);
5383 if (!(child instanceof ExpandableNotificationRow)) {
5384 pw.println(" " + child.getClass().getSimpleName());
5385 // Notifications dump it's viewstate as part of their dump to support children
Dave Mankoffa4d195d2018-11-16 13:33:27 -05005386 ExpandableViewState viewState = child.getViewState();
Selim Cinek30887662018-10-15 17:37:21 -07005387 if (viewState == null) {
5388 pw.println(" no viewState!!!");
5389 } else {
5390 pw.print(" ");
5391 viewState.dump(fd, pw, args);
5392 pw.println();
5393 pw.println();
5394 }
5395 }
5396 }
Selim Cinek30887662018-10-15 17:37:21 -07005397 int transientViewCount = getTransientViewCount();
Selim Cinekd4c32302018-11-19 19:43:14 -08005398 pw.println(" Transient Views: " + transientViewCount);
Selim Cinek30887662018-10-15 17:37:21 -07005399 for (int i = 0; i < transientViewCount; i++) {
5400 ExpandableView child = (ExpandableView) getTransientView(i);
5401 child.dump(fd, pw, args);
5402 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05005403 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
Selim Cinekd4c32302018-11-19 19:43:14 -08005404 int draggedCount = draggedViews.size();
5405 pw.println(" Dragged Views: " + draggedCount);
5406 for (int i = 0; i < draggedCount; i++) {
5407 ExpandableView child = (ExpandableView) draggedViews.get(i);
5408 child.dump(fd, pw, args);
5409 }
Selim Cinek707e2072017-06-30 18:32:40 +02005410 }
5411
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005412 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek195dfc52019-05-30 19:35:05 -07005413 public boolean isFullyHidden() {
5414 return mAmbientState.isFullyHidden();
Lucas Dupin16cfe452018-02-08 13:14:50 -08005415 }
5416
Selim Cinek7103fd42016-05-09 22:22:33 -04005417 /**
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005418 * Add a listener whenever the expanded height changes. The first value passed as an
5419 * argument is the expanded height and the second one is the appearFraction.
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005420 *
5421 * @param listener the listener to notify.
5422 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005423 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005424 public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005425 mExpandedHeightListeners.add(listener);
5426 }
5427
5428 /**
Selim Cinek60ffea62018-03-22 13:16:44 -07005429 * Stop a listener from listening to the expandedHeight.
5430 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005431 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005432 public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
Selim Cinek60ffea62018-03-22 13:16:44 -07005433 mExpandedHeightListeners.remove(listener);
5434 }
5435
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005436 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekf0c79e12018-05-14 17:17:31 -07005437 public void setHeadsUpAppearanceController(
5438 HeadsUpAppearanceController headsUpAppearanceController) {
5439 mHeadsUpAppearanceController = headsUpAppearanceController;
5440 }
5441
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005442 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek9bfc7a52018-06-11 16:09:00 -07005443 public void setIconAreaController(NotificationIconAreaController controller) {
5444 mIconAreaController = controller;
5445 }
5446
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005447 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -04005448 public void manageNotifications(View v) {
5449 Intent intent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
5450 mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
5451 }
5452
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005453 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -04005454 private void clearNotifications(
5455 @SelectedRows int selection,
5456 boolean closeShade) {
Jason Monke59dc402018-08-16 12:05:01 -04005457 // animate-swipe all dismissable notifications, then animate the shade closed
5458 int numChildren = getChildCount();
5459
5460 final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
5461 final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
5462 for (int i = 0; i < numChildren; i++) {
5463 final View child = getChildAt(i);
5464 if (child instanceof ExpandableNotificationRow) {
5465 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
5466 boolean parentVisible = false;
5467 boolean hasClipBounds = child.getClipBounds(mTmpRect);
Ned Burns61269442019-05-02 18:27:23 -04005468 if (includeChildInDismissAll(row, selection)) {
Jason Monke59dc402018-08-16 12:05:01 -04005469 viewsToRemove.add(row);
5470 if (child.getVisibility() == View.VISIBLE
5471 && (!hasClipBounds || mTmpRect.height() > 0)) {
5472 viewsToHide.add(child);
5473 parentVisible = true;
5474 }
5475 } else if (child.getVisibility() == View.VISIBLE
5476 && (!hasClipBounds || mTmpRect.height() > 0)) {
5477 parentVisible = true;
5478 }
5479 List<ExpandableNotificationRow> children = row.getNotificationChildren();
5480 if (children != null) {
5481 for (ExpandableNotificationRow childRow : children) {
Ned Burns61269442019-05-02 18:27:23 -04005482 if (includeChildInDismissAll(row, selection)) {
5483 viewsToRemove.add(childRow);
5484 if (parentVisible && row.areChildrenExpanded()) {
5485 hasClipBounds = childRow.getClipBounds(mTmpRect);
5486 if (childRow.getVisibility() == View.VISIBLE
5487 && (!hasClipBounds || mTmpRect.height() > 0)) {
5488 viewsToHide.add(childRow);
5489 }
Jason Monke59dc402018-08-16 12:05:01 -04005490 }
5491 }
5492 }
5493 }
5494 }
5495 }
Ned Burns61269442019-05-02 18:27:23 -04005496
Jason Monke59dc402018-08-16 12:05:01 -04005497 if (viewsToRemove.isEmpty()) {
Ned Burns61269442019-05-02 18:27:23 -04005498 if (closeShade) {
5499 mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
5500 }
Jason Monke59dc402018-08-16 12:05:01 -04005501 return;
5502 }
5503
Ned Burns61269442019-05-02 18:27:23 -04005504 performDismissAllAnimations(viewsToHide, closeShade, () -> {
Jason Monke59dc402018-08-16 12:05:01 -04005505 for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
Ned Burns61269442019-05-02 18:27:23 -04005506 if (StackScrollAlgorithm.canChildBeDismissed(rowToRemove)) {
5507 if (selection == ROWS_ALL) {
5508 // TODO: This is a listener method; we shouldn't be calling it. Can we just
5509 // call performRemoveNotification as below?
5510 mEntryManager.removeNotification(
5511 rowToRemove.getEntry().key,
5512 null /* ranking */,
5513 NotificationListenerService.REASON_CANCEL_ALL);
5514 } else {
5515 mEntryManager.performRemoveNotification(
5516 rowToRemove.getEntry().notification,
5517 NotificationListenerService.REASON_CANCEL_ALL);
5518 }
Jason Monke59dc402018-08-16 12:05:01 -04005519 } else {
5520 rowToRemove.resetTranslation();
5521 }
5522 }
Ned Burns61269442019-05-02 18:27:23 -04005523 if (selection == ROWS_ALL) {
5524 try {
5525 mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
5526 } catch (Exception ex) {
5527 }
Jason Monke59dc402018-08-16 12:05:01 -04005528 }
5529 });
Jason Monke59dc402018-08-16 12:05:01 -04005530 }
5531
Ned Burns61269442019-05-02 18:27:23 -04005532 private boolean includeChildInDismissAll(
5533 ExpandableNotificationRow row,
5534 @SelectedRows int selection) {
5535 return StackScrollAlgorithm.canChildBeDismissed(row) && matchesSelection(row, selection);
5536 }
5537
5538 /**
5539 * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
5540 * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
5541 * handler.
5542 *
5543 * @param hideAnimatedList List of rows to animated away. Should only be views that are
5544 * currently visible, or else the stagger will look funky.
5545 * @param closeShade Whether to close the shade after the stagger animation completes.
5546 * @param onAnimationComplete Called after the entire animation completes (including the shade
5547 * closing if appropriate). The rows must be dismissed for real here.
5548 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005549 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -04005550 private void performDismissAllAnimations(
5551 final ArrayList<View> hideAnimatedList,
5552 final boolean closeShade,
5553 final Runnable onAnimationComplete) {
5554
5555 final Runnable onSlideAwayAnimationComplete = () -> {
5556 if (closeShade) {
5557 mShadeController.addPostCollapseAction(() -> {
5558 setDismissAllInProgress(false);
5559 onAnimationComplete.run();
5560 });
5561 mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
5562 } else {
5563 setDismissAllInProgress(false);
5564 onAnimationComplete.run();
5565 }
Jason Monke59dc402018-08-16 12:05:01 -04005566 };
5567
5568 if (hideAnimatedList.isEmpty()) {
Ned Burns61269442019-05-02 18:27:23 -04005569 onSlideAwayAnimationComplete.run();
Jason Monke59dc402018-08-16 12:05:01 -04005570 return;
5571 }
5572
5573 // let's disable our normal animations
5574 setDismissAllInProgress(true);
5575
5576 // Decrease the delay for every row we animate to give the sense of
5577 // accelerating the swipes
5578 int rowDelayDecrement = 10;
5579 int currentDelay = 140;
5580 int totalDelay = 180;
5581 int numItems = hideAnimatedList.size();
5582 for (int i = numItems - 1; i >= 0; i--) {
5583 View view = hideAnimatedList.get(i);
5584 Runnable endRunnable = null;
5585 if (i == 0) {
Ned Burns61269442019-05-02 18:27:23 -04005586 endRunnable = onSlideAwayAnimationComplete;
Jason Monke59dc402018-08-16 12:05:01 -04005587 }
Lucas Dupinfb8bdbb2018-12-02 15:09:37 -08005588 dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
Jason Monke59dc402018-08-16 12:05:01 -04005589 currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
5590 totalDelay += currentDelay;
5591 }
5592 }
5593
5594 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005595 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -04005596 protected void inflateFooterView() {
5597 FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
5598 R.layout.status_bar_notification_footer, this, false);
5599 footerView.setDismissButtonClickListener(v -> {
5600 mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
Ned Burns61269442019-05-02 18:27:23 -04005601 clearNotifications(ROWS_ALL, true /* closeShade */);
Jason Monke59dc402018-08-16 12:05:01 -04005602 });
5603 footerView.setManageButtonClickListener(this::manageNotifications);
5604 setFooterView(footerView);
5605 }
5606
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005607 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5608 private void inflateEmptyShadeView() {
Jason Monke59dc402018-08-16 12:05:01 -04005609 EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
5610 R.layout.status_bar_no_notifications, this, false);
5611 view.setText(R.string.empty_shade_text);
5612 setEmptyShadeView(view);
5613 }
5614
5615 /**
5616 * Updates expanded, dimmed and locked states of notification rows.
5617 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005618 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jason Monke59dc402018-08-16 12:05:01 -04005619 public void onUpdateRowStates() {
5620 changeViewPosition(mFooterView, -1);
5621
5622 // The following views will be moved to the end of mStackScroller. This counter represents
5623 // the offset from the last child. Initialized to 1 for the very last position. It is post-
5624 // incremented in the following "changeViewPosition" calls so that its value is correct for
5625 // subsequent calls.
5626 int offsetFromEnd = 1;
5627 changeViewPosition(mEmptyShadeView,
5628 getChildCount() - offsetFromEnd++);
5629
5630 // No post-increment for this call because it is the last one. Make sure to add one if
5631 // another "changeViewPosition" call is ever added.
5632 changeViewPosition(mShelf,
5633 getChildCount() - offsetFromEnd);
Jason Monke59dc402018-08-16 12:05:01 -04005634 }
5635
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005636 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5637 public void setNotificationPanel(NotificationPanelView notificationPanelView) {
Jason Monke59dc402018-08-16 12:05:01 -04005638 mNotificationPanel = notificationPanelView;
5639 }
5640
Jason Monk297c04e2018-08-23 17:16:59 -04005641 public void updateIconAreaViews() {
5642 mIconAreaController.updateNotificationIcons();
5643 }
5644
Selim Cinek60ffea62018-03-22 13:16:44 -07005645 /**
Selim Cinek3d6ae232019-01-04 14:14:33 -08005646 * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
5647 * notification positions accordingly.
5648 * @param height the new wake up height
5649 * @return the overflow how much the height is further than he lowest notification
5650 */
Selim Cinek5040f2e2019-02-14 18:22:42 -08005651 public float setPulseHeight(float height) {
5652 mAmbientState.setPulseHeight(height);
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005653 if (mKeyguardBypassController.getBypassEnabled()) {
5654 notifyAppearChangedListeners();
5655 }
Selim Cinek3d6ae232019-01-04 14:14:33 -08005656 requestChildrenUpdate();
5657 return Math.max(0, height - mAmbientState.getInnerHeight(true /* ignorePulseHeight */));
5658 }
5659
Selim Cinekb0fada62019-06-17 19:03:59 -07005660 public float getPulseHeight() {
5661 return mAmbientState.getPulseHeight();
5662 }
5663
Selim Cinek3d6ae232019-01-04 14:14:33 -08005664 /**
Selim Cinek195dfc52019-05-30 19:35:05 -07005665 * Set the amount how much we're dozing. This is different from how hidden the shade is, when
Selim Cinek3d6ae232019-01-04 14:14:33 -08005666 * the notification is pulsing.
5667 */
5668 public void setDozeAmount(float dozeAmount) {
5669 mAmbientState.setDozeAmount(dozeAmount);
Selim Cinekae55d832019-02-22 17:43:43 -08005670 updateContinuousBackgroundDrawing();
Selim Cinek3d6ae232019-01-04 14:14:33 -08005671 requestChildrenUpdate();
5672 }
5673
Selim Cinek459aee32019-02-20 11:18:56 -08005674 public void wakeUpFromPulse() {
Selim Cinekb0fada62019-06-17 19:03:59 -07005675 setPulseHeight(getWakeUpHeight());
Selim Cinek459aee32019-02-20 11:18:56 -08005676 // Let's place the hidden views at the end of the pulsing notification to make sure we have
5677 // a smooth animation
5678 boolean firstVisibleView = true;
5679 float wakeUplocation = -1f;
5680 int childCount = getChildCount();
5681 for (int i = 0; i < childCount; i++) {
5682 ExpandableView view = (ExpandableView) getChildAt(i);
5683 if (view.getVisibility() == View.GONE) {
5684 continue;
5685 }
5686 boolean isShelf = view == mShelf;
5687 if (!(view instanceof ExpandableNotificationRow) && !isShelf) {
5688 continue;
5689 }
5690 if (view.getVisibility() == View.VISIBLE && !isShelf) {
5691 if (firstVisibleView) {
5692 firstVisibleView = false;
5693 wakeUplocation = view.getTranslationY()
5694 + view.getActualHeight() - mShelf.getIntrinsicHeight();
5695 }
5696 } else if (!firstVisibleView) {
5697 view.setTranslationY(wakeUplocation);
5698 }
5699 }
Selim Cinekf434a742019-05-28 17:39:49 -07005700 mDimmedNeedsAnimation = true;
Selim Cinek459aee32019-02-20 11:18:56 -08005701 }
5702
Selim Cinek6f0a62a2019-04-09 18:40:12 -07005703 @Override
5704 public void onDynamicPrivacyChanged() {
5705 if (mIsExpanded) {
5706 // The bottom might change because we're using the final actual height of the view
5707 mAnimateBottomOnLayout = true;
5708 }
Selim Cinekabcc2012019-07-23 18:44:07 -07005709 // Let's update the footer once the notifications have been updated (in the next frame)
Selim Cinek5454a0d2019-07-30 17:14:50 -07005710 post(() -> {
5711 updateFooter();
5712 updateSectionBoundaries();
5713 });
Selim Cinek6f0a62a2019-04-09 18:40:12 -07005714 }
5715
Selim Cinekb0fada62019-06-17 19:03:59 -07005716 public void setOnPulseHeightChangedListener(Runnable listener) {
5717 mAmbientState.setOnPulseHeightChangedListener(listener);
5718 }
5719
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005720 public float calculateAppearFractionBypass() {
5721 float pulseHeight = getPulseHeight();
5722 float wakeUpHeight = getWakeUpHeight();
5723 float dragDownAmount = pulseHeight - wakeUpHeight;
5724
5725 // The total distance required to fully reveal the header
5726 float totalDistance = getIntrinsicPadding();
5727 return MathUtils.smoothStep(0, totalDistance, dragDownAmount);
5728 }
5729
Selim Cinek3d6ae232019-01-04 14:14:33 -08005730 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005731 * A listener that is notified when the empty space below the notifications is clicked on
5732 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005733 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005734 public interface OnEmptySpaceClickListener {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08005735 void onEmptySpaceClicked(float x, float y);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005736 }
5737
5738 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02005739 * A listener that gets notified when the overscroll at the top has changed.
5740 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005741 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi290600a2014-05-30 17:02:20 +02005742 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02005743
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005744 /**
5745 * Notifies a listener that the overscroll has changed.
5746 *
5747 * @param amount the amount of overscroll, in pixels
5748 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
5749 * unrubberbanded motion to directly expand overscroll view (e.g
5750 * expand
5751 * QS)
5752 */
5753 void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02005754
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005755 /**
5756 * Notify a listener that the scroller wants to escape from the scrolling motion and
5757 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
5758 *
5759 * @param velocity The velocity that the Scroller had when over flinging
5760 * @param open Should the fling open or close the overscroll view.
5761 */
5762 void flingTopOverscroll(float velocity, boolean open);
5763 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02005764
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005765 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5766 public boolean hasActiveNotifications() {
Jason Monke59dc402018-08-16 12:05:01 -04005767 return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
5768 }
5769
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04005770 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005771 public void updateSpeedBumpIndex() {
Jason Monke59dc402018-08-16 12:05:01 -04005772 int speedBumpIndex = 0;
5773 int currentIndex = 0;
5774 final int N = getChildCount();
5775 for (int i = 0; i < N; i++) {
5776 View view = getChildAt(i);
5777 if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) {
5778 continue;
5779 }
5780 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
5781 currentIndex++;
Gus Prevas33619af2018-10-26 15:40:27 -04005782 boolean beforeSpeedBump;
Selim Cinekda330ff2019-06-11 15:41:28 -07005783 if (mHighPriorityBeforeSpeedBump) {
Ned Burns8c1b7632019-07-19 14:26:15 -04005784 beforeSpeedBump = row.getEntry().isTopBucket();
Selim Cinekda330ff2019-06-11 15:41:28 -07005785 } else {
5786 beforeSpeedBump = !row.getEntry().ambient;
Gus Prevas33619af2018-10-26 15:40:27 -04005787 }
5788 if (beforeSpeedBump) {
Jason Monke59dc402018-08-16 12:05:01 -04005789 speedBumpIndex = currentIndex;
5790 }
5791 }
5792 boolean noAmbient = speedBumpIndex == N;
5793 updateSpeedBumpIndex(speedBumpIndex, noAmbient);
5794 }
5795
Gus Prevase2d6f042018-10-17 15:25:30 -04005796 /** Updates the indices of the boundaries between sections. */
5797 @ShadeViewRefactor(RefactorComponent.INPUT)
5798 public void updateSectionBoundaries() {
Ned Burns9eb06332019-04-23 16:02:12 -04005799 mSectionsManager.updateSectionBoundaries();
Gus Prevase2d6f042018-10-17 15:25:30 -04005800 }
5801
Selim Cinekae55d832019-02-22 17:43:43 -08005802 private void updateContinuousBackgroundDrawing() {
5803 boolean continuousBackground = !mAmbientState.isFullyAwake()
5804 && !mAmbientState.getDraggedViews().isEmpty();
5805 if (continuousBackground != mContinuousBackgroundUpdate) {
5806 mContinuousBackgroundUpdate = continuousBackground;
5807 if (continuousBackground) {
5808 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
5809 } else {
5810 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
5811 }
5812 }
5813 }
5814
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005815 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek33223572016-02-19 19:32:22 -08005816 private void updateContinuousShadowDrawing() {
5817 boolean continuousShadowUpdate = mAnimationRunning
5818 || !mAmbientState.getDraggedViews().isEmpty();
5819 if (continuousShadowUpdate != mContinuousShadowUpdate) {
5820 if (continuousShadowUpdate) {
5821 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
5822 } else {
5823 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
5824 }
Jorim Jaggi38b5ec92016-04-12 01:39:49 -07005825 mContinuousShadowUpdate = continuousShadowUpdate;
Selim Cinek33223572016-02-19 19:32:22 -08005826 }
5827 }
5828
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005829 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005830 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellor95d743c2017-01-10 12:05:27 -08005831 public void resetExposedMenuView(boolean animate, boolean force) {
5832 mSwipeHelper.resetExposedMenuView(animate, force);
Mady Mellor7a9b2a62016-03-23 07:41:47 -07005833 }
5834
Ned Burns61269442019-05-02 18:27:23 -04005835 private static boolean matchesSelection(
5836 ExpandableNotificationRow row,
5837 @SelectedRows int selection) {
5838 switch (selection) {
5839 case ROWS_ALL:
5840 return true;
5841 case ROWS_HIGH_PRIORITY:
Ned Burns8c1b7632019-07-19 14:26:15 -04005842 return row.getEntry().isTopBucket();
Ned Burns61269442019-05-02 18:27:23 -04005843 case ROWS_GENTLE:
Ned Burns8c1b7632019-07-19 14:26:15 -04005844 return !row.getEntry().isTopBucket();
Ned Burns61269442019-05-02 18:27:23 -04005845 default:
5846 throw new IllegalArgumentException("Unknown selection: " + selection);
5847 }
5848 }
5849
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005850 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5851 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02005852
Jason Monke59dc402018-08-16 12:05:01 -04005853 static AnimationFilter[] FILTERS = new AnimationFilter[]{
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005854
5855 // ANIMATION_TYPE_ADD
5856 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005857 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005858 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005859 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005860 .animateZ()
5861 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005862
5863 // ANIMATION_TYPE_REMOVE
5864 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005865 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005866 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005867 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005868 .animateZ()
5869 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005870
5871 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
5872 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005873 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005874 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005875 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005876 .animateZ()
5877 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005878
5879 // ANIMATION_TYPE_TOP_PADDING_CHANGED
5880 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005881 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005882 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005883 .animateY()
5884 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005885 .animateZ(),
5886
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005887 // ANIMATION_TYPE_ACTIVATED_CHILD
5888 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08005889 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005890
5891 // ANIMATION_TYPE_DIMMED
5892 new AnimationFilter()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005893 .animateDimmed(),
5894
5895 // ANIMATION_TYPE_CHANGE_POSITION
5896 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08005897 .animateAlpha() // maybe the children change positions
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005898 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005899 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005900 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04005901 .animateZ(),
5902
Jorim Jaggi60d07c52014-07-31 15:38:21 +02005903 // ANIMATION_TYPE_GO_TO_FULL_SHADE
5904 new AnimationFilter()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02005905 .animateHeight()
5906 .animateTopInset()
5907 .animateY()
5908 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02005909 .animateZ()
5910 .hasDelays(),
5911
5912 // ANIMATION_TYPE_HIDE_SENSITIVE
5913 new AnimationFilter()
5914 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02005915
5916 // ANIMATION_TYPE_VIEW_RESIZE
5917 new AnimationFilter()
Selim Cineka5e211b2014-08-11 17:35:48 +02005918 .animateHeight()
5919 .animateTopInset()
5920 .animateY()
5921 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02005922
Selim Cinekb5605e52015-02-20 18:21:41 +01005923 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
5924 new AnimationFilter()
5925 .animateAlpha()
5926 .animateHeight()
5927 .animateTopInset()
5928 .animateY()
5929 .animateZ(),
5930
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005931 // ANIMATION_TYPE_HEADS_UP_APPEAR
5932 new AnimationFilter()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005933 .animateHeight()
5934 .animateTopInset()
5935 .animateY()
5936 .animateZ(),
5937
5938 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
5939 new AnimationFilter()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005940 .animateHeight()
5941 .animateTopInset()
5942 .animateY()
Selim Cinek332c23f2018-03-16 17:37:50 -07005943 .animateZ()
5944 .hasDelays(),
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005945
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07005946 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
5947 new AnimationFilter()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07005948 .animateHeight()
5949 .animateTopInset()
5950 .animateY()
5951 .animateZ()
5952 .hasDelays(),
5953
Selim Cineka59ecc32015-04-07 10:51:49 -07005954 // ANIMATION_TYPE_HEADS_UP_OTHER
5955 new AnimationFilter()
Selim Cineka59ecc32015-04-07 10:51:49 -07005956 .animateHeight()
5957 .animateTopInset()
5958 .animateY()
5959 .animateZ(),
5960
Selim Cinekd9acca52014-09-01 22:33:25 +02005961 // ANIMATION_TYPE_EVERYTHING
5962 new AnimationFilter()
5963 .animateAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02005964 .animateDimmed()
5965 .animateHideSensitive()
5966 .animateHeight()
5967 .animateTopInset()
5968 .animateY()
5969 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005970 };
5971
Jason Monke59dc402018-08-16 12:05:01 -04005972 static int[] LENGTHS = new int[]{
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005973
5974 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005975 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005976
5977 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005978 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005979
5980 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
5981 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5982
5983 // ANIMATION_TYPE_TOP_PADDING_CHANGED
5984 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5985
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005986 // ANIMATION_TYPE_ACTIVATED_CHILD
5987 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
5988
5989 // ANIMATION_TYPE_DIMMED
5990 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005991
5992 // ANIMATION_TYPE_CHANGE_POSITION
5993 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04005994
Jorim Jaggi60d07c52014-07-31 15:38:21 +02005995 // ANIMATION_TYPE_GO_TO_FULL_SHADE
5996 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02005997
5998 // ANIMATION_TYPE_HIDE_SENSITIVE
5999 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02006000
6001 // ANIMATION_TYPE_VIEW_RESIZE
6002 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02006003
Selim Cinekb5605e52015-02-20 18:21:41 +01006004 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08006005 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01006006
Selim Cinekb8f09cf2015-03-16 17:09:28 -07006007 // ANIMATION_TYPE_HEADS_UP_APPEAR
6008 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
6009
6010 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
6011 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6012
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07006013 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
6014 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6015
Selim Cineka59ecc32015-04-07 10:51:49 -07006016 // ANIMATION_TYPE_HEADS_UP_OTHER
6017 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6018
Selim Cinekd9acca52014-09-01 22:33:25 +02006019 // ANIMATION_TYPE_EVERYTHING
6020 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006021 };
6022
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006023 static final int ANIMATION_TYPE_ADD = 0;
6024 static final int ANIMATION_TYPE_REMOVE = 1;
6025 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
6026 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
Selim Cinekff2ffec2018-11-19 18:52:01 -08006027 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 4;
6028 static final int ANIMATION_TYPE_DIMMED = 5;
6029 static final int ANIMATION_TYPE_CHANGE_POSITION = 6;
Selim Cinekc3fec682019-06-06 18:11:07 -07006030 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 7;
6031 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 8;
6032 static final int ANIMATION_TYPE_VIEW_RESIZE = 9;
6033 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 10;
6034 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 11;
6035 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 12;
6036 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
6037 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
6038 static final int ANIMATION_TYPE_EVERYTHING = 15;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02006039
Selim Cinek572bbd42014-04-25 16:43:27 +02006040 final long eventStartTime;
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006041 final ExpandableView mChangingView;
Selim Cinek572bbd42014-04-25 16:43:27 +02006042 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006043 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006044 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006045 View viewAfterChangingView;
Selim Cineka59ecc32015-04-07 10:51:49 -07006046 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02006047
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006048 AnimationEvent(ExpandableView view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02006049 this(view, type, LENGTHS[type]);
6050 }
6051
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006052 AnimationEvent(ExpandableView view, int type, AnimationFilter filter) {
Adrian Roos28f90c72017-05-08 17:24:26 -07006053 this(view, type, LENGTHS[type], filter);
6054 }
6055
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006056 AnimationEvent(ExpandableView view, int type, long length) {
Adrian Roos28f90c72017-05-08 17:24:26 -07006057 this(view, type, length, FILTERS[type]);
6058 }
6059
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006060 AnimationEvent(ExpandableView view, int type, long length, AnimationFilter filter) {
Selim Cinek572bbd42014-04-25 16:43:27 +02006061 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006062 mChangingView = view;
Selim Cinek572bbd42014-04-25 16:43:27 +02006063 animationType = type;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02006064 this.length = length;
Adrian Roos28f90c72017-05-08 17:24:26 -07006065 this.filter = filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006066 }
6067
6068 /**
6069 * Combines the length of several animation events into a single value.
6070 *
6071 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006072 * @return The combined length. Depending on the event types, this might be the maximum of
Jason Monke59dc402018-08-16 12:05:01 -04006073 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006074 */
6075 static long combineLength(ArrayList<AnimationEvent> events) {
6076 long length = 0;
6077 int size = events.size();
6078 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006079 AnimationEvent event = events.get(i);
6080 length = Math.max(length, event.length);
6081 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
6082 return event.length;
6083 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006084 }
6085 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02006086 }
6087 }
Jason Monke59dc402018-08-16 12:05:01 -04006088
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006089 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
6090 private final StateListener mStateListener = new StateListener() {
Jason Monke59dc402018-08-16 12:05:01 -04006091 @Override
6092 public void onStatePreChange(int oldState, int newState) {
6093 if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) {
6094 requestAnimateEverything();
6095 }
6096 }
6097
6098 @Override
6099 public void onStateChanged(int newState) {
6100 setStatusBarState(newState);
6101 }
Evan Laird91d0f102018-09-18 17:39:55 -04006102
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006103 @Override
6104 public void onStatePostChange() {
Evan Laird91d0f102018-09-18 17:39:55 -04006105 NotificationStackScrollLayout.this.onStatePostChange();
6106 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006107 };
6108
Will Brockmane718d582019-01-17 16:38:38 -05006109 @VisibleForTesting
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006110 @ShadeViewRefactor(RefactorComponent.INPUT)
Will Brockmane718d582019-01-17 16:38:38 -05006111 protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006112 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006113 public void onMenuClicked(View view, int x, int y, MenuItem item) {
6114 if (mLongPressListener == null) {
6115 return;
6116 }
6117 if (view instanceof ExpandableNotificationRow) {
6118 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Will Brockmane718d582019-01-17 16:38:38 -05006119 mMetricsLogger.write(row.getStatusBarNotification().getLogMaker()
6120 .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
6121 .setType(MetricsEvent.TYPE_ACTION)
6122 );
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006123 }
6124 mLongPressListener.onLongPress(view, x, y, item);
6125 }
6126
6127 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006128 public void onMenuReset(View row) {
6129 View translatingParentView = mSwipeHelper.getTranslatingParentView();
6130 if (translatingParentView != null && row == translatingParentView) {
6131 mSwipeHelper.clearExposedMenuView();
6132 mSwipeHelper.clearTranslatingParentView();
Gus Prevas211181532018-12-13 14:49:33 -05006133 if (row instanceof ExpandableNotificationRow) {
6134 mHeadsUpManager.setMenuShown(
6135 ((ExpandableNotificationRow) row).getEntry(), false);
6136
6137 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006138 }
6139 }
6140
6141 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006142 public void onMenuShown(View row) {
6143 if (row instanceof ExpandableNotificationRow) {
Gus Prevas211181532018-12-13 14:49:33 -05006144 ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
Will Brockmane718d582019-01-17 16:38:38 -05006145 mMetricsLogger.write(notificationRow.getStatusBarNotification().getLogMaker()
6146 .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
6147 .setType(MetricsEvent.TYPE_ACTION));
Gus Prevas211181532018-12-13 14:49:33 -05006148 mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
Evan Lairde55c6012019-03-13 12:54:37 -04006149 mSwipeHelper.onMenuShown(row);
Steve Elliottff6c25e2019-05-30 17:38:52 -04006150 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6151 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6152 false /* resetMenu */);
Evan Lairde55c6012019-03-13 12:54:37 -04006153
6154 // Check to see if we want to go directly to the notfication guts
6155 NotificationMenuRowPlugin provider = notificationRow.getProvider();
6156 if (provider.shouldShowGutsOnSnapOpen()) {
6157 MenuItem item = provider.menuItemToExposeOnSnap();
6158 if (item != null) {
6159 Point origin = provider.getRevealAnimationOrigin();
Steve Elliottff6c25e2019-05-30 17:38:52 -04006160 mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
Evan Lairde55c6012019-03-13 12:54:37 -04006161 } else {
6162 Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
6163 + "menu item in menuItemtoExposeOnSnap. Skipping.");
6164 }
6165
6166 // Close the menu row since we went directly to the guts
6167 resetExposedMenuView(false, true);
6168 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006169 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006170 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006171 };
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006172
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006173 @ShadeViewRefactor(RefactorComponent.INPUT)
6174 private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
6175 new NotificationSwipeHelper.NotificationCallback() {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006176 @Override
6177 public void onDismiss() {
6178 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6179 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6180 false /* resetMenu */);
6181 }
6182
6183 @Override
6184 public void onSnooze(StatusBarNotification sbn,
6185 NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
6186 mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
6187 }
6188
6189 @Override
Selim Cinekae55d832019-02-22 17:43:43 -08006190 public boolean shouldDismissQuickly() {
6191 return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006192 }
6193
6194 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006195 public void onDragCancelled(View v) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006196 setSwipingInProgress(false);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006197 mFalsingManager.onNotificatonStopDismissing();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006198 }
6199
6200 /**
6201 * Handles cleanup after the given {@code view} has been fully swiped out (including
6202 * re-invoking dismiss logic in case the notification has not made its way out yet).
6203 */
6204 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006205 public void onChildDismissed(View view) {
6206 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6207 if (!row.isDismissed()) {
6208 handleChildViewDismissed(view);
6209 }
6210 ViewGroup transientContainer = row.getTransientContainer();
6211 if (transientContainer != null) {
6212 transientContainer.removeTransientView(view);
6213 }
6214 }
6215
6216 /**
6217 * Starts up notification dismiss and tells the notification, if any, to remove itself from
6218 * layout.
6219 *
6220 * @param view view (e.g. notification) to dismiss from the layout
6221 */
6222
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006223 public void handleChildViewDismissed(View view) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006224 setSwipingInProgress(false);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006225 if (mDismissAllInProgress) {
6226 return;
6227 }
6228
6229 boolean isBlockingHelperShown = false;
6230
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006231 mAmbientState.onDragFinished(view);
6232 updateContinuousShadowDrawing();
6233
6234 if (view instanceof ExpandableNotificationRow) {
6235 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6236 if (row.isHeadsUp()) {
6237 mHeadsUpManager.addSwipedOutNotification(
6238 row.getStatusBarNotification().getKey());
6239 }
6240 isBlockingHelperShown =
6241 row.performDismissWithBlockingHelper(false /* fromAccessibility */);
6242 }
6243
6244 if (!isBlockingHelperShown) {
6245 mSwipedOutViews.add(view);
6246 }
6247 mFalsingManager.onNotificationDismissed();
6248 if (mFalsingManager.shouldEnforceBouncer()) {
6249 mStatusBar.executeRunnableDismissingKeyguard(
6250 null,
6251 null /* cancelAction */,
6252 false /* dismissShade */,
6253 true /* afterKeyguardGone */,
6254 false /* deferred */);
6255 }
6256 }
6257
6258 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006259 public boolean isAntiFalsingNeeded() {
6260 return onKeyguard();
6261 }
6262
6263 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006264 public View getChildAtPosition(MotionEvent ev) {
6265 View child = NotificationStackScrollLayout.this.getChildAtPosition(ev.getX(),
6266 ev.getY());
6267 if (child instanceof ExpandableNotificationRow) {
6268 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
6269 ExpandableNotificationRow parent = row.getNotificationParent();
6270 if (parent != null && parent.areChildrenExpanded()
6271 && (parent.areGutsExposed()
6272 || mSwipeHelper.getExposedMenuView() == parent
6273 || (parent.getNotificationChildren().size() == 1
Evan Laird94492852018-10-25 13:43:01 -04006274 && parent.getEntry().isClearable()))) {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006275 // In this case the group is expanded and showing the menu for the
6276 // group, further interaction should apply to the group, not any
6277 // child notifications so we use the parent of the child. We also do the same
6278 // if we only have a single child.
6279 child = parent;
6280 }
6281 }
6282 return child;
6283 }
6284
6285 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006286 public void onBeginDrag(View v) {
6287 mFalsingManager.onNotificatonStartDismissing();
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006288 setSwipingInProgress(true);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006289 mAmbientState.onBeginDrag((ExpandableView) v);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006290 updateContinuousShadowDrawing();
Selim Cinekae55d832019-02-22 17:43:43 -08006291 updateContinuousBackgroundDrawing();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006292 requestChildrenUpdate();
6293 }
6294
6295 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006296 public void onChildSnappedBack(View animView, float targetLeft) {
6297 mAmbientState.onDragFinished(animView);
6298 updateContinuousShadowDrawing();
Selim Cinekae55d832019-02-22 17:43:43 -08006299 updateContinuousBackgroundDrawing();
Selim Cineke3c6e462019-06-24 19:37:06 -07006300 if (animView instanceof ExpandableNotificationRow) {
6301 ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
6302 if (row.isPinned() && !canChildBeDismissed(row)
6303 && row.getStatusBarNotification().getNotification().fullScreenIntent
6304 == null) {
6305 mHeadsUpManager.removeNotification(row.getStatusBarNotification().getKey(),
6306 true /* removeImmediately */);
6307 }
6308 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006309 }
6310
6311 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006312 public boolean updateSwipeProgress(View animView, boolean dismissable,
6313 float swipeProgress) {
6314 // Returning true prevents alpha fading.
6315 return !mFadeNotificationsOnDismiss;
6316 }
6317
6318 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006319 public float getFalsingThresholdFactor() {
6320 return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
6321 }
6322
6323 @Override
Gus Prevasc4e68d42019-01-17 15:45:21 -05006324 public int getConstrainSwipeStartPosition() {
6325 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
6326 if (menuRow != null) {
6327 return Math.abs(menuRow.getMenuSnapTarget());
6328 }
6329 return 0;
6330 }
6331
6332 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006333 public boolean canChildBeDismissed(View v) {
Ned Burns61269442019-05-02 18:27:23 -04006334 return StackScrollAlgorithm.canChildBeDismissed(v);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006335 }
Gus Prevas37d67e22018-11-02 14:48:55 -04006336
6337 @Override
6338 public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
Evan Laird30b9b162019-04-24 15:22:24 -04006339 //TODO: b/131242807 for why this doesn't do anything with direction
6340 return canChildBeDismissed(v);
Gus Prevas37d67e22018-11-02 14:48:55 -04006341 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006342 };
6343
6344 // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
6345
6346 @ShadeViewRefactor(RefactorComponent.INPUT)
6347 private final DragDownCallback mDragDownCallback = new DragDownCallback() {
6348
6349 /* Only ever called as a consequence of a lockscreen expansion gesture. */
6350 @Override
6351 public boolean onDraggedDown(View startingChild, int dragLengthY) {
6352 if (mStatusBarState == StatusBarState.KEYGUARD
Lucas Dupin55c6e802018-09-27 18:07:36 -07006353 && hasActiveNotifications()) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006354 mLockscreenGestureLogger.write(
6355 MetricsEvent.ACTION_LS_SHADE,
6356 (int) (dragLengthY / mDisplayMetrics.density),
6357 0 /* velocityDp - N/A */);
6358
Selim Cinekc1d9ab22019-05-21 18:08:30 -07006359 if (!mAmbientState.isDozing() || startingChild != null) {
Lucas Dupin55c6e802018-09-27 18:07:36 -07006360 // We have notifications, go to locked shade.
Jason Monk297c04e2018-08-23 17:16:59 -04006361 mShadeController.goToLockedShade(startingChild);
Lucas Dupin55c6e802018-09-27 18:07:36 -07006362 if (startingChild instanceof ExpandableNotificationRow) {
6363 ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
6364 row.onExpandedByGesture(true /* drag down is always an open */);
6365 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006366 }
Lucas Dupin55c6e802018-09-27 18:07:36 -07006367
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006368 return true;
Selim Cinek5454a0d2019-07-30 17:14:50 -07006369 } else if (mDynamicPrivacyController.isInLockedDownShade()) {
6370 mStatusbarStateController.setLeaveOpenOnKeyguardHide(true);
6371 mStatusBar.dismissKeyguardThenExecute(() -> false /* dismissAction */,
6372 null /* cancelRunnable */, false /* afterKeyguardGone */);
6373 return true;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006374 } else {
6375 // abort gesture.
6376 return false;
6377 }
6378 }
6379
6380 @Override
6381 public void onDragDownReset() {
6382 setDimmed(true /* dimmed */, true /* animated */);
6383 resetScrollPosition();
6384 resetCheckSnoozeLeavebehind();
6385 }
6386
6387 @Override
6388 public void onCrossedThreshold(boolean above) {
6389 setDimmed(!above /* dimmed */, true /* animate */);
6390 }
6391
6392 @Override
6393 public void onTouchSlopExceeded() {
6394 cancelLongPress();
6395 checkSnoozeLeavebehind();
6396 }
6397
6398 @Override
6399 public void setEmptyDragAmount(float amount) {
6400 mNotificationPanel.setEmptyDragAmount(amount);
6401 }
6402
6403 @Override
6404 public boolean isFalsingCheckNeeded() {
6405 return mStatusBarState == StatusBarState.KEYGUARD;
6406 }
Selim Cinek5454a0d2019-07-30 17:14:50 -07006407
6408 @Override
6409 public boolean isDragDownEnabledForView(ExpandableView view) {
6410 if (isDragDownAnywhereEnabled()) {
6411 return true;
6412 }
6413 if (mDynamicPrivacyController.isInLockedDownShade()) {
6414 if (view == null) {
6415 // Dragging down is allowed in general
6416 return true;
6417 }
6418 if (view instanceof ExpandableNotificationRow) {
6419 // Only drag down on sensitive views, otherwise the ExpandHelper will take this
6420 return ((ExpandableNotificationRow) view).getEntry().isSensitive();
6421 }
6422 }
6423 return false;
6424 }
6425
6426 @Override
6427 public boolean isDragDownAnywhereEnabled() {
6428 return mStatusbarStateController.getState() == StatusBarState.KEYGUARD
6429 && !mKeyguardBypassController.getBypassEnabled();
6430 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006431 };
6432
6433 public DragDownCallback getDragDownCallback() { return mDragDownCallback; }
6434
6435 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6436 private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() {
6437 @Override
6438 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6439 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6440 }
6441
6442 @Override
6443 public boolean isExpanded() {
6444 return mIsExpanded;
6445 }
6446
6447 @Override
6448 public Context getContext() {
6449 return mContext;
6450 }
6451 };
6452
6453 public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
6454
6455
6456 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6457 private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
6458 @Override
6459 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
6460 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
6461 && (mIsExpanded || changedRow.isPinned());
6462 if (animated) {
6463 mExpandedGroupView = changedRow;
6464 mNeedsAnimation = true;
6465 }
6466 changedRow.setChildrenExpanded(expanded, animated);
6467 if (!mGroupExpandedForMeasure) {
6468 onHeightChanged(changedRow, false /* needsAnimation */);
6469 }
6470 runAfterAnimationFinished(new Runnable() {
6471 @Override
6472 public void run() {
6473 changedRow.onFinishedExpansionChange();
6474 }
6475 });
6476 }
6477
6478 @Override
6479 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
6480 mStatusBar.requestNotificationUpdate();
6481 }
6482
6483 @Override
6484 public void onGroupsChanged() {
6485 mStatusBar.requestNotificationUpdate();
6486 }
6487 };
6488
6489 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6490 private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
6491 @Override
6492 public ExpandableView getChildAtPosition(float touchX, float touchY) {
6493 return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY);
6494 }
6495
6496 @Override
6497 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6498 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6499 }
6500
6501 @Override
6502 public boolean canChildBeExpanded(View v) {
6503 return v instanceof ExpandableNotificationRow
6504 && ((ExpandableNotificationRow) v).isExpandable()
6505 && !((ExpandableNotificationRow) v).areGutsExposed()
6506 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
6507 }
6508
6509 /* Only ever called as a consequence of an expansion gesture in the shade. */
6510 @Override
6511 public void setUserExpandedChild(View v, boolean userExpanded) {
6512 if (v instanceof ExpandableNotificationRow) {
6513 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
6514 if (userExpanded && onKeyguard()) {
6515 // Due to a race when locking the screen while touching, a notification may be
6516 // expanded even after we went back to keyguard. An example of this happens if
6517 // you click in the empty space while expanding a group.
6518
6519 // We also need to un-user lock it here, since otherwise the content height
6520 // calculated might be wrong. We also can't invert the two calls since
6521 // un-userlocking it will trigger a layout switch in the content view.
6522 row.setUserLocked(false);
6523 updateContentHeight();
6524 notifyHeightChangeListener(row);
6525 return;
6526 }
6527 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
6528 row.onExpandedByGesture(userExpanded);
6529 }
6530 }
6531
6532 @Override
6533 public void setExpansionCancelled(View v) {
6534 if (v instanceof ExpandableNotificationRow) {
6535 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
6536 }
6537 }
6538
6539 @Override
6540 public void setUserLockedChild(View v, boolean userLocked) {
6541 if (v instanceof ExpandableNotificationRow) {
6542 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
6543 }
6544 cancelLongPress();
6545 requestDisallowInterceptTouchEvent(true);
6546 }
6547
6548 @Override
6549 public void expansionStateChanged(boolean isExpanding) {
6550 mExpandingNotification = isExpanding;
6551 if (!mExpandedInThisMotion) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05006552 if (ANCHOR_SCROLLING) {
6553 // TODO
6554 } else {
6555 mMaxScrollAfterExpand = mOwnScrollY;
6556 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006557 mExpandedInThisMotion = true;
6558 }
6559 }
6560
6561 @Override
6562 public int getMaxExpandHeight(ExpandableView view) {
6563 return view.getMaxContentHeight();
6564 }
6565 };
6566
6567 public ExpandHelper.Callback getExpandHelperCallback() {
6568 return mExpandHelperCallback;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006569 }
Ned Burns61269442019-05-02 18:27:23 -04006570
6571 /** Enum for selecting some or all notification rows (does not included non-notif views). */
6572 @Retention(SOURCE)
6573 @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
6574 public @interface SelectedRows {}
6575 /** All rows representing notifs. */
6576 public static final int ROWS_ALL = 0;
6577 /** Only rows where entry.isHighPriority() is true. */
6578 public static final int ROWS_HIGH_PRIORITY = 1;
6579 /** Only rows where entry.isHighPriority() is false. */
6580 public static final int ROWS_GENTLE = 2;
Selim Cinek67b22602014-03-10 15:40:16 +01006581}