blob: 1ccc2bde2288a97e3a9028910705043ae3a0e386 [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
Beverly7e0d6492020-02-07 16:22:14 -050019import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
20import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
21
Gus Prevas59ec2ff2018-12-28 16:20:28 -050022import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
Gus Prevase83700cb2018-12-14 11:42:51 -050023import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
Evan Laird25f02752019-08-14 19:25:06 -040024import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
Gus Prevas0fa58d62019-01-11 13:58:40 -050025import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
Gus Prevase83700cb2018-12-14 11:42:51 -050026import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
Selim Cinekda330ff2019-06-11 15:41:28 -070027import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
Gus Prevas59ec2ff2018-12-28 16:20:28 -050028import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
Selim Cinek2627d722018-01-19 12:16:49 -080029
Ned Burns61269442019-05-02 18:27:23 -040030import static java.lang.annotation.RetentionPolicy.SOURCE;
31
Selim Cinek614576e2016-01-20 10:54:09 -080032import android.animation.Animator;
33import android.animation.AnimatorListenerAdapter;
Selim Cinekd35c2792016-01-21 13:20:57 -080034import android.animation.TimeAnimator;
35import android.animation.ValueAnimator;
Steve Elliott577ef792020-04-02 13:49:36 -040036import android.annotation.ColorInt;
Ned Burns61269442019-05-02 18:27:23 -040037import android.annotation.IntDef;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040038import android.annotation.NonNull;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010039import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010040import android.content.Context;
Jason Monke59dc402018-08-16 12:05:01 -040041import android.content.Intent;
Selim Cinek67b22602014-03-10 15:40:16 +010042import android.content.res.Configuration;
Anthony Chen3cb3ad92016-12-01 10:58:47 -080043import android.content.res.Resources;
Selim Cinek67b22602014-03-10 15:40:16 +010044import android.graphics.Canvas;
Lucas Dupind285cf02018-01-18 09:18:23 -080045import android.graphics.Color;
Lucas Dupin64e2f572019-03-21 14:21:14 -070046import android.graphics.Outline;
Selim Cinek67b22602014-03-10 15:40:16 +010047import android.graphics.Paint;
Evan Lairde55c6012019-03-13 12:54:37 -040048import android.graphics.Point;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010049import android.graphics.PointF;
Selim Cinek6811d722016-01-19 17:53:12 -080050import android.graphics.PorterDuff;
51import android.graphics.PorterDuffXfermode;
52import android.graphics.Rect;
Selim Cinekc22fff62016-05-20 12:44:30 -070053import android.os.Bundle;
Jason Monke59dc402018-08-16 12:05:01 -040054import android.os.ServiceManager;
Julia Reynolds1f94a9d2020-05-05 12:40:21 -040055import android.os.UserHandle;
Jason Monke59dc402018-08-16 12:05:01 -040056import android.provider.Settings;
Mady Mellorc2ff0112019-03-28 14:18:06 -070057import android.service.notification.NotificationListenerService;
Mady Mellor95d743c2017-01-10 12:05:27 -080058import android.service.notification.StatusBarNotification;
Selim Cinek67b22602014-03-10 15:40:16 +010059import android.util.AttributeSet;
Jason Monke59dc402018-08-16 12:05:01 -040060import android.util.DisplayMetrics;
Selim Cinek67b22602014-03-10 15:40:16 +010061import android.util.Log;
Lucas Dupind285cf02018-01-18 09:18:23 -080062import android.util.MathUtils;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070063import android.util.Pair;
Lucas Dupine17ce522017-07-17 15:45:06 -070064import android.view.ContextThemeWrapper;
shawnlinbd6d0ad2020-01-29 18:38:49 +080065import android.view.DisplayCutout;
Selim Cinek11e33232016-08-05 15:30:53 -070066import android.view.InputDevice;
Jason Monke59dc402018-08-16 12:05:01 -040067import android.view.LayoutInflater;
Selim Cinek67b22602014-03-10 15:40:16 +010068import android.view.MotionEvent;
69import android.view.VelocityTracker;
70import android.view.View;
71import android.view.ViewConfiguration;
72import android.view.ViewGroup;
Lucas Dupin64e2f572019-03-21 14:21:14 -070073import android.view.ViewOutlineProvider;
Selim Cinek343e6e22014-04-11 21:23:30 +020074import android.view.ViewTreeObserver;
Adrian Roos5153d4a2016-03-22 10:01:56 -070075import android.view.WindowInsets;
Selim Cinekc22fff62016-05-20 12:44:30 -070076import android.view.accessibility.AccessibilityEvent;
77import android.view.accessibility.AccessibilityNodeInfo;
Selim Cinek572bbd42014-04-25 16:43:27 +020078import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080079import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010080import android.widget.OverScroller;
Selim Cinek41fe89a2016-06-02 15:27:56 -070081import android.widget.ScrollView;
Lucas Dupin8da8f2e92017-04-21 14:02:16 -070082
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040083import com.android.internal.annotations.VisibleForTesting;
84import com.android.internal.graphics.ColorUtils;
Mady Mellora41587b2016-02-11 18:43:06 -080085import com.android.internal.logging.MetricsLogger;
Will Brockmanb65faa82020-03-06 10:26:04 -050086import com.android.internal.logging.UiEvent;
87import com.android.internal.logging.UiEventLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010088import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Jason Monke59dc402018-08-16 12:05:01 -040089import com.android.internal.statusbar.IStatusBarService;
Beverly7e0d6492020-02-07 16:22:14 -050090import com.android.internal.statusbar.NotificationVisibility;
Lucas Dupin4e023812018-04-02 21:19:23 -070091import com.android.keyguard.KeyguardSliceView;
Lucas Dupine17ce522017-07-17 15:45:06 -070092import com.android.settingslib.Utils;
Rohan Shah524cf7b2018-03-15 14:40:02 -070093import com.android.systemui.Dependency;
Jason Monke59dc402018-08-16 12:05:01 -040094import com.android.systemui.Dumpable;
Selim Cinek67b22602014-03-10 15:40:16 +010095import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080096import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010097import com.android.systemui.R;
98import com.android.systemui.SwipeHelper;
Jason Monke59dc402018-08-16 12:05:01 -040099import com.android.systemui.colorextraction.SysuiColorExtractor;
Dave Mankoff468d4f62019-05-08 14:56:29 -0400100import com.android.systemui.plugins.FalsingManager;
Mady Mellor95d743c2017-01-10 12:05:27 -0800101import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
102import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400103import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
Mady Mellor95d743c2017-01-10 12:05:27 -0800104import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
Beverly8fdb5332019-02-04 14:29:49 -0500105import com.android.systemui.plugins.statusbar.StatusBarStateController;
106import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
Jason Monke59dc402018-08-16 12:05:01 -0400107import com.android.systemui.statusbar.CommandQueue;
108import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200109import com.android.systemui.statusbar.EmptyShadeView;
Beverly7e0d6492020-02-07 16:22:14 -0500110import com.android.systemui.statusbar.FeatureFlags;
Jason Monk297c04e2018-08-23 17:16:59 -0400111import com.android.systemui.statusbar.NotificationLockscreenUserManager;
Lucas Dupin28790c02020-01-30 14:18:41 -0800112import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
Jason Monk297c04e2018-08-23 17:16:59 -0400113import com.android.systemui.statusbar.NotificationRemoteInputManager;
114import com.android.systemui.statusbar.NotificationShelf;
115import com.android.systemui.statusbar.RemoteInputController;
116import com.android.systemui.statusbar.StatusBarState;
Beverly8fdb5332019-02-04 14:29:49 -0500117import com.android.systemui.statusbar.SysuiStatusBarStateController;
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700118import com.android.systemui.statusbar.notification.DynamicPrivacyController;
Jason Monk297c04e2018-08-23 17:16:59 -0400119import com.android.systemui.statusbar.notification.FakeShadowView;
Evan Laird04373662020-01-24 17:37:39 -0500120import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
Julia Reynolds43689322020-04-28 18:48:54 -0400121import com.android.systemui.statusbar.notification.NotificationActivityStarter;
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500122import com.android.systemui.statusbar.notification.NotificationEntryListener;
Jason Monk297c04e2018-08-23 17:16:59 -0400123import com.android.systemui.statusbar.notification.NotificationEntryManager;
124import com.android.systemui.statusbar.notification.NotificationUtils;
125import com.android.systemui.statusbar.notification.ShadeViewRefactor;
126import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
Jason Monk297c04e2018-08-23 17:16:59 -0400127import com.android.systemui.statusbar.notification.VisualStabilityManager;
Beverly7e0d6492020-02-07 16:22:14 -0500128import com.android.systemui.statusbar.notification.collection.NotifCollection;
129import com.android.systemui.statusbar.notification.collection.NotifPipeline;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500130import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Beverly7e0d6492020-02-07 16:22:14 -0500131import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
132import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
Jason Monk297c04e2018-08-23 17:16:59 -0400133import com.android.systemui.statusbar.notification.logging.NotificationLogger;
134import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
Rohan Shah20790b82018-07-02 17:21:04 -0700135import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
136import com.android.systemui.statusbar.notification.row.ExpandableView;
137import com.android.systemui.statusbar.notification.row.FooterView;
Evan Laird04373662020-01-24 17:37:39 -0500138import com.android.systemui.statusbar.notification.row.ForegroundServiceDungeonView;
Rohan Shah20790b82018-07-02 17:21:04 -0700139import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
Rohan Shah20790b82018-07-02 17:21:04 -0700140import com.android.systemui.statusbar.notification.row.NotificationGuts;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400141import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
Rohan Shah20790b82018-07-02 17:21:04 -0700142import com.android.systemui.statusbar.notification.row.NotificationSnooze;
143import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700144import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900145import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400146import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
Selim Cinekc3fec682019-06-06 18:11:07 -0700147import com.android.systemui.statusbar.phone.KeyguardBypassController;
Jason Monke59dc402018-08-16 12:05:01 -0400148import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
Selim Cinekb5605e52015-02-20 18:21:41 +0100149import com.android.systemui.statusbar.phone.NotificationGroupManager;
Jason Monke59dc402018-08-16 12:05:01 -0400150import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700151import com.android.systemui.statusbar.phone.NotificationIconAreaController;
Dave Mankoffaf8163f2020-01-08 14:24:35 -0500152import com.android.systemui.statusbar.phone.NotificationPanelViewController;
Selim Cinekaac93252015-04-14 20:04:12 -0700153import com.android.systemui.statusbar.phone.ScrimController;
Jason Monk297c04e2018-08-23 17:16:59 -0400154import com.android.systemui.statusbar.phone.ShadeController;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800155import com.android.systemui.statusbar.phone.StatusBar;
Jason Monke59dc402018-08-16 12:05:01 -0400156import com.android.systemui.statusbar.policy.ConfigurationController;
157import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900158import com.android.systemui.statusbar.policy.HeadsUpUtil;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100159import com.android.systemui.statusbar.policy.ScrollAdapter;
Beverly482ad6a2019-11-06 16:57:05 -0500160import com.android.systemui.statusbar.policy.ZenModeController;
Gus Prevas33619af2018-10-26 15:40:27 -0400161import com.android.systemui.tuner.TunerService;
Ned Burnsd4a69f72019-06-19 19:49:19 -0400162import com.android.systemui.util.Assert;
Selim Cinek67b22602014-03-10 15:40:16 +0100163
Selim Cinek707e2072017-06-30 18:32:40 +0200164import java.io.FileDescriptor;
165import java.io.PrintWriter;
Ned Burns61269442019-05-02 18:27:23 -0400166import java.lang.annotation.Retention;
Selim Cinek572bbd42014-04-25 16:43:27 +0200167import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -0800168import java.util.Collections;
169import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200170import java.util.HashSet;
Selim Cinekef8c2252017-02-10 14:52:18 -0800171import java.util.List;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800172import java.util.function.BiConsumer;
Selim Cinek572bbd42014-04-25 16:43:27 +0200173
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500174import javax.inject.Inject;
175import javax.inject.Named;
176
Selim Cinek67b22602014-03-10 15:40:16 +0100177/**
178 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
179 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400180public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700181 NotificationListContainer, ConfigurationListener, Dumpable,
182 DynamicPrivacyController.Listener {
Selim Cinek67b22602014-03-10 15:40:16 +0100183
Selim Cinekd35c2792016-01-21 13:20:57 -0800184 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -0800185 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +0100186 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200187 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
188 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200189 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +0100190 /**
191 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
192 */
Lucas Dupind285cf02018-01-18 09:18:23 -0800193 private static final int INVALID_POINTER = -1;
Gus Prevase2d6f042018-10-17 15:25:30 -0400194 /**
195 * The distance in pixels between sections when the sections are directly adjacent (no visible
196 * gap is drawn between them). In this case we don't want to round their corners.
197 */
198 private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
Selim Cinekc3fec682019-06-06 18:11:07 -0700199 private final KeyguardBypassController mKeyguardBypassController;
Selim Cinek5454a0d2019-07-30 17:14:50 -0700200 private final DynamicPrivacyController mDynamicPrivacyController;
201 private final SysuiStatusBarStateController mStatusbarStateController;
Selim Cinek67b22602014-03-10 15:40:16 +0100202
Selim Cinek1408eb52014-06-02 14:45:38 +0200203 private ExpandHelper mExpandHelper;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400204 private final NotificationSwipeHelper mSwipeHelper;
Selim Cinek67b22602014-03-10 15:40:16 +0100205 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800206 private final Paint mBackgroundPaint = new Paint();
Adrian Roosf0b4f962017-05-25 11:53:11 -0700207 private final boolean mShouldDrawNotificationBackground;
Selim Cinekda330ff2019-06-11 15:41:28 -0700208 private boolean mHighPriorityBeforeSpeedBump;
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500209 private final boolean mAllowLongPress;
Gus Prevasa18dc572019-01-14 16:11:22 -0500210 private boolean mDismissRtl;
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100211
Selim Cinekbc243a92016-09-27 16:35:13 -0700212 private float mExpandedHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100213 private int mOwnScrollY;
Gus Prevas0fa58d62019-01-11 13:58:40 -0500214 private View mScrollAnchorView;
215 private int mScrollAnchorViewY;
Selim Cinek67b22602014-03-10 15:40:16 +0100216 private int mMaxLayoutHeight;
217
218 private VelocityTracker mVelocityTracker;
219 private OverScroller mScroller;
Gus Prevascdc98342019-01-14 14:29:44 -0500220 /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
221 private int mLastScrollerY;
222 /**
223 * True if the max position was set to a known position on the last call to {@link #mScroller}.
224 */
225 private boolean mIsScrollerBoundSet;
Ricky Waicd35def2016-05-03 11:07:07 +0100226 private Runnable mFinishScrollingCallback;
Selim Cinek67b22602014-03-10 15:40:16 +0100227 private int mTouchSlop;
Philip Quinn47169132020-03-23 19:04:55 -0700228 private float mSlopMultiplier;
Selim Cinek67b22602014-03-10 15:40:16 +0100229 private int mMinimumVelocity;
230 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100231 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200232 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100233 private boolean mIsBeingDragged;
234 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200235 private int mDownX;
Dong-wan Kimb9266662016-09-21 13:08:30 -0700236 private int mActivePointerId = INVALID_POINTER;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100237 private boolean mTouchIsClick;
238 private float mInitialTouchX;
239 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100240
Selim Cinek67b22602014-03-10 15:40:16 +0100241 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100242 private int mContentHeight;
Lucas Dupin60661a62018-04-12 10:50:13 -0700243 private int mIntrinsicContentHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100244 private int mCollapsedSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100245 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800246 private int mIncreasedPaddingBetweenElements;
shawnlin8e4e92c2018-04-12 18:47:24 +0800247 private int mMaxTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200248 private int mTopPadding;
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700249 private int mBottomMargin;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700250 private int mBottomInset = 0;
shawnlin8e4e92c2018-04-12 18:47:24 +0800251 private float mQsExpansionFraction;
Selim Cinek67b22602014-03-10 15:40:16 +0100252
253 /**
254 * The algorithm which calculates the properties for our children
255 */
Muyuan Li87798022016-04-07 17:51:25 -0700256 protected final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100257
Selim Cinek281c2022016-10-13 19:14:43 -0700258 private final AmbientState mAmbientState;
Selim Cinekb5605e52015-02-20 18:21:41 +0100259 private NotificationGroupManager mGroupManager;
Julia Reynolds43689322020-04-28 18:48:54 -0400260 private NotificationActivityStarter mNotificationActivityStarter;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500261 private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700262 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500263 private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
264 private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200265 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700266 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
267 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200268 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200269 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200270 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800271 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200272
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200273 /**
274 * The raw amount of the overScroll on the top, which is not rubber-banded.
275 */
276 private float mOverScrolledTopPixels;
277
278 /**
279 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
280 */
281 private float mOverScrolledBottomPixels;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900282 private NotificationLogger.OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200283 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200284 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100285 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200286 private boolean mNeedsAnimation;
287 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200288 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200289 private boolean mHideSensitiveNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200290 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200291 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200292 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200293 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200294 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700295 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200296 private boolean mExpandingNotification;
297 private boolean mExpandedInThisMotion;
shawnlin8e4e92c2018-04-12 18:47:24 +0800298 private boolean mShouldShowShelfOnly;
Muyuan Li84b45612016-06-01 11:05:08 -0700299 protected boolean mScrollingEnabled;
Julia Reynoldsed1c9af2018-03-21 15:21:09 -0400300 protected FooterView mFooterView;
Muyuan Lidd9ae752016-05-13 16:45:43 -0700301 protected EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400302 private boolean mDismissAllInProgress;
Anthony Chen7acbb772017-04-07 16:45:25 -0700303 private boolean mFadeNotificationsOnDismiss;
Selim Cinek1408eb52014-06-02 14:45:38 +0200304
305 /**
306 * Was the scroller scrolled to the top when the down motion was observed?
307 */
308 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200309 /**
310 * The minimal amount of over scroll which is needed in order to switch to the quick settings
311 * when over scrolling on a expanded card.
312 */
313 private float mMinTopOverScrollToEscape;
314 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700315 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200316 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200317 private boolean mDontReportNextOverScroll;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700318 private boolean mDontClampNextScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200319 private boolean mNeedViewResizeAnimation;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500320 private ExpandableView mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700321 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700322
Selim Cinek1408eb52014-06-02 14:45:38 +0200323 /**
324 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
325 * This is needed to avoid scrolling too far after the notification was collapsed in the same
326 * motion.
327 */
328 private int mMaxScrollAfterExpand;
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000329 private ExpandableNotificationRow.LongPressListener mLongPressListener;
Mady Mellorc2dbe492017-03-30 13:22:03 -0700330 boolean mCheckForLeavebehind;
Selim Cinek1408eb52014-06-02 14:45:38 +0200331
332 /**
333 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
334 * animating.
335 */
336 private boolean mOnlyScrollingInThisMotion;
Adrian Roosfa139752016-04-27 09:59:08 -0700337 private boolean mDisallowDismissInThisMotion;
Selim Cineka59ecc32015-04-07 10:51:49 -0700338 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700339 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200340 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200341 = new ViewTreeObserver.OnPreDrawListener() {
342 @Override
343 public boolean onPreDraw() {
Adrian Roos181385c2016-05-05 17:45:44 -0400344 updateForcedScroll();
Selim Cinek1f553cf2014-05-02 12:01:36 +0200345 updateChildren();
346 mChildrenUpdateRequested = false;
347 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200348 return true;
349 }
350 };
Lucas Dupin28790c02020-01-30 14:18:41 -0800351 private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() {
352 @Override
353 public void onUserChanged(int userId) {
354 updateSensitiveness(false /* animated */);
355 }
356 };
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500357 private StatusBar mStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100358 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100359 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700360 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek9dd0d042018-05-14 18:12:42 -0700361 private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700362 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
363 = new HashSet<>();
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900364 private HeadsUpManagerPhone mHeadsUpManager;
Lucas Dupin00be88f2019-01-03 17:50:52 -0800365 private final NotificationRoundnessManager mRoundnessManager;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700366 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700367 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200368 private boolean mForceNoOverlappingRendering;
Selim Cineke0890e52015-06-17 11:17:08 -0700369 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700370 private FalsingManager mFalsingManager;
Beverly482ad6a2019-11-06 16:57:05 -0500371 private final ZenModeController mZenController;
Selim Cinek6811d722016-01-19 17:53:12 -0800372 private boolean mAnimationRunning;
Selim Cinekc383fd02016-10-21 15:31:26 -0700373 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
Selim Cinek6811d722016-01-19 17:53:12 -0800374 = new ViewTreeObserver.OnPreDrawListener() {
375 @Override
376 public boolean onPreDraw() {
Selim Cinekc383fd02016-10-21 15:31:26 -0700377 onPreDrawDuringAnimation();
Selim Cinek6811d722016-01-19 17:53:12 -0800378 return true;
379 }
380 };
Evan Laird25f02752019-08-14 19:25:06 -0400381 private NotificationSection[] mSections;
Selim Cinek614576e2016-01-20 10:54:09 -0800382 private boolean mAnimateNextBackgroundTop;
Gus Prevase2d6f042018-10-17 15:25:30 -0400383 private boolean mAnimateNextBackgroundBottom;
384 private boolean mAnimateNextSectionBoundsChange;
Selim Cinekd35c2792016-01-21 13:20:57 -0800385 private int mBgColor;
386 private float mDimAmount;
387 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800388 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400389 private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
Selim Cinekd35c2792016-01-21 13:20:57 -0800390 @Override
391 public void onAnimationEnd(Animator animation) {
392 mDimAnimator = null;
393 }
394 };
395 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
396 = new ValueAnimator.AnimatorUpdateListener() {
397
398 @Override
399 public void onAnimationUpdate(ValueAnimator animation) {
400 setDimAmount((Float) animation.getAnimatedValue());
401 }
402 };
Muyuan Li4fe4a402016-03-30 16:50:11 -0700403 protected ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800404 private boolean mContinuousShadowUpdate;
Selim Cinekae55d832019-02-22 17:43:43 -0800405 private boolean mContinuousBackgroundUpdate;
Selim Cinek33223572016-02-19 19:32:22 -0800406 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
407 = new ViewTreeObserver.OnPreDrawListener() {
408
409 @Override
410 public boolean onPreDraw() {
411 updateViewShadows();
412 return true;
413 }
414 };
Selim Cinekae55d832019-02-22 17:43:43 -0800415 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
416 updateBackground();
417 return true;
418 };
Selim Cinek33223572016-02-19 19:32:22 -0800419 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
420 @Override
421 public int compare(ExpandableView view, ExpandableView otherView) {
422 float endY = view.getTranslationY() + view.getActualHeight();
423 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
424 if (endY < otherEndY) {
425 return -1;
426 } else if (endY > otherEndY) {
427 return 1;
428 } else {
429 // The two notifications end at the same location
430 return 0;
431 }
432 }
433 };
Lucas Dupin64e2f572019-03-21 14:21:14 -0700434 private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
435 @Override
436 public void getOutline(View view, Outline outline) {
Selim Cinek195dfc52019-05-30 19:35:05 -0700437 if (mAmbientState.isHiddenAtAll()) {
438 float xProgress = mHideXInterpolator.getInterpolation(
439 (1 - mLinearHideAmount) * mBackgroundXFactor);
Selim Cinek9ad240c2019-04-08 19:07:15 -0700440 outline.setRoundRect(mBackgroundAnimationRect,
441 MathUtils.lerp(mCornerRadius / 2.0f, mCornerRadius,
442 xProgress));
Selim Cinek33295a42019-07-26 19:49:08 -0700443 outline.setAlpha(1.0f - mAmbientState.getHideAmount());
Lucas Dupin64e2f572019-03-21 14:21:14 -0700444 } else {
445 ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
446 }
447 }
448 };
Selim Cinek25503252016-03-03 15:31:43 -0800449 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900450 private boolean mPulsing;
Selim Cinek1b2a05e2016-04-28 14:20:39 -0700451 private boolean mGroupExpandedForMeasure;
Selim Cinekc22fff62016-05-20 12:44:30 -0700452 private boolean mScrollable;
Adrian Roos181385c2016-05-05 17:45:44 -0400453 private View mForcedScroll;
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200454
455 /**
Selim Cinek195dfc52019-05-30 19:35:05 -0700456 * @see #setHideAmount(float, float)
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200457 */
Selim Cinek195dfc52019-05-30 19:35:05 -0700458 private float mInterpolatedHideAmount = 0f;
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200459
460 /**
Selim Cinek195dfc52019-05-30 19:35:05 -0700461 * @see #setHideAmount(float, float)
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200462 */
Selim Cinek195dfc52019-05-30 19:35:05 -0700463 private float mLinearHideAmount = 0f;
Lucas Dupin439bd442018-06-12 15:05:28 -0700464
465 /**
466 * How fast the background scales in the X direction as a factor of the Y expansion.
467 */
468 private float mBackgroundXFactor = 1f;
Selim Cinek972123d2016-05-03 14:25:58 -0700469
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -0500470 private boolean mSwipingInProgress;
471
Lucas Dupine17ce522017-07-17 15:45:06 -0700472 private boolean mUsingLightTheme;
Selim Cinekbc243a92016-09-27 16:35:13 -0700473 private boolean mQsExpanded;
Selim Cinekef406062016-09-29 17:33:13 -0700474 private boolean mForwardScrollable;
475 private boolean mBackwardScrollable;
Selim Cinek281c2022016-10-13 19:14:43 -0700476 private NotificationShelf mShelf;
Selim Cinekad7fac02016-10-18 17:09:15 -0700477 private int mMaxDisplayedNotifications = -1;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800478 private int mStatusBarHeight;
Selim Cinek51d21972017-07-19 17:39:20 -0700479 private int mMinInteractionHeight;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800480 private boolean mNoAmbient;
481 private final Rect mClipRect = new Rect();
482 private boolean mIsClipped;
Selim Cinekcafa87f2016-10-26 17:00:17 -0700483 private Rect mRequestedClipBounds;
484 private boolean mInHeadsUpPinnedMode;
485 private boolean mHeadsUpAnimatingAway;
Selim Cinek355652a2016-12-07 13:32:12 -0800486 private int mStatusBarState;
Selim Cinekfb6ee6d2016-12-29 16:49:26 +0100487 private int mCachedBackgroundColor;
Selim Cinek5cf1d052017-06-01 17:36:46 -0700488 private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
Gus Prevascdc98342019-01-14 14:29:44 -0500489 private Runnable mReflingAndAnimateScroll = () -> {
490 if (ANCHOR_SCROLLING) {
491 maybeReflingScroller();
492 }
493 animateScroll();
494 };
Selim Cinek0fe07392017-11-09 13:26:34 -0800495 private int mCornerRadius;
Selim Cinek515b2032017-11-15 10:20:19 -0800496 private int mSidePaddings;
Lucas Dupin16cfe452018-02-08 13:14:50 -0800497 private final Rect mBackgroundAnimationRect = new Rect();
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800498 private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
499 private int mHeadsUpInset;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700500 private HeadsUpAppearanceController mHeadsUpAppearanceController;
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700501 private NotificationIconAreaController mIconAreaController;
Lucas Dupind236ee32019-10-08 15:33:59 -0700502 private final NotificationLockscreenUserManager mLockscreenUserManager;
Jason Monke59dc402018-08-16 12:05:01 -0400503 private final Rect mTmpRect = new Rect();
Beverly7e0d6492020-02-07 16:22:14 -0500504 private final FeatureFlags mFeatureFlags;
505 private final NotifPipeline mNotifPipeline;
506 private final NotifCollection mNotifCollection;
507 private final NotificationEntryManager mEntryManager;
Jason Monke59dc402018-08-16 12:05:01 -0400508 private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
509 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Will Brockmane718d582019-01-17 16:38:38 -0500510 @VisibleForTesting
511 protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
Will Brockmanb65faa82020-03-06 10:26:04 -0500512 protected final UiEventLogger mUiEventLogger;
Jason Monke59dc402018-08-16 12:05:01 -0400513 private final NotificationRemoteInputManager mRemoteInputManager =
514 Dependency.get(NotificationRemoteInputManager.class);
515 private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
516
517 private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
518 private final LockscreenGestureLogger mLockscreenGestureLogger =
519 Dependency.get(LockscreenGestureLogger.class);
520 private final VisualStabilityManager mVisualStabilityManager =
521 Dependency.get(VisualStabilityManager.class);
522 protected boolean mClearAllEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100523
Selim Cinek195dfc52019-05-30 19:35:05 -0700524 private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
Dave Mankoffaf8163f2020-01-08 14:24:35 -0500525 private NotificationPanelViewController mNotificationPanelController;
Jason Monk1fd3fc32018-08-14 17:20:09 -0400526
Lucas Dupind236ee32019-10-08 15:33:59 -0700527 private final NotificationGutsManager mNotificationGutsManager;
Ned Burns9eb06332019-04-23 16:02:12 -0400528 private final NotificationSectionsManager mSectionsManager;
Evan Laird04373662020-01-24 17:37:39 -0500529 private final ForegroundServiceSectionController mFgsSectionController;
530 private ForegroundServiceDungeonView mFgsSectionView;
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700531 private boolean mAnimateBottomOnLayout;
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700532 private float mLastSentAppear;
533 private float mLastSentExpandedHeight;
Selim Cinekaae9ad62019-07-22 18:36:54 -0700534 private boolean mWillExpand;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400535
shawnlinbd6d0ad2020-01-29 18:38:49 +0800536 private int mWaterfallTopInset;
537
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500538 @Inject
539 public NotificationStackScrollLayout(
540 @Named(VIEW_CONTEXT) Context context,
541 AttributeSet attrs,
Lucas Dupin00be88f2019-01-03 17:50:52 -0800542 @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
Selim Cinek3d6ae232019-01-04 14:14:33 -0800543 NotificationRoundnessManager notificationRoundnessManager,
Ned Burns9eb06332019-04-23 16:02:12 -0400544 DynamicPrivacyController dynamicPrivacyController,
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500545 SysuiStatusBarStateController statusBarStateController,
Selim Cinekc3fec682019-06-06 18:11:07 -0700546 HeadsUpManagerPhone headsUpManager,
Dave Mankoffc195ea82019-06-28 16:33:25 -0400547 KeyguardBypassController keyguardBypassController,
Evan Laird367b6142019-08-30 14:40:34 -0400548 FalsingManager falsingManager,
Lucas Dupind236ee32019-10-08 15:33:59 -0700549 NotificationLockscreenUserManager notificationLockscreenUserManager,
550 NotificationGutsManager notificationGutsManager,
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500551 ZenModeController zenController,
Evan Laird04373662020-01-24 17:37:39 -0500552 NotificationSectionsManager notificationSectionsManager,
553 ForegroundServiceSectionController fgsSectionController,
Beverly7e0d6492020-02-07 16:22:14 -0500554 ForegroundServiceDismissalFeatureController fgsFeatureController,
555 FeatureFlags featureFlags,
556 NotifPipeline notifPipeline,
557 NotificationEntryManager entryManager,
Will Brockmanb65faa82020-03-06 10:26:04 -0500558 NotifCollection notifCollection,
559 UiEventLogger uiEventLogger
Evan Laird04373662020-01-24 17:37:39 -0500560 ) {
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500561 super(context, attrs, 0, 0);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800562 Resources res = getResources();
563
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500564 mAllowLongPress = allowLongPress;
565
Selim Cinekc3fec682019-06-06 18:11:07 -0700566 mRoundnessManager = notificationRoundnessManager;
Gus Prevase2d6f042018-10-17 15:25:30 -0400567
Lucas Dupind236ee32019-10-08 15:33:59 -0700568 mLockscreenUserManager = notificationLockscreenUserManager;
569 mNotificationGutsManager = notificationGutsManager;
Selim Cinekc3fec682019-06-06 18:11:07 -0700570 mHeadsUpManager = headsUpManager;
571 mHeadsUpManager.addListener(mRoundnessManager);
572 mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
573 mKeyguardBypassController = keyguardBypassController;
Dave Mankoffc195ea82019-06-28 16:33:25 -0400574 mFalsingManager = falsingManager;
Beverly482ad6a2019-11-06 16:57:05 -0500575 mZenController = zenController;
Evan Laird04373662020-01-24 17:37:39 -0500576 mFgsSectionController = fgsSectionController;
Ned Burns61269442019-05-02 18:27:23 -0400577
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500578 mSectionsManager = notificationSectionsManager;
579 mSectionsManager.initialize(this, LayoutInflater.from(context));
Ned Burns61269442019-05-02 18:27:23 -0400580 mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
581 // Leave the shade open if there will be other notifs left over to clear
582 final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
583 clearNotifications(ROWS_GENTLE, closeShade);
584 });
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500585 mSections = mSectionsManager.createSectionsForBuckets();
Ned Burns61269442019-05-02 18:27:23 -0400586
Selim Cinekc3fec682019-06-06 18:11:07 -0700587 mAmbientState = new AmbientState(context, mSectionsManager, mHeadsUpManager);
Selim Cinekd35c2792016-01-21 13:20:57 -0800588 mBgColor = context.getColor(R.color.notification_shade_background_color);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800589 int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
590 int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400591 mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
Selim Cinek1cf41c12014-08-12 20:06:19 +0200592 minHeight, maxHeight);
593 mExpandHelper.setEventSource(this);
594 mExpandHelper.setScrollAdapter(this);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400595 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback,
Dave Mankoffc195ea82019-06-28 16:33:25 -0400596 getContext(), mMenuEventListener, mFalsingManager);
Muyuan Li333a4fc2016-04-16 17:13:46 -0700597 mStackScrollAlgorithm = createStackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100598 initView(context);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800599 mShouldDrawNotificationBackground =
600 res.getBoolean(R.bool.config_drawNotificationBackground);
Anthony Chen7acbb772017-04-07 16:45:25 -0700601 mFadeNotificationsOnDismiss =
602 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
Selim Cinek29aab962018-02-27 17:05:45 -0800603 mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated);
604 mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700605 addOnExpandedHeightChangedListener(mRoundnessManager::setExpanded);
Lucas Dupin28790c02020-01-30 14:18:41 -0800606 mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
Lucas Dupin64e2f572019-03-21 14:21:14 -0700607 setOutlineProvider(mOutlineProvider);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800608
Rohan Shah524cf7b2018-03-15 14:40:02 -0700609 // Blocking helper manager wants to know the expanded state, update as well.
610 NotificationBlockingHelperManager blockingHelperManager =
611 Dependency.get(NotificationBlockingHelperManager.class);
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700612 addOnExpandedHeightChangedListener((height, unused) -> {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700613 blockingHelperManager.setNotificationShadeExpanded(height);
614 });
615
Selim Cinek195dfc52019-05-30 19:35:05 -0700616 boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
617 setWillNotDraw(!willDraw);
Lucas Dupind285cf02018-01-18 09:18:23 -0800618 mBackgroundPaint.setAntiAlias(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100619 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100620 mDebugPaint = new Paint();
621 mDebugPaint.setColor(0xffff0000);
622 mDebugPaint.setStrokeWidth(2);
623 mDebugPaint.setStyle(Paint.Style.STROKE);
Gus Prevas0fa58d62019-01-11 13:58:40 -0500624 mDebugPaint.setTextSize(25f);
Selim Cinek67b22602014-03-10 15:40:16 +0100625 }
Jason Monke59dc402018-08-16 12:05:01 -0400626 mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
Gus Prevas33619af2018-10-26 15:40:27 -0400627
628 TunerService tunerService = Dependency.get(TunerService.class);
629 tunerService.addTunable((key, newValue) -> {
Selim Cinekda330ff2019-06-11 15:41:28 -0700630 if (key.equals(HIGH_PRIORITY)) {
631 mHighPriorityBeforeSpeedBump = "1".equals(newValue);
Gus Prevasa18dc572019-01-14 16:11:22 -0500632 } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
633 updateDismissRtlSetting("1".equals(newValue));
Julia Reynoldsda7c2452020-04-16 16:44:58 -0400634 } else if (key.equals(Settings.Secure.NOTIFICATION_HISTORY_ENABLED)) {
635 updateFooter();
Gus Prevas33619af2018-10-26 15:40:27 -0400636 }
Julia Reynoldsda7c2452020-04-16 16:44:58 -0400637 }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL,
638 Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500639
Beverly7e0d6492020-02-07 16:22:14 -0500640 mFeatureFlags = featureFlags;
641 mNotifPipeline = notifPipeline;
642 mEntryManager = entryManager;
643 mNotifCollection = notifCollection;
644 if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
645 mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
646 @Override
647 public void onEntryUpdated(NotificationEntry entry) {
648 NotificationStackScrollLayout.this.onEntryUpdated(entry);
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500649 }
Beverly7e0d6492020-02-07 16:22:14 -0500650 });
651 } else {
652 mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
653 @Override
654 public void onPreEntryUpdated(NotificationEntry entry) {
655 NotificationStackScrollLayout.this.onEntryUpdated(entry);
656 }
657 });
658 }
659
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700660 dynamicPrivacyController.addListener(this);
Selim Cinek5454a0d2019-07-30 17:14:50 -0700661 mDynamicPrivacyController = dynamicPrivacyController;
Dave Mankoff56fe9e42020-01-08 15:42:06 -0500662 mStatusbarStateController = statusBarStateController;
Evan Laird04373662020-01-24 17:37:39 -0500663 initializeForegroundServiceSection(fgsFeatureController);
Will Brockmanb65faa82020-03-06 10:26:04 -0500664 mUiEventLogger = uiEventLogger;
Evan Laird04373662020-01-24 17:37:39 -0500665 }
666
667 private void initializeForegroundServiceSection(
668 ForegroundServiceDismissalFeatureController featureController) {
669 if (featureController.isForegroundServiceDismissalEnabled()) {
670 LayoutInflater li = LayoutInflater.from(mContext);
671 mFgsSectionView =
672 (ForegroundServiceDungeonView) mFgsSectionController.createView(li);
673 addView(mFgsSectionView, -1);
674 }
Jason Monke59dc402018-08-16 12:05:01 -0400675 }
676
Gus Prevasa18dc572019-01-14 16:11:22 -0500677 private void updateDismissRtlSetting(boolean dismissRtl) {
678 mDismissRtl = dismissRtl;
679 for (int i = 0; i < getChildCount(); i++) {
680 View child = getChildAt(i);
681 if (child instanceof ExpandableNotificationRow) {
682 ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
683 }
684 }
685 }
686
Jason Monke59dc402018-08-16 12:05:01 -0400687 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400688 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400689 protected void onFinishInflate() {
690 super.onFinishInflate();
691
692 inflateEmptyShadeView();
693 inflateFooterView();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400694 mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500695 if (mAllowLongPress) {
Steve Elliottff6c25e2019-05-30 17:38:52 -0400696 setLongPressListener(mNotificationGutsManager::openGuts);
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500697 }
Jason Monke59dc402018-08-16 12:05:01 -0400698 }
699
Selim Cinek34518f62019-02-28 19:41:18 -0800700 /**
701 * @return the height at which we will wake up when pulsing
702 */
Selim Cinekb0fada62019-06-17 19:03:59 -0700703 public float getWakeUpHeight() {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800704 ActivatableNotificationView firstChild = getFirstChildWithBackground();
705 if (firstChild != null) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -0700706 if (mKeyguardBypassController.getBypassEnabled()) {
707 return firstChild.getHeadsUpHeightWithoutHeader();
708 } else {
709 return firstChild.getCollapsedHeight();
710 }
Selim Cinek3d6ae232019-01-04 14:14:33 -0800711 }
Selim Cinek34518f62019-02-28 19:41:18 -0800712 return 0f;
Selim Cinek3d6ae232019-01-04 14:14:33 -0800713 }
714
Jason Monke59dc402018-08-16 12:05:01 -0400715 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400716 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400717 public void onDensityOrFontScaleChanged() {
Selim Cinekdd26a7e2019-02-11 18:42:55 -0800718 reinflateViews();
719 }
720
721 private void reinflateViews() {
Jason Monke59dc402018-08-16 12:05:01 -0400722 inflateFooterView();
723 inflateEmptyShadeView();
724 updateFooter();
Ned Burns2c74c2a2019-06-13 19:06:47 -0400725 mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
Jason Monke59dc402018-08-16 12:05:01 -0400726 }
727
728 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400729 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400730 public void onThemeChanged() {
Lucas Dupin89233c42019-06-10 18:00:22 -0700731 final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
Jason Monke59dc402018-08-16 12:05:01 -0400732 updateDecorViews(useDarkText);
733
734 updateFooter();
735 }
736
Fabian Kozynskid254b192019-02-05 13:42:58 -0500737 @Override
738 public void onOverlayChanged() {
739 int newRadius = mContext.getResources().getDimensionPixelSize(
740 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
741 if (mCornerRadius != newRadius) {
742 mCornerRadius = newRadius;
743 invalidate();
744 }
Selim Cinekdd26a7e2019-02-11 18:42:55 -0800745 reinflateViews();
Fabian Kozynskid254b192019-02-05 13:42:58 -0500746 }
747
Jason Monke59dc402018-08-16 12:05:01 -0400748 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400749 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400750 public void updateFooter() {
Julia Reynoldsda7c2452020-04-16 16:44:58 -0400751 if (mFooterView == null) {
752 return;
753 }
Ned Burns61269442019-05-02 18:27:23 -0400754 boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
Beverly7e0d6492020-02-07 16:22:14 -0500755 boolean showFooterView = (showDismissView || hasActiveNotifications())
Jason Monke59dc402018-08-16 12:05:01 -0400756 && mStatusBarState != StatusBarState.KEYGUARD
757 && !mRemoteInputManager.getController().isRemoteInputActive();
Julia Reynolds1f94a9d2020-05-05 12:40:21 -0400758 boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
759 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
Jason Monke59dc402018-08-16 12:05:01 -0400760
Julia Reynoldsda7c2452020-04-16 16:44:58 -0400761 updateFooterView(showFooterView, showDismissView, showHistory);
Jason Monke59dc402018-08-16 12:05:01 -0400762 }
763
764 /**
765 * Return whether there are any clearable notifications
766 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400767 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -0400768 public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
Selim Cinek5454a0d2019-07-30 17:14:50 -0700769 if (mDynamicPrivacyController.isInLockedDownShade()) {
770 return false;
771 }
Jason Monke59dc402018-08-16 12:05:01 -0400772 int childCount = getChildCount();
773 for (int i = 0; i < childCount; i++) {
774 View child = getChildAt(i);
775 if (!(child instanceof ExpandableNotificationRow)) {
776 continue;
777 }
Ned Burns61269442019-05-02 18:27:23 -0400778 final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
779 if (row.canViewBeDismissed() && matchesSelection(row, selection)) {
Jason Monke59dc402018-08-16 12:05:01 -0400780 return true;
781 }
782 }
783 return false;
784 }
785
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400786 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400787 public RemoteInputController.Delegate createDelegate() {
Jason Monke59dc402018-08-16 12:05:01 -0400788 return new RemoteInputController.Delegate() {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500789 public void setRemoteInputActive(NotificationEntry entry,
Jason Monke59dc402018-08-16 12:05:01 -0400790 boolean remoteInputActive) {
791 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
Evan Laird94492852018-10-25 13:43:01 -0400792 entry.notifyHeightChanged(true /* needsAnimation */);
Jason Monke59dc402018-08-16 12:05:01 -0400793 updateFooter();
794 }
795
Ned Burnsf81c4c42019-01-07 14:10:43 -0500796 public void lockScrollTo(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -0400797 NotificationStackScrollLayout.this.lockScrollTo(entry.getRow());
Jason Monke59dc402018-08-16 12:05:01 -0400798 }
799
800 public void requestDisallowLongPressAndDismiss() {
801 requestDisallowLongPress();
802 requestDisallowDismiss();
803 }
804 };
Selim Cinek67b22602014-03-10 15:40:16 +0100805 }
806
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900807 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400808 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk1fd3fc32018-08-14 17:20:09 -0400809 protected void onAttachedToWindow() {
810 super.onAttachedToWindow();
Beverly8fdb5332019-02-04 14:29:49 -0500811 ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
812 .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
Jason Monke59dc402018-08-16 12:05:01 -0400813 Dependency.get(ConfigurationController.class).addCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400814 }
815
816 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400817 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk1fd3fc32018-08-14 17:20:09 -0400818 protected void onDetachedFromWindow() {
819 super.onDetachedFromWindow();
Jason Monkaf08c152018-12-04 11:12:39 -0500820 Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
Jason Monke59dc402018-08-16 12:05:01 -0400821 Dependency.get(ConfigurationController.class).removeCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400822 }
823
824 @Override
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400825 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellor95d743c2017-01-10 12:05:27 -0800826 public NotificationSwipeActionHelper getSwipeActionHelper() {
827 return mSwipeHelper;
828 }
829
Selim Cinek67b22602014-03-10 15:40:16 +0100830 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400831 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupinf03e7522018-06-25 16:21:13 -0700832 public void onUiModeChanged() {
833 mBgColor = mContext.getColor(R.color.notification_shade_background_color);
834 updateBackgroundDimming();
Selim Cinekab9c7b22018-12-11 18:15:47 -0800835 mShelf.onUiModeChanged();
Lucas Dupinf03e7522018-06-25 16:21:13 -0700836 }
837
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400838 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Selim Cinek67b22602014-03-10 15:40:16 +0100839 protected void onDraw(Canvas canvas) {
Lucas Dupind285cf02018-01-18 09:18:23 -0800840 if (mShouldDrawNotificationBackground
Gus Prevase2d6f042018-10-17 15:25:30 -0400841 && (mSections[0].getCurrentBounds().top
Evan Laird25f02752019-08-14 19:25:06 -0400842 < mSections[mSections.length - 1].getCurrentBounds().bottom
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700843 || mAmbientState.isDozing())) {
Lucas Dupind285cf02018-01-18 09:18:23 -0800844 drawBackground(canvas);
Gus Prevas211181532018-12-13 14:49:33 -0500845 } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
846 drawHeadsUpBackground(canvas);
Selim Cinekd381bc32016-08-15 12:40:57 -0700847 }
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800848
Selim Cinek67b22602014-03-10 15:40:16 +0100849 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800850 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100851 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Mady Mellor43c2cd12016-12-12 21:05:13 -0800852 y = getLayoutHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100853 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200854 y = getHeight() - getEmptyBottomMargin();
855 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100856 }
857 }
858
Gus Prevas0fa58d62019-01-11 13:58:40 -0500859 @Override
860 public void draw(Canvas canvas) {
861 super.draw(canvas);
862
863 if (DEBUG && ANCHOR_SCROLLING) {
864 if (mScrollAnchorView instanceof ExpandableNotificationRow) {
865 canvas.drawRect(0,
866 mScrollAnchorView.getTranslationY(),
867 getWidth(),
868 mScrollAnchorView.getTranslationY()
869 + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
870 mDebugPaint);
871 canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
872 mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
873 int y = (int) mShelf.getTranslationY();
874 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
875 }
Gus Prevascdc98342019-01-14 14:29:44 -0500876 canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
Selim Cinekdb6d6df2019-05-22 20:39:59 -0700877 getTopPadding() + 30, mDebugPaint);
Gus Prevascdc98342019-01-14 14:29:44 -0500878 canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
879 getHeight() - 30, mDebugPaint);
Gus Prevas0fa58d62019-01-11 13:58:40 -0500880 }
881 }
882
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400883 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Lucas Dupind285cf02018-01-18 09:18:23 -0800884 private void drawBackground(Canvas canvas) {
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700885 int lockScreenLeft = mSidePaddings;
886 int lockScreenRight = getWidth() - mSidePaddings;
Gus Prevase2d6f042018-10-17 15:25:30 -0400887 int lockScreenTop = mSections[0].getCurrentBounds().top;
Evan Laird25f02752019-08-14 19:25:06 -0400888 int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
Selim Cinek195dfc52019-05-30 19:35:05 -0700889 int hiddenLeft = getWidth() / 2;
890 int hiddenTop = mTopPadding;
Lucas Dupind285cf02018-01-18 09:18:23 -0800891
Selim Cinek195dfc52019-05-30 19:35:05 -0700892 float yProgress = 1 - mInterpolatedHideAmount;
893 float xProgress = mHideXInterpolator.getInterpolation(
894 (1 - mLinearHideAmount) * mBackgroundXFactor);
Lucas Dupin60661a62018-04-12 10:50:13 -0700895
Selim Cinek195dfc52019-05-30 19:35:05 -0700896 int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
897 int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
898 int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
899 int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700900 mBackgroundAnimationRect.set(
Gus Prevase2d6f042018-10-17 15:25:30 -0400901 left,
902 top,
903 right,
904 bottom);
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200905
Gus Prevase2d6f042018-10-17 15:25:30 -0400906 int backgroundTopAnimationOffset = top - lockScreenTop;
907 // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
908 boolean anySectionHasVisibleChild = false;
909 for (NotificationSection section : mSections) {
910 if (section.getFirstVisibleChild() != null) {
911 anySectionHasVisibleChild = true;
912 break;
913 }
914 }
Selim Cinek5fedd812019-06-26 19:34:24 -0700915 boolean shouldDrawBackground;
916 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
917 shouldDrawBackground = isPulseExpanding();
918 } else {
919 shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
920 }
921 if (shouldDrawBackground) {
Gus Prevase2d6f042018-10-17 15:25:30 -0400922 drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
Lucas Dupind285cf02018-01-18 09:18:23 -0800923 }
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700924
Lucas Dupin16cfe452018-02-08 13:14:50 -0800925 updateClipping();
Lucas Dupind285cf02018-01-18 09:18:23 -0800926 }
927
Gus Prevase2d6f042018-10-17 15:25:30 -0400928 /**
929 * Draws round rects for each background section.
930 *
931 * We want to draw a round rect for each background section as defined by {@link #mSections}.
932 * However, if two sections are directly adjacent with no gap between them (e.g. on the
933 * lockscreen where the shelf can appear directly below the high priority section, or while
934 * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
935 * section), we don't want to round the adjacent corners.
936 *
937 * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
938 * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
939 * This method tracks the top of each rect we need to draw, then iterates through the visible
940 * sections. If a section is not adjacent to the previous section, we draw the previous rect
941 * behind the sections we've accumulated up to that point, then start a new rect at the top of
942 * the current section. When we're done iterating we will always have one rect left to draw.
943 */
944 private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
945 int animationYOffset) {
946 int backgroundRectTop = top;
947 int lastSectionBottom =
948 mSections[0].getCurrentBounds().bottom + animationYOffset;
Selim Cinek0acf4302019-06-11 12:43:03 -0700949 int currentLeft = left;
950 int currentRight = right;
Selim Cinekae55d832019-02-22 17:43:43 -0800951 boolean first = true;
Gus Prevase2d6f042018-10-17 15:25:30 -0400952 for (NotificationSection section : mSections) {
953 if (section.getFirstVisibleChild() == null) {
954 continue;
955 }
956 int sectionTop = section.getCurrentBounds().top + animationYOffset;
Selim Cinekae55d832019-02-22 17:43:43 -0800957 int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
Selim Cinekd0b48e32019-05-24 20:49:23 -0700958 int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
Gus Prevase2d6f042018-10-17 15:25:30 -0400959 // If sections are directly adjacent to each other, we don't want to draw them
960 // as separate roundrects, as the rounded corners right next to each other look
961 // bad.
Selim Cinekae55d832019-02-22 17:43:43 -0800962 if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
Selim Cinek0acf4302019-06-11 12:43:03 -0700963 || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
964 canvas.drawRoundRect(currentLeft,
Gus Prevase2d6f042018-10-17 15:25:30 -0400965 backgroundRectTop,
Selim Cinek0acf4302019-06-11 12:43:03 -0700966 currentRight,
Gus Prevase2d6f042018-10-17 15:25:30 -0400967 lastSectionBottom,
968 mCornerRadius, mCornerRadius, mBackgroundPaint);
969 backgroundRectTop = sectionTop;
970 }
Selim Cinek0acf4302019-06-11 12:43:03 -0700971 currentLeft = ownLeft;
972 currentRight = ownRight;
Gus Prevase2d6f042018-10-17 15:25:30 -0400973 lastSectionBottom =
974 section.getCurrentBounds().bottom + animationYOffset;
Selim Cinekae55d832019-02-22 17:43:43 -0800975 first = false;
Gus Prevase2d6f042018-10-17 15:25:30 -0400976 }
Selim Cinek0acf4302019-06-11 12:43:03 -0700977 canvas.drawRoundRect(currentLeft,
Gus Prevase2d6f042018-10-17 15:25:30 -0400978 backgroundRectTop,
Selim Cinek0acf4302019-06-11 12:43:03 -0700979 currentRight,
Gus Prevase2d6f042018-10-17 15:25:30 -0400980 lastSectionBottom,
981 mCornerRadius, mCornerRadius, mBackgroundPaint);
982 }
983
Gus Prevas211181532018-12-13 14:49:33 -0500984 private void drawHeadsUpBackground(Canvas canvas) {
985 int left = mSidePaddings;
986 int right = getWidth() - mSidePaddings;
987
988 float top = getHeight();
989 float bottom = 0;
990 int childCount = getChildCount();
991 for (int i = 0; i < childCount; i++) {
992 View child = getChildAt(i);
993 if (child.getVisibility() != View.GONE
994 && child instanceof ExpandableNotificationRow) {
995 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
shawnlin3a950c32019-05-15 20:06:10 +0800996 if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
997 && row.getProvider().shouldShowGutsOnSnapOpen()) {
Gus Prevas211181532018-12-13 14:49:33 -0500998 top = Math.min(top, row.getTranslationY());
999 bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
1000 }
1001 }
1002 }
1003
1004 if (top < bottom) {
1005 canvas.drawRoundRect(
1006 left, top, right, bottom,
1007 mCornerRadius, mCornerRadius, mBackgroundPaint);
1008 }
1009 }
1010
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001011 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd35c2792016-01-21 13:20:57 -08001012 private void updateBackgroundDimming() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08001013 // No need to update the background color if it's not being drawn.
1014 if (!mShouldDrawNotificationBackground) {
1015 return;
1016 }
1017
Lucas Dupinb561eda2018-04-09 17:25:04 -07001018 // Interpolate between semi-transparent notification panel background color
1019 // and white AOD separator.
Lucas Dupinf03e7522018-06-25 16:21:13 -07001020 float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
Selim Cinek195dfc52019-05-30 19:35:05 -07001021 mLinearHideAmount);
Felka Chang97bc8d52019-05-29 02:51:15 +08001022 int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
Lucas Dupind285cf02018-01-18 09:18:23 -08001023
Selim Cinekfb6ee6d2016-12-29 16:49:26 +01001024 if (mCachedBackgroundColor != color) {
1025 mCachedBackgroundColor = color;
1026 mBackgroundPaint.setColor(color);
1027 invalidate();
1028 }
Selim Cinekd35c2792016-01-21 13:20:57 -08001029 }
1030
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001031 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001032 private void initView(Context context) {
1033 mScroller = new OverScroller(getContext());
Selim Cinek67b22602014-03-10 15:40:16 +01001034 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +02001035 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +01001036 final ViewConfiguration configuration = ViewConfiguration.get(context);
1037 mTouchSlop = configuration.getScaledTouchSlop();
Philip Quinn47169132020-03-23 19:04:55 -07001038 mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
Selim Cinek67b22602014-03-10 15:40:16 +01001039 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
1040 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +01001041 mOverflingDistance = configuration.getScaledOverflingDistance();
Anthony Chen9fe1ee72017-04-07 13:53:37 -07001042
1043 Resources res = context.getResources();
1044 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinekaf0dc312015-12-15 17:01:44 -08001045 mStackScrollAlgorithm.initView(context);
Selim Cinek281c2022016-10-13 19:14:43 -07001046 mAmbientState.reload(context);
Anthony Chen9fe1ee72017-04-07 13:53:37 -07001047 mPaddingBetweenElements = Math.max(1,
1048 res.getDimensionPixelSize(R.dimen.notification_divider_height));
1049 mIncreasedPaddingBetweenElements =
1050 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
1051 mMinTopOverScrollToEscape = res.getDimensionPixelSize(
Selim Cinek1408eb52014-06-02 14:45:38 +02001052 R.dimen.min_top_overscroll_to_qs);
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001053 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
Anthony Chen9fe1ee72017-04-07 13:53:37 -07001054 mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
Selim Cinekb95fd182017-12-21 13:03:32 -08001055 mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
Selim Cinek51d21972017-07-19 17:39:20 -07001056 mMinInteractionHeight = res.getDimensionPixelSize(
1057 R.dimen.notification_min_interaction_height);
Selim Cinek0fe07392017-11-09 13:26:34 -08001058 mCornerRadius = res.getDimensionPixelSize(
1059 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001060 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
1061 R.dimen.heads_up_status_bar_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +02001062 }
1063
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001064 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekaef92ef2014-06-06 18:06:04 +02001065 private void notifyHeightChangeListener(ExpandableView view) {
Lucas Dupin60661a62018-04-12 10:50:13 -07001066 notifyHeightChangeListener(view, false /* needsAnimation */);
1067 }
1068
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001069 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Lucas Dupin60661a62018-04-12 10:50:13 -07001070 private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
Selim Cinekaef92ef2014-06-06 18:06:04 +02001071 if (mOnHeightChangedListener != null) {
Lucas Dupin60661a62018-04-12 10:50:13 -07001072 mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
Selim Cinekaef92ef2014-06-06 18:06:04 +02001073 }
Selim Cinek67b22602014-03-10 15:40:16 +01001074 }
1075
Selim Cinekb0fada62019-06-17 19:03:59 -07001076 public boolean isPulseExpanding() {
1077 return mAmbientState.isPulseExpanding();
1078 }
1079
Selim Cinek67b22602014-03-10 15:40:16 +01001080 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001081 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001082 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1083 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinekb95fd182017-12-21 13:03:32 -08001084
1085 int width = MeasureSpec.getSize(widthMeasureSpec);
1086 int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2,
1087 MeasureSpec.getMode(widthMeasureSpec));
Evan Lairdeb7dbd52018-06-28 13:17:25 -04001088 // Don't constrain the height of the children so we know how big they'd like to be
1089 int childHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
1090 MeasureSpec.UNSPECIFIED);
1091
Selim Cinekfa760d42016-05-10 15:50:53 -04001092 // We need to measure all children even the GONE ones, such that the heights are calculated
1093 // correctly as they are used to calculate how many we can fit on the screen.
1094 final int size = getChildCount();
1095 for (int i = 0; i < size; i++) {
Evan Lairdeb7dbd52018-06-28 13:17:25 -04001096 measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -04001097 }
Selim Cinek67b22602014-03-10 15:40:16 +01001098 }
1099
1100 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001101 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001102 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinek67b22602014-03-10 15:40:16 +01001103 // we layout all our children centered on the top
1104 float centerX = getWidth() / 2.0f;
1105 for (int i = 0; i < getChildCount(); i++) {
1106 View child = getChildAt(i);
Selim Cinekfa760d42016-05-10 15:50:53 -04001107 // We need to layout all children even the GONE ones, such that the heights are
1108 // calculated correctly as they are used to calculate how many we can fit on the screen
Selim Cinek67b22602014-03-10 15:40:16 +01001109 float width = child.getMeasuredWidth();
1110 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001111 child.layout((int) (centerX - width / 2.0f),
1112 0,
1113 (int) (centerX + width / 2.0f),
1114 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +01001115 }
Jorim Jaggi1d480692014-05-20 19:41:58 +02001116 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +01001117 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001118 clampScrollPosition();
Selim Cinek319bdc42014-05-01 23:01:58 +02001119 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -08001120 updateFirstAndLastBackgroundViews();
Selim Cinekbc243a92016-09-27 16:35:13 -07001121 updateAlgorithmLayoutMinHeight();
Selim Cinek33295a42019-07-26 19:49:08 -07001122 updateOwnTranslationZ();
Selim Cinek67b22602014-03-10 15:40:16 +01001123 }
1124
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001125 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5bc852a2015-12-21 12:19:09 -08001126 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
1127 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +02001128 mNeedViewResizeAnimation = true;
1129 mNeedsAnimation = true;
1130 }
Selim Cineka5e211b2014-08-11 17:35:48 +02001131 }
1132
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001133 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekdb167372016-11-17 15:41:17 -08001134 public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) {
1135 mAmbientState.setSpeedBumpIndex(newIndex);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001136 mNoAmbient = noAmbient;
Selim Cinekc27437b2014-05-14 10:23:33 +02001137 }
1138
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001139 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001140 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001141 public void setChildLocationsChangedListener(
1142 NotificationLogger.OnChildLocationsChangedListener listener) {
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001143 mListener = listener;
1144 }
1145
Selim Cineka7d4f822016-12-06 14:34:47 -08001146 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001147 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001148 public boolean isInVisibleLocation(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04001149 ExpandableNotificationRow row = entry.getRow();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05001150 ExpandableViewState childViewState = row.getViewState();
1151
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001152 if (childViewState == null) {
Selim Cineka7d4f822016-12-06 14:34:47 -08001153 return false;
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001154 }
Selim Cinek9b9d6e12017-11-30 12:29:47 +01001155 if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
Selim Cineka7d4f822016-12-06 14:34:47 -08001156 return false;
Christoph Studer12cf9e52014-10-29 17:35:30 +01001157 }
Selim Cineka7d4f822016-12-06 14:34:47 -08001158 if (row.getVisibility() != View.VISIBLE) {
1159 return false;
1160 }
1161 return true;
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001162 }
1163
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001164 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Selim Cinek67b22602014-03-10 15:40:16 +01001165 private void setMaxLayoutHeight(int maxLayoutHeight) {
1166 mMaxLayoutHeight = maxLayoutHeight;
Selim Cinek9458b192016-10-25 19:02:42 -07001167 mShelf.setMaxLayoutHeight(maxLayoutHeight);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001168 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +01001169 }
1170
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001171 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001172 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001173 mAmbientState.setLayoutHeight(getLayoutHeight());
Selim Cinekbc243a92016-09-27 16:35:13 -07001174 updateAlgorithmLayoutMinHeight();
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001175 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +01001176 }
1177
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001178 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Selim Cinekbc243a92016-09-27 16:35:13 -07001179 private void updateAlgorithmLayoutMinHeight() {
shawnlinc3457912018-05-15 16:39:56 +08001180 mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition()
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001181 ? getLayoutMinHeight() : 0);
Selim Cinekbc243a92016-09-27 16:35:13 -07001182 }
1183
Selim Cinek67b22602014-03-10 15:40:16 +01001184 /**
1185 * Updates the children views according to the stack scroll algorithm. Call this whenever
1186 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
1187 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001188 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001189 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -08001190 updateScrollStateForAddedChildren();
Selim Cinek727903c2016-12-06 17:28:10 -08001191 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
1192 ? 0
1193 : mScroller.getCurrVelocity());
Gus Prevas0fa58d62019-01-11 13:58:40 -05001194 if (ANCHOR_SCROLLING) {
1195 mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
1196 mAmbientState.setAnchorViewY(mScrollAnchorViewY);
1197 } else {
1198 mAmbientState.setScrollY(mOwnScrollY);
1199 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05001200 mStackScrollAlgorithm.resetViewStates(mAmbientState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001201 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001202 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +01001203 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +02001204 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +01001205 }
1206 }
1207
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001208 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc383fd02016-10-21 15:31:26 -07001209 private void onPreDrawDuringAnimation() {
Selim Cineka686b2c2016-10-26 13:58:27 -07001210 mShelf.updateAppearance();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001211 updateClippingToTopRoundedCorner();
Selim Cinekc383fd02016-10-21 15:31:26 -07001212 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
1213 updateBackground();
1214 }
Selim Cinekc383fd02016-10-21 15:31:26 -07001215 }
1216
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001217 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001218 private void updateClippingToTopRoundedCorner() {
Arthur Hungc0ef5652018-05-22 14:00:42 +08001219 Float clipStart = (float) mTopPadding
Jason Monke59dc402018-08-16 12:05:01 -04001220 + mStackTranslation
1221 + mAmbientState.getExpandAnimationTopChange();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001222 Float clipEnd = clipStart + mCornerRadius;
1223 boolean first = true;
1224 for (int i = 0; i < getChildCount(); i++) {
1225 ExpandableView child = (ExpandableView) getChildAt(i);
1226 if (child.getVisibility() == GONE) {
1227 continue;
1228 }
1229 float start = child.getTranslationY();
Arthur Hungc0ef5652018-05-22 14:00:42 +08001230 float end = start + child.getActualHeight();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001231 boolean clip = clipStart > start && clipStart < end
1232 || clipEnd >= start && clipEnd <= end;
Gus Prevas0fa58d62019-01-11 13:58:40 -05001233 clip &= !(first && isScrolledToTop());
Selim Cinekeccf4942018-05-30 09:55:36 -07001234 child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
1235 : ExpandableView.NO_ROUNDNESS);
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001236 first = false;
1237 }
1238 }
1239
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001240 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek3776fe02016-02-04 13:32:43 -08001241 private void updateScrollStateForAddedChildren() {
1242 if (mChildrenToAddAnimated.isEmpty()) {
1243 return;
1244 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001245 if (!ANCHOR_SCROLLING) {
1246 for (int i = 0; i < getChildCount(); i++) {
1247 ExpandableView child = (ExpandableView) getChildAt(i);
1248 if (mChildrenToAddAnimated.contains(child)) {
1249 int startingPosition = getPositionInLinearLayout(child);
1250 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
1251 int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
1252 : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
1253 int childHeight = getIntrinsicHeight(child) + padding;
1254 if (startingPosition < mOwnScrollY) {
1255 // This child starts off screen, so let's keep it offscreen to keep the
1256 // others visible
Selim Cinek3776fe02016-02-04 13:32:43 -08001257
Gus Prevas0fa58d62019-01-11 13:58:40 -05001258 setOwnScrollY(mOwnScrollY + childHeight);
1259 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001260 }
1261 }
1262 }
1263 clampScrollPosition();
1264 }
1265
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001266 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04001267 private void updateForcedScroll() {
1268 if (mForcedScroll != null && (!mForcedScroll.hasFocus()
1269 || !mForcedScroll.isAttachedToWindow())) {
1270 mForcedScroll = null;
1271 }
1272 if (mForcedScroll != null) {
1273 ExpandableView expandableView = (ExpandableView) mForcedScroll;
1274 int positionInLinearLayout = getPositionInLinearLayout(expandableView);
1275 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
Adrian Roos4a579672016-05-24 16:54:37 -07001276 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Adrian Roos181385c2016-05-05 17:45:44 -04001277
Gus Prevas0fa58d62019-01-11 13:58:40 -05001278 if (ANCHOR_SCROLLING) {
1279 // TODO
1280 } else {
1281 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
Adrian Roos4a579672016-05-24 16:54:37 -07001282
Gus Prevas0fa58d62019-01-11 13:58:40 -05001283 // Only apply the scroll if we're scrolling the view upwards, or the view is so
1284 // far up that it is not visible anymore.
1285 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1286 setOwnScrollY(targetScroll);
1287 }
Adrian Roos181385c2016-05-05 17:45:44 -04001288 }
1289 }
1290 }
1291
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001292 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek319bdc42014-05-01 23:01:58 +02001293 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +02001294 if (!mChildrenUpdateRequested) {
1295 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
1296 mChildrenUpdateRequested = true;
1297 invalidate();
1298 }
Selim Cinek319bdc42014-05-01 23:01:58 +02001299 }
1300
Robert Snoeberger168949a2019-04-18 09:39:42 -04001301 /**
1302 * Returns best effort count of visible notifications.
1303 */
1304 public int getVisibleNotificationCount() {
1305 int count = 0;
1306 for (int i = 0; i < getChildCount(); i++) {
1307 final View child = getChildAt(i);
1308 if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
1309 count++;
1310 }
1311 }
1312 return count;
1313 }
1314
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001315 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001316 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +02001317 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +01001318 }
1319
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001320 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekf7a14c02014-07-07 14:01:46 +02001321 private void clampScrollPosition() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001322 if (ANCHOR_SCROLLING) {
1323 // TODO
1324 } else {
1325 int scrollRange = getScrollRange();
1326 if (scrollRange < mOwnScrollY) {
1327 setOwnScrollY(scrollRange);
1328 }
Selim Cinek67b22602014-03-10 15:40:16 +01001329 }
1330 }
1331
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001332 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001333 public int getTopPadding() {
1334 return mTopPadding;
1335 }
1336
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001337 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001338 private void setTopPadding(int topPadding, boolean animate) {
Lucas Dupin00be88f2019-01-03 17:50:52 -08001339 if (mTopPadding != topPadding) {
1340 mTopPadding = topPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001341 updateAlgorithmHeightAndPadding();
1342 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +02001343 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001344 mTopPaddingNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04001345 mNeedsAnimation = true;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001346 }
Selim Cinek319bdc42014-05-01 23:01:58 +02001347 requestChildrenUpdate();
Lucas Dupin60661a62018-04-12 10:50:13 -07001348 notifyHeightChangeListener(null, animate);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001349 }
1350 }
1351
1352 /**
Selim Cinekbc243a92016-09-27 16:35:13 -07001353 * Update the height of the panel.
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001354 *
Selim Cinekbc243a92016-09-27 16:35:13 -07001355 * @param height the expanded height of the panel
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001356 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001357 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekbc243a92016-09-27 16:35:13 -07001358 public void setExpandedHeight(float height) {
1359 mExpandedHeight = height;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001360 setIsExpanded(height > 0);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001361 int minExpansionHeight = getMinExpansionHeight();
1362 if (height < minExpansionHeight) {
1363 mClipRect.left = 0;
1364 mClipRect.right = getWidth();
1365 mClipRect.top = 0;
1366 mClipRect.bottom = (int) height;
1367 height = minExpansionHeight;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001368 setRequestedClipBounds(mClipRect);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001369 } else {
Selim Cinekcafa87f2016-10-26 17:00:17 -07001370 setRequestedClipBounds(null);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001371 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001372 int stackHeight;
Selim Cinek94c2d822016-07-13 18:50:04 -07001373 float translationY;
1374 float appearEndPosition = getAppearEndPosition();
1375 float appearStartPosition = getAppearStartPosition();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001376 float appearFraction = 1.0f;
shawnlin5be1f7c2018-05-21 20:50:54 +08001377 boolean appearing = height < appearEndPosition;
1378 mAmbientState.setAppearing(appearing);
1379 if (!appearing) {
Selim Cinekbc243a92016-09-27 16:35:13 -07001380 translationY = 0;
shawnlin8e4e92c2018-04-12 18:47:24 +08001381 if (mShouldShowShelfOnly) {
1382 stackHeight = mTopPadding + mShelf.getIntrinsicHeight();
1383 } else if (mQsExpanded) {
1384 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding;
1385 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
1386 if (stackStartPosition <= stackEndPosition) {
1387 stackHeight = stackEndPosition;
1388 } else {
1389 stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
1390 stackEndPosition, mQsExpansionFraction);
1391 }
1392 } else {
1393 stackHeight = (int) height;
1394 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001395 } else {
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001396 appearFraction = calculateAppearFraction(height);
Selim Cinek94c2d822016-07-13 18:50:04 -07001397 if (appearFraction >= 0) {
1398 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
1399 appearFraction);
1400 } else {
1401 // This may happen when pushing up a heads up. We linearly push it up from the
1402 // start
1403 translationY = height - appearStartPosition + getExpandTranslationStart();
1404 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001405 if (isHeadsUpTransition()) {
Gus Prevase2d6f042018-10-17 15:25:30 -04001406 stackHeight =
1407 getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001408 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
1409 } else {
1410 stackHeight = (int) (height - translationY);
1411 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001412 }
1413 if (stackHeight != mCurrentStackHeight) {
1414 mCurrentStackHeight = stackHeight;
1415 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +02001416 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001417 }
Selim Cinek94c2d822016-07-13 18:50:04 -07001418 setStackTranslation(translationY);
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001419 notifyAppearChangedListeners();
1420 }
1421
1422 private void notifyAppearChangedListeners() {
1423 float appear;
1424 float expandAmount;
1425 if (mKeyguardBypassController.getBypassEnabled() && onKeyguard()) {
1426 appear = calculateAppearFractionBypass();
1427 expandAmount = getPulseHeight();
1428 } else {
1429 appear = MathUtils.saturate(calculateAppearFraction(mExpandedHeight));
1430 expandAmount = mExpandedHeight;
1431 }
1432 if (appear != mLastSentAppear || expandAmount != mLastSentExpandedHeight) {
1433 mLastSentAppear = appear;
1434 mLastSentExpandedHeight = expandAmount;
1435 for (int i = 0; i < mExpandedHeightListeners.size(); i++) {
1436 BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i);
1437 listener.accept(expandAmount, appear);
1438 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001439 }
Selim Cinekcafa87f2016-10-26 17:00:17 -07001440 }
1441
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001442 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekcafa87f2016-10-26 17:00:17 -07001443 private void setRequestedClipBounds(Rect clipRect) {
1444 mRequestedClipBounds = clipRect;
1445 updateClipping();
1446 }
1447
Lucas Dupin60661a62018-04-12 10:50:13 -07001448 /**
1449 * Return the height of the content ignoring the footer.
1450 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001451 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Lucas Dupin60661a62018-04-12 10:50:13 -07001452 public int getIntrinsicContentHeight() {
1453 return mIntrinsicContentHeight;
1454 }
1455
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001456 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcafa87f2016-10-26 17:00:17 -07001457 public void updateClipping() {
1458 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
1459 && !mHeadsUpAnimatingAway;
Lucas Dupin64e2f572019-03-21 14:21:14 -07001460 boolean clipToOutline = false;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001461 if (mIsClipped != clipped) {
1462 mIsClipped = clipped;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001463 }
Lucas Dupin16cfe452018-02-08 13:14:50 -08001464
Selim Cinek195dfc52019-05-30 19:35:05 -07001465 if (mAmbientState.isHiddenAtAll()) {
Lucas Dupin64e2f572019-03-21 14:21:14 -07001466 clipToOutline = true;
1467 invalidateOutline();
Selim Cinek195dfc52019-05-30 19:35:05 -07001468 if (isFullyHidden()) {
1469 setClipBounds(null);
1470 }
Lucas Dupin16cfe452018-02-08 13:14:50 -08001471 } else if (clipped) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07001472 setClipBounds(mRequestedClipBounds);
1473 } else {
1474 setClipBounds(null);
1475 }
Lucas Dupin64e2f572019-03-21 14:21:14 -07001476
1477 setClipToOutline(clipToOutline);
Selim Cinek94c2d822016-07-13 18:50:04 -07001478 }
1479
1480 /**
1481 * @return The translation at the beginning when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001482 * Measured relative to the resting position.
Selim Cinek94c2d822016-07-13 18:50:04 -07001483 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001484 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001485 private float getExpandTranslationStart() {
Selim Cinek083f2142018-11-06 16:32:23 -08001486 return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07001487 }
1488
1489 /**
1490 * @return the position from where the appear transition starts when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001491 * Measured in absolute height.
Selim Cinek94c2d822016-07-13 18:50:04 -07001492 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001493 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001494 private float getAppearStartPosition() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001495 if (isHeadsUpTransition()) {
Gus Prevase2d6f042018-10-17 15:25:30 -04001496 return mHeadsUpInset
1497 + getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
Selim Cinekd127d792016-11-01 19:11:41 -07001498 }
Selim Cinek48ff9b42016-11-09 19:31:51 -08001499 return getMinExpansionHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07001500 }
1501
1502 /**
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001503 * @return the height of the top heads up notification when pinned. This is different from the
Jason Monke59dc402018-08-16 12:05:01 -04001504 * intrinsic height, which also includes whether the notification is system expanded and
1505 * is mainly used when dragging down from a heads up notification.
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001506 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001507 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001508 private int getTopHeadsUpPinnedHeight() {
Ned Burnsf81c4c42019-01-07 14:10:43 -05001509 NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001510 if (topEntry == null) {
1511 return 0;
1512 }
Evan Laird94492852018-10-25 13:43:01 -04001513 ExpandableNotificationRow row = topEntry.getRow();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001514 if (row.isChildInGroup()) {
Ned Burns1c2b85a42019-11-14 15:37:03 -05001515 final NotificationEntry groupSummary =
1516 mGroupManager.getGroupSummary(row.getEntry().getSbn());
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001517 if (groupSummary != null) {
Evan Laird94492852018-10-25 13:43:01 -04001518 row = groupSummary.getRow();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001519 }
1520 }
1521 return row.getPinnedHeadsUpHeight();
1522 }
1523
1524 /**
Selim Cinek94c2d822016-07-13 18:50:04 -07001525 * @return the position from where the appear transition ends when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001526 * Measured in absolute height.
Selim Cinek94c2d822016-07-13 18:50:04 -07001527 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001528 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001529 private float getAppearEndPosition() {
Selim Cinekaa417da2016-10-27 18:17:08 -07001530 int appearPosition;
Selim Cinek66440cf2017-05-26 13:48:47 -07001531 int notGoneChildCount = getNotGoneChildCount();
Julia Reynolds34f14962018-05-03 12:40:20 +00001532 if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001533 if (isHeadsUpTransition()
Selim Cinekc1d9ab22019-05-21 18:08:30 -07001534 || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDozing())) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001535 appearPosition = getTopHeadsUpPinnedHeight();
Selim Cinekcde90e52016-12-22 21:01:49 +01001536 } else {
1537 appearPosition = 0;
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001538 if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) {
1539 appearPosition += mShelf.getIntrinsicHeight();
1540 }
Selim Cinekcde90e52016-12-22 21:01:49 +01001541 }
Selim Cinekaa417da2016-10-27 18:17:08 -07001542 } else {
Selim Cinekcde90e52016-12-22 21:01:49 +01001543 appearPosition = mEmptyShadeView.getHeight();
Selim Cinekaa417da2016-10-27 18:17:08 -07001544 }
1545 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
Selim Cinek94c2d822016-07-13 18:50:04 -07001546 }
1547
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001548 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001549 private boolean isHeadsUpTransition() {
Gus Prevase2d6f042018-10-17 15:25:30 -04001550 NotificationSection firstVisibleSection = getFirstVisibleSection();
1551 return mTrackingHeadsUp && firstVisibleSection != null
Selim Cinek459aee32019-02-20 11:18:56 -08001552 && firstVisibleSection.getFirstVisibleChild().isAboveShelf();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001553 }
1554
Selim Cinek94c2d822016-07-13 18:50:04 -07001555 /**
1556 * @param height the height of the panel
1557 * @return the fraction of the appear animation that has been performed
1558 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001559 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001560 public float calculateAppearFraction(float height) {
Selim Cinek94c2d822016-07-13 18:50:04 -07001561 float appearEndPosition = getAppearEndPosition();
1562 float appearStartPosition = getAppearStartPosition();
1563 return (height - appearStartPosition)
1564 / (appearEndPosition - appearStartPosition);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001565 }
1566
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001567 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd2281152015-04-10 14:37:46 -07001568 public float getStackTranslation() {
1569 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001570 }
1571
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001572 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd2281152015-04-10 14:37:46 -07001573 private void setStackTranslation(float stackTranslation) {
1574 if (stackTranslation != mStackTranslation) {
1575 mStackTranslation = stackTranslation;
1576 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001577 requestChildrenUpdate();
1578 }
Selim Cinek67b22602014-03-10 15:40:16 +01001579 }
1580
1581 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001582 * 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 +01001583 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
1584 *
1585 * @return either the layout height or the externally defined height, whichever is smaller
1586 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001587 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek343e6e22014-04-11 21:23:30 +02001588 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001589 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
1590 }
1591
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001592 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek816c8e42015-11-19 12:00:45 -08001593 public int getFirstItemMinHeight() {
1594 final ExpandableView firstChild = getFirstChildNotGone();
1595 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001596 }
1597
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001598 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Jason Monk16ac3772016-02-10 15:39:21 -05001599 public void setQsContainer(ViewGroup qsContainer) {
1600 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +02001601 }
1602
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001603 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek684a4422015-04-15 16:18:39 -07001604 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001605 if (v instanceof ExpandableNotificationRow) {
1606 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -07001607 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -07001608 }
1609 return false;
1610 }
1611
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001612 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cineka59ecc32015-04-07 10:51:49 -07001613 private boolean isHeadsUp(View v) {
1614 if (v instanceof ExpandableNotificationRow) {
1615 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1616 return row.isHeadsUp();
1617 }
1618 return false;
1619 }
1620
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001621 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001622 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
1623 getLocationOnScreen(mTempInt2);
1624 float localTouchY = touchY - mTempInt2[1];
1625
1626 ExpandableView closestChild = null;
1627 float minDist = Float.MAX_VALUE;
1628
1629 // find the view closest to the location, accounting for GONE views
1630 final int count = getChildCount();
1631 for (int childIdx = 0; childIdx < count; childIdx++) {
1632 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1633 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -08001634 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001635 continue;
1636 }
1637 float childTop = slidingChild.getTranslationY();
1638 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001639 float bottom = childTop + slidingChild.getActualHeight()
1640 - slidingChild.getClipBottomAmount();
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001641
1642 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
1643 if (dist < minDist) {
1644 closestChild = slidingChild;
1645 minDist = dist;
1646 }
1647 }
1648 return closestChild;
1649 }
1650
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001651 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1652 private ExpandableView getChildAtPosition(float touchX, float touchY) {
Steve Elliottb0940382020-02-20 14:24:02 -05001653 return getChildAtPosition(
1654 touchX, touchY, true /* requireMinHeight */, true /* ignoreDecors */);
Selim Cinek34ed7c02017-09-08 15:03:12 -07001655 }
1656
1657 /**
1658 * Get the child at a certain screen location.
1659 *
Jason Monke59dc402018-08-16 12:05:01 -04001660 * @param touchX the x coordinate
1661 * @param touchY the y coordinate
Selim Cinek34ed7c02017-09-08 15:03:12 -07001662 * @param requireMinHeight Whether a minimum height is required for a child to be returned.
Steve Elliottb0940382020-02-20 14:24:02 -05001663 * @param ignoreDecors Whether decors can be returned
Selim Cinek34ed7c02017-09-08 15:03:12 -07001664 * @return the child at the given location.
1665 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001666 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek34ed7c02017-09-08 15:03:12 -07001667 private ExpandableView getChildAtPosition(float touchX, float touchY,
Steve Elliottb0940382020-02-20 14:24:02 -05001668 boolean requireMinHeight, boolean ignoreDecors) {
Selim Cinek67b22602014-03-10 15:40:16 +01001669 // find the view under the pointer, accounting for GONE views
1670 final int count = getChildCount();
1671 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001672 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek51d21972017-07-19 17:39:20 -07001673 if (slidingChild.getVisibility() != VISIBLE
Steve Elliottb0940382020-02-20 14:24:02 -05001674 || (ignoreDecors && slidingChild instanceof StackScrollerDecorView)) {
Selim Cinek67b22602014-03-10 15:40:16 +01001675 continue;
1676 }
Selim Cinek89faff12014-06-19 16:29:04 -07001677 float childTop = slidingChild.getTranslationY();
1678 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001679 float bottom = childTop + slidingChild.getActualHeight()
1680 - slidingChild.getClipBottomAmount();
Jorim Jaggi28f0e592014-08-05 22:03:07 +02001681
1682 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
1683 // camera affordance).
1684 int left = 0;
1685 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +01001686
Selim Cinek34ed7c02017-09-08 15:03:12 -07001687 if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
Selim Cinek51d21972017-07-19 17:39:20 -07001688 && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001689 if (slidingChild instanceof ExpandableNotificationRow) {
1690 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Ned Burnsf81c4c42019-01-07 14:10:43 -05001691 NotificationEntry entry = row.getEntry();
Selim Cinek131c1e22015-05-11 19:04:49 -07001692 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Evan Laird94492852018-10-25 13:43:01 -04001693 && mHeadsUpManager.getTopEntry().getRow() != row
Selim Cinek5bc852a2015-12-21 12:19:09 -08001694 && mGroupManager.getGroupSummary(
Ned Burns00b4b2d2019-10-17 22:09:27 -04001695 mHeadsUpManager.getTopEntry().getSbn())
Evan Laird94492852018-10-25 13:43:01 -04001696 != entry) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001697 continue;
1698 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001699 return row.getViewAtPosition(touchY - childTop);
1700 }
Selim Cinek67b22602014-03-10 15:40:16 +01001701 return slidingChild;
1702 }
1703 }
1704 return null;
1705 }
1706
Selim Cinek3d6ae232019-01-04 14:14:33 -08001707 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001708 getLocationOnScreen(mTempInt2);
1709 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001710 }
1711
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001712 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001713 public void setScrollingEnabled(boolean enable) {
1714 mScrollingEnabled = enable;
1715 }
1716
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001717 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04001718 public void lockScrollTo(View v) {
1719 if (mForcedScroll == v) {
1720 return;
1721 }
1722 mForcedScroll = v;
1723 scrollTo(v);
1724 }
1725
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001726 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ricky Waicd35def2016-05-03 11:07:07 +01001727 public boolean scrollTo(View v) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001728 ExpandableView expandableView = (ExpandableView) v;
Gus Prevas0fa58d62019-01-11 13:58:40 -05001729 if (ANCHOR_SCROLLING) {
1730 // TODO
1731 } else {
1732 int positionInLinearLayout = getPositionInLinearLayout(v);
1733 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1734 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Ricky Waicd35def2016-05-03 11:07:07 +01001735
Gus Prevas0fa58d62019-01-11 13:58:40 -05001736 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1737 // that it is not visible anymore.
1738 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1739 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1740 mDontReportNextOverScroll = true;
1741 animateScroll();
1742 return true;
1743 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001744 }
Ricky Waicd35def2016-05-03 11:07:07 +01001745 return false;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001746 }
1747
Adrian Roos181385c2016-05-05 17:45:44 -04001748 /**
1749 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
Jason Monke59dc402018-08-16 12:05:01 -04001750 * the IME.
Adrian Roos181385c2016-05-05 17:45:44 -04001751 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001752 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Adrian Roos181385c2016-05-05 17:45:44 -04001753 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1754 return positionInLinearLayout + v.getIntrinsicHeight() +
felkachang529bfe62018-07-04 12:51:44 +08001755 getImeInset() - getHeight()
1756 + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
Adrian Roos181385c2016-05-05 17:45:44 -04001757 }
1758
Adrian Roos5153d4a2016-03-22 10:01:56 -07001759 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001760 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Adrian Roos5153d4a2016-03-22 10:01:56 -07001761 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Selim Cineka424c502016-04-05 13:07:54 -07001762 mBottomInset = insets.getSystemWindowInsetBottom();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001763
shawnlinbd6d0ad2020-01-29 18:38:49 +08001764 mWaterfallTopInset = 0;
1765 final DisplayCutout cutout = insets.getDisplayCutout();
1766 if (cutout != null) {
1767 mWaterfallTopInset = cutout.getWaterfallInsets().top;
1768 }
1769
Gus Prevas0fa58d62019-01-11 13:58:40 -05001770 if (ANCHOR_SCROLLING) {
1771 // TODO
1772 } else {
1773 int range = getScrollRange();
1774 if (mOwnScrollY > range) {
1775 // HACK: We're repeatedly getting staggered insets here while the IME is
1776 // animating away. To work around that we'll wait until things have settled.
1777 removeCallbacks(mReclamp);
1778 postDelayed(mReclamp, 50);
1779 } else if (mForcedScroll != null) {
1780 // The scroll was requested before we got the actual inset - in case we need
1781 // to scroll up some more do so now.
1782 scrollTo(mForcedScroll);
1783 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001784 }
1785 return insets;
1786 }
1787
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001788 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Adrian Roos5153d4a2016-03-22 10:01:56 -07001789 private Runnable mReclamp = new Runnable() {
1790 @Override
1791 public void run() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001792 if (ANCHOR_SCROLLING) {
1793 // TODO
1794 } else {
1795 int range = getScrollRange();
1796 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1797 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001798 mDontReportNextOverScroll = true;
1799 mDontClampNextScroll = true;
Selim Cinek9212de82017-02-06 16:04:28 -08001800 animateScroll();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001801 }
1802 };
1803
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001804 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1805 public void setExpandingEnabled(boolean enable) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001806 mExpandHelper.setEnabled(enable);
1807 }
1808
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001809 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001810 private boolean isScrollingEnabled() {
1811 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +01001812 }
1813
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001814 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbc243a92016-09-27 16:35:13 -07001815 private boolean onKeyguard() {
Selim Cinek355652a2016-12-07 13:32:12 -08001816 return mStatusBarState == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +02001817 }
1818
Selim Cinek67b22602014-03-10 15:40:16 +01001819 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001820 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001821 protected void onConfigurationChanged(Configuration newConfig) {
1822 super.onConfigurationChanged(newConfig);
Adrian Roos22af6502018-02-22 16:57:08 +01001823 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
Selim Cinek67b22602014-03-10 15:40:16 +01001824 float densityScale = getResources().getDisplayMetrics().density;
1825 mSwipeHelper.setDensityScale(densityScale);
1826 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1827 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1828 initView(getContext());
1829 }
1830
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001831 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dan Sandlereceda3d2014-07-21 15:35:01 -04001832 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07001833 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1834 true /* isDismissAll */);
Selim Cinek67b22602014-03-10 15:40:16 +01001835 }
1836
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001837 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001838 private void snapViewIfNeeded(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04001839 ExpandableNotificationRow child = entry.getRow();
dongwan0605.kim30637e42016-03-02 17:16:47 +09001840 boolean animate = mIsExpanded || isPinnedHeadsUp(child);
Mady Mellor95d743c2017-01-10 12:05:27 -08001841 // If the child is showing the notification menu snap to that
Evan Lairde55c6012019-03-13 12:54:37 -04001842 if (child.getProvider() != null) {
1843 float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
1844 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
1845 }
dongwan0605.kim30637e42016-03-02 17:16:47 +09001846 }
1847
Selim Cinek67b22602014-03-10 15:40:16 +01001848 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001849 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001850 public ViewGroup getViewParentForNotification(NotificationEntry entry) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001851 return this;
1852 }
1853
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001854 /**
1855 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1856 *
1857 * @param deltaY The amount to scroll upwards, has to be positive.
1858 * @return The amount of scrolling to be performed by the scroller,
Jason Monke59dc402018-08-16 12:05:01 -04001859 * not handled by the overScroll amount.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001860 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001861 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001862 private float overScrollUp(int deltaY, int range) {
1863 deltaY = Math.max(deltaY, 0);
1864 float currentTopAmount = getCurrentOverScrollAmount(true);
1865 float newTopAmount = currentTopAmount - deltaY;
1866 if (currentTopAmount > 0) {
1867 setOverScrollAmount(newTopAmount, true /* onTop */,
1868 false /* animate */);
1869 }
1870 // Top overScroll might not grab all scrolling motion,
1871 // we have to scroll as well.
Gus Prevas0fa58d62019-01-11 13:58:40 -05001872 if (ANCHOR_SCROLLING) {
1873 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1874 // TODO: once we're recycling this will need to check the adapter position of the child
1875 ExpandableView lastRow = getLastRowNotGone();
Gus Prevascdc98342019-01-14 14:29:44 -05001876 if (lastRow != null && !lastRow.isInShelf()) {
1877 float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
1878 if (scrollAmount > distanceToMax) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001879 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1880 // We overScroll on the bottom
1881 setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
1882 false /* onTop */,
1883 false /* animate */);
1884 mScrollAnchorViewY -= distanceToMax;
1885 scrollAmount = 0f;
1886 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001887 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001888 return scrollAmount;
1889 } else {
1890 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1891 float newScrollY = mOwnScrollY + scrollAmount;
1892 if (newScrollY > range) {
1893 if (!mExpandedInThisMotion) {
1894 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1895 // We overScroll on the bottom
1896 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1897 false /* onTop */,
1898 false /* animate */);
1899 }
1900 setOwnScrollY(range);
1901 scrollAmount = 0.0f;
1902 }
1903 return scrollAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001904 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001905 }
1906
1907 /**
1908 * Perform a scroll downward and adapt the overscroll amounts accordingly
1909 *
1910 * @param deltaY The amount to scroll downwards, has to be negative.
1911 * @return The amount of scrolling to be performed by the scroller,
Jason Monke59dc402018-08-16 12:05:01 -04001912 * not handled by the overScroll amount.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001913 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001914 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001915 private float overScrollDown(int deltaY) {
1916 deltaY = Math.min(deltaY, 0);
1917 float currentBottomAmount = getCurrentOverScrollAmount(false);
1918 float newBottomAmount = currentBottomAmount + deltaY;
1919 if (currentBottomAmount > 0) {
1920 setOverScrollAmount(newBottomAmount, false /* onTop */,
1921 false /* animate */);
1922 }
1923 // Bottom overScroll might not grab all scrolling motion,
1924 // we have to scroll as well.
Gus Prevas0fa58d62019-01-11 13:58:40 -05001925 if (ANCHOR_SCROLLING) {
1926 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1927 // TODO: once we're recycling this will need to check the adapter position of the child
1928 ExpandableView firstChild = getFirstChildNotGone();
1929 float top = firstChild.getTranslationY();
1930 float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
1931 if (distanceToTop < -scrollAmount) {
1932 float currentTopPixels = getCurrentOverScrolledPixels(true);
1933 // We overScroll on the top
1934 setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
1935 true /* onTop */,
1936 false /* animate */);
1937 mScrollAnchorView = firstChild;
1938 mScrollAnchorViewY = 0;
1939 scrollAmount = 0f;
1940 }
1941 return scrollAmount;
1942 } else {
1943 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1944 float newScrollY = mOwnScrollY + scrollAmount;
1945 if (newScrollY < 0) {
1946 float currentTopPixels = getCurrentOverScrolledPixels(true);
1947 // We overScroll on the top
1948 setOverScrolledPixels(currentTopPixels - newScrollY,
1949 true /* onTop */,
1950 false /* animate */);
1951 setOwnScrollY(0);
1952 scrollAmount = 0.0f;
1953 }
1954 return scrollAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001955 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001956 }
1957
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001958 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001959 private void initVelocityTrackerIfNotExists() {
1960 if (mVelocityTracker == null) {
1961 mVelocityTracker = VelocityTracker.obtain();
1962 }
1963 }
1964
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001965 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001966 private void recycleVelocityTracker() {
1967 if (mVelocityTracker != null) {
1968 mVelocityTracker.recycle();
1969 mVelocityTracker = null;
1970 }
1971 }
1972
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001973 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001974 private void initOrResetVelocityTracker() {
1975 if (mVelocityTracker == null) {
1976 mVelocityTracker = VelocityTracker.obtain();
1977 } else {
1978 mVelocityTracker.clear();
1979 }
1980 }
1981
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001982 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ricky Waicd35def2016-05-03 11:07:07 +01001983 public void setFinishScrollingCallback(Runnable runnable) {
1984 mFinishScrollingCallback = runnable;
1985 }
1986
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001987 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek9212de82017-02-06 16:04:28 -08001988 private void animateScroll() {
Selim Cinek67b22602014-03-10 15:40:16 +01001989 if (mScroller.computeScrollOffset()) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001990 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05001991 int oldY = mLastScrollerY;
1992 int y = mScroller.getCurrY();
1993 int deltaY = y - oldY;
1994 if (deltaY != 0) {
1995 int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
1996 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
1997 if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
1998 || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
1999 // This frame takes us into overscroll, so set the max overscroll based on
2000 // the current velocity
2001 setMaxOverScrollFromCurrentVelocity();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002002 }
Gus Prevascdc98342019-01-14 14:29:44 -05002003 customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
2004 mLastScrollerY = y;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002005 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002006 } else {
2007 int oldY = mOwnScrollY;
2008 int y = mScroller.getCurrY();
Selim Cinek67b22602014-03-10 15:40:16 +01002009
Gus Prevas0fa58d62019-01-11 13:58:40 -05002010 if (oldY != y) {
2011 int range = getScrollRange();
2012 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
Gus Prevascdc98342019-01-14 14:29:44 -05002013 // This frame takes us into overscroll, so set the max overscroll based on
2014 // the current velocity
2015 setMaxOverScrollFromCurrentVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +01002016 }
Selim Cinek67b22602014-03-10 15:40:16 +01002017
Gus Prevas0fa58d62019-01-11 13:58:40 -05002018 if (mDontClampNextScroll) {
2019 range = Math.max(range, oldY);
2020 }
2021 customOverScrollBy(y - oldY, oldY, range,
2022 (int) (mMaxOverScroll));
Adrian Roos5153d4a2016-03-22 10:01:56 -07002023 }
Selim Cinek67b22602014-03-10 15:40:16 +01002024 }
2025
Gus Prevascdc98342019-01-14 14:29:44 -05002026 postOnAnimation(mReflingAndAnimateScroll);
Adrian Roos5153d4a2016-03-22 10:01:56 -07002027 } else {
2028 mDontClampNextScroll = false;
Ricky Waicd35def2016-05-03 11:07:07 +01002029 if (mFinishScrollingCallback != null) {
2030 mFinishScrollingCallback.run();
2031 }
Selim Cinek67b22602014-03-10 15:40:16 +01002032 }
2033 }
2034
Gus Prevascdc98342019-01-14 14:29:44 -05002035 private void setMaxOverScrollFromCurrentVelocity() {
2036 float currVelocity = mScroller.getCurrVelocity();
2037 if (currVelocity >= mMinimumVelocity) {
2038 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
Selim Cinek4195dd02014-05-19 18:16:14 +02002039 }
Gus Prevascdc98342019-01-14 14:29:44 -05002040 }
Selim Cinek4195dd02014-05-19 18:16:14 +02002041
Gus Prevascdc98342019-01-14 14:29:44 -05002042 /**
2043 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
2044 * would cause us to exceed the provided maximum overscroll, springs back instead.
2045 *
2046 * This method performs the determination of whether we're exceeding the overscroll and clamps
2047 * the scroll amount if so. The actual scrolling/overscrolling happens in
2048 * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
2049 * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
2050 *
2051 * @param deltaY The (signed) number of pixels to scroll.
2052 * @param scrollY The current scroll position (absolute scrolling only).
2053 * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
2054 * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
2055 */
Selim Cinek4195dd02014-05-19 18:16:14 +02002056 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Gus Prevascdc98342019-01-14 14:29:44 -05002057 private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002058 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05002059 boolean clampedY = false;
2060 if (deltaY < 0) {
2061 int maxScrollAmount = getMaxNegativeScrollAmount();
2062 if (maxScrollAmount > Integer.MIN_VALUE) {
2063 maxScrollAmount -= maxOverScrollY;
2064 if (deltaY < maxScrollAmount) {
2065 deltaY = maxScrollAmount;
2066 clampedY = true;
2067 }
2068 }
2069 } else {
2070 int maxScrollAmount = getMaxPositiveScrollAmount();
2071 if (maxScrollAmount < Integer.MAX_VALUE) {
2072 maxScrollAmount += maxOverScrollY;
2073 if (deltaY > maxScrollAmount) {
2074 deltaY = maxScrollAmount;
2075 clampedY = true;
2076 }
2077 }
2078 }
2079 onCustomOverScrolledBy(deltaY, clampedY);
Gus Prevas0fa58d62019-01-11 13:58:40 -05002080 } else {
2081 int newScrollY = scrollY + deltaY;
2082 final int top = -maxOverScrollY;
2083 final int bottom = maxOverScrollY + scrollRangeY;
Selim Cinek4195dd02014-05-19 18:16:14 +02002084
Gus Prevas0fa58d62019-01-11 13:58:40 -05002085 boolean clampedY = false;
2086 if (newScrollY > bottom) {
2087 newScrollY = bottom;
2088 clampedY = true;
2089 } else if (newScrollY < top) {
2090 newScrollY = top;
2091 clampedY = true;
2092 }
Selim Cinek4195dd02014-05-19 18:16:14 +02002093
Gus Prevas0fa58d62019-01-11 13:58:40 -05002094 onCustomOverScrolled(newScrollY, clampedY);
Selim Cinek4195dd02014-05-19 18:16:14 +02002095 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002096 }
2097
2098 /**
2099 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
2100 * overscroll effect based on numPixels. By default this will also cancel animations on the
2101 * same overScroll edge.
2102 *
2103 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
2104 * the rubber-banding logic.
Jason Monke59dc402018-08-16 12:05:01 -04002105 * @param onTop Should the effect be applied on top of the scroller.
2106 * @param animate Should an animation be performed.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002107 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002108 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002109 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07002110 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002111 }
2112
2113 /**
2114 * Set the effective overScroll amount which will be directly reflected in the layout.
2115 * By default this will also cancel animations on the same overScroll edge.
2116 *
Jason Monke59dc402018-08-16 12:05:01 -04002117 * @param amount The amount to overScroll by.
2118 * @param onTop Should the effect be applied on top of the scroller.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002119 * @param animate Should an animation be performed.
2120 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002121
2122 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002123 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
2124 setOverScrollAmount(amount, onTop, animate, true);
2125 }
2126
2127 /**
2128 * Set the effective overScroll amount which will be directly reflected in the layout.
2129 *
Jason Monke59dc402018-08-16 12:05:01 -04002130 * @param amount The amount to overScroll by.
2131 * @param onTop Should the effect be applied on top of the scroller.
2132 * @param animate Should an animation be performed.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002133 * @param cancelAnimators Should running animations be cancelled.
2134 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002135 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002136 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2137 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002138 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
2139 }
2140
2141 /**
2142 * Set the effective overScroll amount which will be directly reflected in the layout.
2143 *
Jason Monke59dc402018-08-16 12:05:01 -04002144 * @param amount The amount to overScroll by.
2145 * @param onTop Should the effect be applied on top of the scroller.
2146 * @param animate Should an animation be performed.
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002147 * @param cancelAnimators Should running animations be cancelled.
Jason Monke59dc402018-08-16 12:05:01 -04002148 * @param isRubberbanded The value which will be passed to
2149 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002150 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002151 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002152 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2153 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002154 if (cancelAnimators) {
2155 mStateAnimator.cancelOverScrollAnimators(onTop);
2156 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002157 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002158 }
2159
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002160 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002161 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
2162 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002163 amount = Math.max(0, amount);
2164 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002165 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002166 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07002167 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002168 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002169 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002170 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002171 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002172 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002173 }
2174 }
2175
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002176 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002177 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002178 mExpandHelper.onlyObserveMovements(amount > 1.0f);
2179 if (mDontReportNextOverScroll) {
2180 mDontReportNextOverScroll = false;
2181 return;
2182 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02002183 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002184 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002185 }
2186 }
2187
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002188 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi290600a2014-05-30 17:02:20 +02002189 public void setOverscrollTopChangedListener(
2190 OnOverscrollTopChangedListener overscrollTopChangedListener) {
2191 mOverscrollTopChangedListener = overscrollTopChangedListener;
2192 }
2193
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002194 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002195 public float getCurrentOverScrollAmount(boolean top) {
2196 return mAmbientState.getOverScrollAmount(top);
2197 }
2198
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002199 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002200 public float getCurrentOverScrolledPixels(boolean top) {
Jason Monke59dc402018-08-16 12:05:01 -04002201 return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002202 }
2203
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002204 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002205 private void setOverScrolledPixels(float amount, boolean onTop) {
2206 if (onTop) {
2207 mOverScrolledTopPixels = amount;
2208 } else {
2209 mOverScrolledBottomPixels = amount;
2210 }
2211 }
2212
Gus Prevascdc98342019-01-14 14:29:44 -05002213 /**
2214 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
2215 * would cause us to exceed the provided maximum overscroll, springs back instead.
2216 *
2217 * @param deltaY The (signed) number of pixels to scroll.
2218 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2219 * the overscroll limit.
2220 */
2221 private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
2222 assert ANCHOR_SCROLLING;
Gus Prevas0fa58d62019-01-11 13:58:40 -05002223 mScrollAnchorViewY -= deltaY;
2224 // Treat animating scrolls differently; see #computeScroll() for why.
2225 if (!mScroller.isFinished()) {
Gus Prevascdc98342019-01-14 14:29:44 -05002226 if (clampedY) {
2227 springBack();
2228 } else {
2229 float overScrollTop = getCurrentOverScrollAmount(true /* top */);
2230 if (isScrolledToTop() && mScrollAnchorViewY > 0) {
2231 notifyOverscrollTopListener(mScrollAnchorViewY,
2232 isRubberbanded(true /* onTop */));
2233 } else {
2234 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
2235 }
2236 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002237 }
2238 updateScrollAnchor();
2239 updateOnScrollChange();
2240 }
2241
Gus Prevascdc98342019-01-14 14:29:44 -05002242 /**
2243 * Scrolls to the given position, overscrolling if needed. If called during a fling and the
2244 * position exceeds the provided maximum overscroll, springs back instead.
2245 *
2246 * @param scrollY The target scroll position.
2247 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2248 * the overscroll limit.
2249 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002250 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek9212de82017-02-06 16:04:28 -08002251 private void onCustomOverScrolled(int scrollY, boolean clampedY) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002252 assert !ANCHOR_SCROLLING;
Selim Cinek67b22602014-03-10 15:40:16 +01002253 // Treat animating scrolls differently; see #computeScroll() for why.
2254 if (!mScroller.isFinished()) {
Selim Cinekef406062016-09-29 17:33:13 -07002255 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01002256 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002257 springBack();
2258 } else {
Jorim Jaggi290600a2014-05-30 17:02:20 +02002259 float overScrollTop = getCurrentOverScrollAmount(true);
2260 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002261 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02002262 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002263 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02002264 }
Selim Cinek67b22602014-03-10 15:40:16 +01002265 }
Selim Cinek67b22602014-03-10 15:40:16 +01002266 } else {
Selim Cinek9212de82017-02-06 16:04:28 -08002267 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01002268 }
2269 }
2270
Gus Prevascdc98342019-01-14 14:29:44 -05002271 /**
2272 * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
2273 * overscroll amount back to zero.
2274 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002275 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002276 private void springBack() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002277 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05002278 boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
2279 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
2280 boolean overscrolledBottom = maxPositiveScrollAmount < 0;
2281 if (overScrolledTop || overscrolledBottom) {
2282 float newAmount;
2283 if (overScrolledTop) {
2284 newAmount = mScrollAnchorViewY;
2285 mScrollAnchorViewY = 0;
2286 mDontReportNextOverScroll = true;
2287 } else {
2288 newAmount = -maxPositiveScrollAmount;
2289 mScrollAnchorViewY -= maxPositiveScrollAmount;
2290 }
2291 setOverScrollAmount(newAmount, overScrolledTop, false);
2292 setOverScrollAmount(0.0f, overScrolledTop, true);
2293 mScroller.forceFinished(true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002294 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002295 } else {
2296 int scrollRange = getScrollRange();
2297 boolean overScrolledTop = mOwnScrollY <= 0;
2298 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
2299 if (overScrolledTop || overScrolledBottom) {
2300 boolean onTop;
2301 float newAmount;
2302 if (overScrolledTop) {
2303 onTop = true;
2304 newAmount = -mOwnScrollY;
2305 setOwnScrollY(0);
2306 mDontReportNextOverScroll = true;
2307 } else {
2308 onTop = false;
2309 newAmount = mOwnScrollY - scrollRange;
2310 setOwnScrollY(scrollRange);
2311 }
2312 setOverScrollAmount(newAmount, onTop, false);
2313 setOverScrollAmount(0.0f, onTop, true);
2314 mScroller.forceFinished(true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002315 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002316 }
2317 }
2318
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002319 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek67b22602014-03-10 15:40:16 +01002320 private int getScrollRange() {
felkachang529bfe62018-07-04 12:51:44 +08002321 // In current design, it only use the top HUN to treat all of HUNs
2322 // although there are more than one HUNs
2323 int contentHeight = mContentHeight;
2324 if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
2325 contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
2326 }
2327 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
Selim Cineka424c502016-04-05 13:07:54 -07002328 int imeInset = getImeInset();
felkachang529bfe62018-07-04 12:51:44 +08002329 scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
Selim Cineka424c502016-04-05 13:07:54 -07002330 return scrollRange;
2331 }
2332
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002333 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cineka424c502016-04-05 13:07:54 -07002334 private int getImeInset() {
2335 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
Selim Cinek67b22602014-03-10 15:40:16 +01002336 }
2337
Selim Cinek343e6e22014-04-11 21:23:30 +02002338 /**
2339 * @return the first child which has visibility unequal to GONE
2340 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002341 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb55386d2015-12-16 17:26:49 -08002342 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02002343 int childCount = getChildCount();
2344 for (int i = 0; i < childCount; i++) {
2345 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002346 if (child.getVisibility() != View.GONE && child != mShelf) {
Selim Cinek816c8e42015-11-19 12:00:45 -08002347 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02002348 }
2349 }
2350 return null;
2351 }
2352
Selim Cinek4a1ac842014-05-01 15:51:58 +02002353 /**
Selim Cinek1b2a05e2016-04-28 14:20:39 -07002354 * @return the child before the given view which has visibility unequal to GONE
2355 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002356 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1b2a05e2016-04-28 14:20:39 -07002357 public ExpandableView getViewBeforeView(ExpandableView view) {
2358 ExpandableView previousView = null;
2359 int childCount = getChildCount();
2360 for (int i = 0; i < childCount; i++) {
2361 View child = getChildAt(i);
2362 if (child == view) {
2363 return previousView;
2364 }
2365 if (child.getVisibility() != View.GONE) {
2366 previousView = (ExpandableView) child;
2367 }
2368 }
2369 return null;
2370 }
2371
2372 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002373 * @return The first child which has visibility unequal to GONE which is currently below the
Jason Monke59dc402018-08-16 12:05:01 -04002374 * given translationY or equal to it.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002375 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002376 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekef8c2252017-02-10 14:52:18 -08002377 private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002378 int childCount = getChildCount();
2379 for (int i = 0; i < childCount; i++) {
2380 View child = getChildAt(i);
Selim Cinekef8c2252017-02-10 14:52:18 -08002381 if (child.getVisibility() == View.GONE) {
2382 continue;
2383 }
2384 float rowTranslation = child.getTranslationY();
2385 if (rowTranslation >= translationY) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002386 return child;
Selim Cinekef8c2252017-02-10 14:52:18 -08002387 } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
2388 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2389 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
2390 List<ExpandableNotificationRow> notificationChildren =
Kevin Han43077f92020-02-28 12:51:53 -08002391 row.getAttachedChildren();
Selim Cinekef8c2252017-02-10 14:52:18 -08002392 for (int childIndex = 0; childIndex < notificationChildren.size();
2393 childIndex++) {
2394 ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
2395 if (rowChild.getTranslationY() + rowTranslation >= translationY) {
2396 return rowChild;
2397 }
2398 }
2399 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002400 }
2401 }
2402 return null;
2403 }
2404
2405 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02002406 * @return the last child which has visibility unequal to GONE
2407 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002408 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002409 public ExpandableView getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02002410 int childCount = getChildCount();
2411 for (int i = childCount - 1; i >= 0; i--) {
2412 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002413 if (child.getVisibility() != View.GONE && child != mShelf) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002414 return (ExpandableView) child;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002415 }
2416 }
2417 return null;
2418 }
2419
Gus Prevas0fa58d62019-01-11 13:58:40 -05002420 private ExpandableNotificationRow getLastRowNotGone() {
2421 int childCount = getChildCount();
2422 for (int i = childCount - 1; i >= 0; i--) {
2423 View child = getChildAt(i);
2424 if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
2425 return (ExpandableNotificationRow) child;
2426 }
2427 }
2428 return null;
2429 }
2430
Jorim Jaggi069cd032014-05-15 03:09:01 +02002431 /**
2432 * @return the number of children which have visibility unequal to GONE
2433 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002434 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi069cd032014-05-15 03:09:01 +02002435 public int getNotGoneChildCount() {
2436 int childCount = getChildCount();
2437 int count = 0;
2438 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07002439 ExpandableView child = (ExpandableView) getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002440 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02002441 count++;
2442 }
2443 }
2444 return count;
2445 }
2446
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002447 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01002448 private void updateContentHeight() {
2449 int height = 0;
Selim Cineka7ed2c12017-01-23 20:47:24 -08002450 float previousPaddingRequest = mPaddingBetweenElements;
2451 float previousPaddingAmount = 0.0f;
Selim Cinekad7fac02016-10-18 17:09:15 -07002452 int numShownItems = 0;
2453 boolean finish = false;
Selim Cinek5040f2e2019-02-14 18:22:42 -08002454 int maxDisplayedNotifications = mMaxDisplayedNotifications;
Adrian Roos7a9551a2017-01-11 12:27:49 -08002455
Selim Cinek67b22602014-03-10 15:40:16 +01002456 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002457 ExpandableView expandableView = (ExpandableView) getChildAt(i);
Lucas Dupin60661a62018-04-12 10:50:13 -07002458 boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
Selim Cinek281c2022016-10-13 19:14:43 -07002459 if (expandableView.getVisibility() != View.GONE
Lucas Dupin60661a62018-04-12 10:50:13 -07002460 && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
Adrian Roos7d062c42017-03-30 15:11:43 -07002461 boolean limitReached = maxDisplayedNotifications != -1
2462 && numShownItems >= maxDisplayedNotifications;
Selim Cinek5040f2e2019-02-14 18:22:42 -08002463 if (limitReached) {
Selim Cinekad7fac02016-10-18 17:09:15 -07002464 expandableView = mShelf;
2465 finish = true;
2466 }
Selim Cinek42357e02016-02-24 18:48:01 -08002467 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Selim Cineka7ed2c12017-01-23 20:47:24 -08002468 float padding;
2469 if (increasedPaddingAmount >= 0.0f) {
2470 padding = (int) NotificationUtils.interpolate(
2471 previousPaddingRequest,
2472 mIncreasedPaddingBetweenElements,
2473 increasedPaddingAmount);
2474 previousPaddingRequest = (int) NotificationUtils.interpolate(
Selim Cinek42357e02016-02-24 18:48:01 -08002475 mPaddingBetweenElements,
2476 mIncreasedPaddingBetweenElements,
Selim Cineka7ed2c12017-01-23 20:47:24 -08002477 increasedPaddingAmount);
2478 } else {
2479 int ownPadding = (int) NotificationUtils.interpolate(
2480 0,
2481 mPaddingBetweenElements,
2482 1.0f + increasedPaddingAmount);
2483 if (previousPaddingAmount > 0.0f) {
2484 padding = (int) NotificationUtils.interpolate(
2485 ownPadding,
2486 mIncreasedPaddingBetweenElements,
2487 previousPaddingAmount);
2488 } else {
2489 padding = ownPadding;
2490 }
2491 previousPaddingRequest = ownPadding;
Jorim Jaggid4a57442014-04-10 02:45:55 +02002492 }
Selim Cineka7ed2c12017-01-23 20:47:24 -08002493 if (height != 0) {
2494 height += padding;
2495 }
2496 previousPaddingAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002497 height += expandableView.getIntrinsicHeight();
Selim Cinekad7fac02016-10-18 17:09:15 -07002498 numShownItems++;
2499 if (finish) {
2500 break;
2501 }
Selim Cinek67b22602014-03-10 15:40:16 +01002502 }
2503 }
Lucas Dupin60661a62018-04-12 10:50:13 -07002504 mIntrinsicContentHeight = height;
Selim Cinekf4b04ae2018-06-13 18:23:45 -07002505
Selim Cinekdb6d6df2019-05-22 20:39:59 -07002506 // The topPadding can be bigger than the regular padding when qs is expanded, in that
2507 // state the maxPanelHeight and the contentHeight should be bigger
2508 mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
Selim Cinekc22fff62016-05-20 12:44:30 -07002509 updateScrollability();
Selim Cinek51d21972017-07-19 17:39:20 -07002510 clampScrollPosition();
Selim Cinek91d4cba2016-11-10 19:59:48 -08002511 mAmbientState.setLayoutMaxHeight(mContentHeight);
Selim Cinekc22fff62016-05-20 12:44:30 -07002512 }
2513
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09002514 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002515 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbe2c4432017-05-30 12:11:09 -07002516 public boolean hasPulsingNotifications() {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09002517 return mPulsing;
Adrian Roos7d062c42017-03-30 15:11:43 -07002518 }
2519
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002520 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07002521 private void updateScrollability() {
Riddle Hsu065c01c2018-05-10 23:14:19 +08002522 boolean scrollable = !mQsExpanded && getScrollRange() > 0;
Selim Cinekc22fff62016-05-20 12:44:30 -07002523 if (scrollable != mScrollable) {
2524 mScrollable = scrollable;
2525 setFocusable(scrollable);
Selim Cinekef406062016-09-29 17:33:13 -07002526 updateForwardAndBackwardScrollability();
2527 }
2528 }
2529
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002530 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekef406062016-09-29 17:33:13 -07002531 private void updateForwardAndBackwardScrollability() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002532 boolean forwardScrollable = mScrollable && !isScrolledToBottom();
2533 boolean backwardsScrollable = mScrollable && !isScrolledToTop();
Selim Cinekef406062016-09-29 17:33:13 -07002534 boolean changed = forwardScrollable != mForwardScrollable
2535 || backwardsScrollable != mBackwardScrollable;
2536 mForwardScrollable = forwardScrollable;
2537 mBackwardScrollable = backwardsScrollable;
2538 if (changed) {
2539 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Selim Cinekc22fff62016-05-20 12:44:30 -07002540 }
Selim Cinek67b22602014-03-10 15:40:16 +01002541 }
2542
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002543 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek6811d722016-01-19 17:53:12 -08002544 private void updateBackground() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08002545 // No need to update the background color if it's not being drawn.
Selim Cinek195dfc52019-05-30 19:35:05 -07002546 if (!mShouldDrawNotificationBackground) {
Selim Cinek6811d722016-01-19 17:53:12 -08002547 return;
2548 }
Anthony Chen3cb3ad92016-12-01 10:58:47 -08002549
Selim Cinek6811d722016-01-19 17:53:12 -08002550 updateBackgroundBounds();
Gus Prevase2d6f042018-10-17 15:25:30 -04002551 if (didSectionBoundsChange()) {
2552 boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
2553 || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
Selim Cinek54680902016-10-19 16:49:44 -07002554 if (!isExpanded()) {
2555 abortBackgroundAnimators();
2556 animate = false;
2557 }
2558 if (animate) {
Selim Cinek614576e2016-01-20 10:54:09 -08002559 startBackgroundAnimation();
2560 } else {
Gus Prevase2d6f042018-10-17 15:25:30 -04002561 for (NotificationSection section : mSections) {
2562 section.resetCurrentBounds();
2563 }
Lucas Dupin90a38dd2018-09-05 09:37:37 -07002564 invalidate();
Selim Cinek614576e2016-01-20 10:54:09 -08002565 }
2566 } else {
Selim Cinek54680902016-10-19 16:49:44 -07002567 abortBackgroundAnimators();
Selim Cinek6811d722016-01-19 17:53:12 -08002568 }
Selim Cinek614576e2016-01-20 10:54:09 -08002569 mAnimateNextBackgroundTop = false;
Gus Prevase2d6f042018-10-17 15:25:30 -04002570 mAnimateNextBackgroundBottom = false;
2571 mAnimateNextSectionBoundsChange = false;
Selim Cinek614576e2016-01-20 10:54:09 -08002572 }
2573
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002574 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek54680902016-10-19 16:49:44 -07002575 private void abortBackgroundAnimators() {
Gus Prevase2d6f042018-10-17 15:25:30 -04002576 for (NotificationSection section : mSections) {
2577 section.cancelAnimators();
Selim Cinek54680902016-10-19 16:49:44 -07002578 }
2579 }
2580
Gus Prevase2d6f042018-10-17 15:25:30 -04002581 private boolean didSectionBoundsChange() {
2582 for (NotificationSection section : mSections) {
2583 if (section.didBoundsChange()) {
2584 return true;
2585 }
2586 }
2587 return false;
2588 }
2589
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002590 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Gus Prevase2d6f042018-10-17 15:25:30 -04002591 private boolean areSectionBoundsAnimating() {
2592 for (NotificationSection section : mSections) {
2593 if (section.areBoundsAnimating()) {
2594 return true;
2595 }
2596 }
2597 return false;
Selim Cinek614576e2016-01-20 10:54:09 -08002598 }
2599
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002600 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek614576e2016-01-20 10:54:09 -08002601 private void startBackgroundAnimation() {
Gus Prevase2d6f042018-10-17 15:25:30 -04002602 // TODO(kprevas): do we still need separate fields for top/bottom?
2603 // or can each section manage its own animation state?
2604 NotificationSection firstVisibleSection = getFirstVisibleSection();
2605 NotificationSection lastVisibleSection = getLastVisibleSection();
2606 for (NotificationSection section : mSections) {
2607 section.startBackgroundAnimation(
2608 section == firstVisibleSection
2609 ? mAnimateNextBackgroundTop
2610 : mAnimateNextSectionBoundsChange,
2611 section == lastVisibleSection
2612 ? mAnimateNextBackgroundBottom
2613 : mAnimateNextSectionBoundsChange);
Selim Cinek614576e2016-01-20 10:54:09 -08002614 }
Selim Cinek6811d722016-01-19 17:53:12 -08002615 }
2616
2617 /**
2618 * Update the background bounds to the new desired bounds
2619 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002620 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek6811d722016-01-19 17:53:12 -08002621 private void updateBackgroundBounds() {
shawnlin27716722019-04-17 20:09:45 +08002622 int left = mSidePaddings;
2623 int right = getWidth() - mSidePaddings;
Gus Prevase2d6f042018-10-17 15:25:30 -04002624 for (NotificationSection section : mSections) {
2625 section.getBounds().left = left;
2626 section.getBounds().right = right;
2627 }
shawnlin3a2a2e22018-05-04 17:09:50 +08002628
Selim Cinek614576e2016-01-20 10:54:09 -08002629 if (!mIsExpanded) {
Gus Prevase2d6f042018-10-17 15:25:30 -04002630 for (NotificationSection section : mSections) {
2631 section.getBounds().top = 0;
2632 section.getBounds().bottom = 0;
2633 }
Selim Cinek1791f502016-10-07 17:38:03 -04002634 return;
Selim Cinek614576e2016-01-20 10:54:09 -08002635 }
Selim Cinek893a9df2019-07-19 17:30:16 -07002636 int minTopPosition;
Gus Prevase2d6f042018-10-17 15:25:30 -04002637 NotificationSection lastSection = getLastVisibleSection();
Selim Cinekc3fec682019-06-06 18:11:07 -07002638 boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
2639 if (!onKeyguard) {
Selim Cinek3fe7e7e2019-02-15 18:40:53 -08002640 minTopPosition = (int) (mTopPadding + mStackTranslation);
2641 } else if (lastSection == null) {
2642 minTopPosition = mTopPadding;
Selim Cinek893a9df2019-07-19 17:30:16 -07002643 } else {
2644 // The first sections could be empty while there could still be elements in later
2645 // sections. The position of these first few sections is determined by the position of
2646 // the first visible section.
2647 NotificationSection firstVisibleSection = getFirstVisibleSection();
2648 firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
2649 false /* shiftPulsingWithFirst */);
2650 minTopPosition = firstVisibleSection.getBounds().top;
Selim Cinek3776fe02016-02-04 13:32:43 -08002651 }
Selim Cinekc3fec682019-06-06 18:11:07 -07002652 boolean shiftPulsingWithFirst = mHeadsUpManager.getAllEntries().count() <= 1
2653 && (mAmbientState.isDozing()
2654 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard));
Selim Cinek3fe7e7e2019-02-15 18:40:53 -08002655 for (NotificationSection section : mSections) {
2656 int minBottomPosition = minTopPosition;
2657 if (section == lastSection) {
2658 // We need to make sure the section goes all the way to the shelf
Selim Cinek3a1d2742019-03-11 18:38:29 -07002659 minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
2660 + mShelf.getIntrinsicHeight());
Gus Prevase83700cb2018-12-14 11:42:51 -05002661 }
Selim Cinekae55d832019-02-22 17:43:43 -08002662 minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
2663 shiftPulsingWithFirst);
2664 shiftPulsingWithFirst = false;
Gus Prevase83700cb2018-12-14 11:42:51 -05002665 }
Selim Cinek614576e2016-01-20 10:54:09 -08002666 }
2667
Gus Prevase2d6f042018-10-17 15:25:30 -04002668 private NotificationSection getFirstVisibleSection() {
2669 for (NotificationSection section : mSections) {
2670 if (section.getFirstVisibleChild() != null) {
2671 return section;
2672 }
2673 }
2674 return null;
2675 }
2676
2677 private NotificationSection getLastVisibleSection() {
2678 for (int i = mSections.length - 1; i >= 0; i--) {
2679 NotificationSection section = mSections[i];
2680 if (section.getLastVisibleChild() != null) {
2681 return section;
2682 }
2683 }
2684 return null;
2685 }
2686
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002687 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek614576e2016-01-20 10:54:09 -08002688 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002689 int childCount = getChildCount();
2690 for (int i = childCount - 1; i >= 0; i--) {
2691 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002692 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2693 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002694 return (ActivatableNotificationView) child;
2695 }
2696 }
2697 return null;
2698 }
2699
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002700 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek614576e2016-01-20 10:54:09 -08002701 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002702 int childCount = getChildCount();
2703 for (int i = 0; i < childCount; i++) {
2704 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002705 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2706 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002707 return (ActivatableNotificationView) child;
2708 }
2709 }
2710 return null;
2711 }
2712
Evan Laird25f02752019-08-14 19:25:06 -04002713 //TODO: We shouldn't have to generate this list every time
2714 private List<ActivatableNotificationView> getChildrenWithBackground() {
2715 ArrayList<ActivatableNotificationView> children = new ArrayList<>();
2716 int childCount = getChildCount();
2717 for (int i = 0; i < childCount; i++) {
2718 View child = getChildAt(i);
2719 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2720 && child != mShelf) {
2721 children.add((ActivatableNotificationView) child);
2722 }
2723 }
2724
2725 return children;
2726 }
2727
Selim Cinek67b22602014-03-10 15:40:16 +01002728 /**
2729 * Fling the scroll view
2730 *
2731 * @param velocityY The initial velocity in the Y direction. Positive
2732 * numbers mean that the finger/cursor is moving down the screen,
2733 * which means we want to scroll towards the top.
2734 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002735 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Muyuan Li26e30ae2016-04-11 17:31:42 -07002736 protected void fling(int velocityY) {
Selim Cinek67b22602014-03-10 15:40:16 +01002737 if (getChildCount() > 0) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002738 float topAmount = getCurrentOverScrollAmount(true);
2739 float bottomAmount = getCurrentOverScrollAmount(false);
2740 if (velocityY < 0 && topAmount > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002741 if (ANCHOR_SCROLLING) {
2742 mScrollAnchorViewY += topAmount;
2743 } else {
2744 setOwnScrollY(mOwnScrollY - (int) topAmount);
2745 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002746 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002747 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002748 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002749 * mOverflingDistance + topAmount;
2750 } else if (velocityY > 0 && bottomAmount > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002751 if (ANCHOR_SCROLLING) {
2752 mScrollAnchorViewY -= bottomAmount;
2753 } else {
2754 setOwnScrollY((int) (mOwnScrollY + bottomAmount));
2755 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002756 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002757 mMaxOverScroll = Math.abs(velocityY) / 1000f
2758 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
Jason Monke59dc402018-08-16 12:05:01 -04002759 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002760 } else {
2761 // it will be set once we reach the boundary
2762 mMaxOverScroll = 0.0f;
2763 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002764 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05002765 flingScroller(velocityY);
Gus Prevas0fa58d62019-01-11 13:58:40 -05002766 } else {
2767 int scrollRange = getScrollRange();
2768 int minScrollY = Math.max(0, scrollRange);
2769 if (mExpandedInThisMotion) {
2770 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2771 }
2772 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
2773 mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek94ab18c2016-02-25 12:35:51 -08002774 }
Selim Cinek67b22602014-03-10 15:40:16 +01002775
Selim Cinek9212de82017-02-06 16:04:28 -08002776 animateScroll();
Selim Cinek67b22602014-03-10 15:40:16 +01002777 }
2778 }
2779
Selim Cinek1408eb52014-06-02 14:45:38 +02002780 /**
Gus Prevascdc98342019-01-14 14:29:44 -05002781 * Flings the overscroller with the given velocity (anchor-based scrolling).
2782 *
2783 * Because anchor-based scrolling can't track the current scroll position, the overscroller is
2784 * always started at startY = 0, and we interpret the positions it computes as relative to the
2785 * start of the scroll.
2786 */
2787 private void flingScroller(int velocityY) {
2788 assert ANCHOR_SCROLLING;
2789 mIsScrollerBoundSet = false;
2790 maybeFlingScroller(velocityY, true /* always fling */);
2791 }
2792
2793 private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
2794 assert ANCHOR_SCROLLING;
2795 // Attempt to determine the maximum amount to scroll before we reach the end.
2796 // If the first view is not materialized (for an upwards scroll) or the last view is either
2797 // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
2798 // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
2799 // the scroller once we approach the start/end of the list.
2800 int minY = Integer.MIN_VALUE;
2801 int maxY = Integer.MAX_VALUE;
2802 if (velocityY < 0) {
2803 minY = getMaxNegativeScrollAmount();
2804 if (minY > Integer.MIN_VALUE) {
2805 mIsScrollerBoundSet = true;
2806 }
2807 } else {
2808 maxY = getMaxPositiveScrollAmount();
2809 if (maxY < Integer.MAX_VALUE) {
2810 mIsScrollerBoundSet = true;
2811 }
2812 }
2813 if (mIsScrollerBoundSet || alwaysFling) {
2814 mLastScrollerY = 0;
2815 // x velocity is set to 1 to avoid overscroller bug
2816 mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
2817 mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
2818 }
2819 }
2820
2821 /**
2822 * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
2823 * before reaching the bottom of the list (discounting overscroll).
2824 *
2825 * If the return value is negative then we have overscrolled; this is a transient state which
2826 * should immediately be handled by adjusting the anchor position and adding the extra space to
2827 * the bottom overscroll amount.
2828 *
2829 * If we don't know how many pixels we have left to scroll (because the last row has not been
2830 * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
2831 * {@link Integer#MAX_VALUE}.
2832 */
2833 private int getMaxPositiveScrollAmount() {
2834 assert ANCHOR_SCROLLING;
2835 // TODO: once we're recycling we need to check the adapter position of the last child.
2836 ExpandableNotificationRow lastRow = getLastRowNotGone();
2837 if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
2838 // distance from bottom of last child to bottom of notifications area is:
2839 // distance from bottom of last child
2840 return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
2841 // to top of anchor view
2842 - mScrollAnchorView.getTranslationY()
2843 // plus distance from anchor view to top of notifications area
2844 + mScrollAnchorViewY
2845 // minus height of notifications area.
2846 - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
2847 } else {
2848 return Integer.MAX_VALUE;
2849 }
2850 }
2851
2852 /**
2853 * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
2854 * direction (upwards) before reaching the top of the list (discounting overscroll).
2855 *
2856 * If the return value is positive then we have overscrolled; this is a transient state which
2857 * should immediately be handled by adjusting the anchor position and adding the extra space to
2858 * the top overscroll amount.
2859 *
2860 * If we don't know how many pixels we have left to scroll (because the first row has not been
2861 * materialized), we return {@link Integer#MIN_VALUE}.
2862 */
2863 private int getMaxNegativeScrollAmount() {
2864 assert ANCHOR_SCROLLING;
2865 // TODO: once we're recycling we need to check the adapter position of the first child.
2866 ExpandableView firstChild = getFirstChildNotGone();
2867 if (mScrollAnchorView != null && firstChild != null) {
2868 // distance from top of first child to top of notifications area is:
2869 // distance from top of anchor view
2870 return (int) -(mScrollAnchorView.getTranslationY()
2871 // to top of first child
2872 - firstChild.getTranslationY()
2873 // minus distance from top of anchor view to top of notifications area.
2874 - mScrollAnchorViewY);
2875 } else {
2876 return Integer.MIN_VALUE;
2877 }
2878 }
2879
2880 /**
2881 * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
2882 * not being materialized or being pinned to the shelf, we need to check on every frame if we're
2883 * able to set the bounds. If we are, we fling the scroller again with the newly computed
2884 * bounds.
2885 */
2886 private void maybeReflingScroller() {
2887 if (!mIsScrollerBoundSet) {
2888 // Because mScroller is a flywheel scroller, we fling with the minimum possible
2889 // velocity to establish direction, so as not to perceptibly affect the velocity.
2890 maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
2891 false /* alwaysFling */);
2892 }
2893 }
2894
2895 /**
Selim Cinek1408eb52014-06-02 14:45:38 +02002896 * @return Whether a fling performed on the top overscroll edge lead to the expanded
2897 * overScroll view (i.e QS).
2898 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002899 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02002900 private boolean shouldOverScrollFling(int initialVelocity) {
2901 float topOverScroll = getCurrentOverScrollAmount(true);
2902 return mScrolledToTopOnFirstDown
2903 && !mExpandedInThisMotion
2904 && topOverScroll > mMinTopOverScrollToEscape
2905 && initialVelocity > 0;
2906 }
2907
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002908 /**
2909 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2910 * account.
2911 *
Jason Monke59dc402018-08-16 12:05:01 -04002912 * @param qsHeight the top padding imposed by the quick settings panel
2913 * @param animate whether to animate the change
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002914 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002915 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekdb6d6df2019-05-22 20:39:59 -07002916 public void updateTopPadding(float qsHeight, boolean animate) {
Selim Cinekbc243a92016-09-27 16:35:13 -07002917 int topPadding = (int) qsHeight;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002918 int minStackHeight = getLayoutMinHeight();
Selim Cinekbc243a92016-09-27 16:35:13 -07002919 if (topPadding + minStackHeight > getHeight()) {
2920 mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
Selim Cinek1408eb52014-06-02 14:45:38 +02002921 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002922 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02002923 }
Selim Cinekb0fada62019-06-17 19:03:59 -07002924 setTopPadding(topPadding, animate && !mKeyguardBypassController.getBypassEnabled());
Selim Cinekbc243a92016-09-27 16:35:13 -07002925 setExpandedHeight(mExpandedHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02002926 }
2927
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002928 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
shawnlin8e4e92c2018-04-12 18:47:24 +08002929 public void setMaxTopPadding(int maxTopPadding) {
2930 mMaxTopPadding = maxTopPadding;
2931 }
2932
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002933 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002934 public int getLayoutMinHeight() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08002935 if (isHeadsUpTransition()) {
2936 return getTopHeadsUpPinnedHeight();
2937 }
Anthony Chen9e05d462017-04-07 10:10:21 -07002938 return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07002939 }
2940
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002941 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002942 public float getTopPaddingOverflow() {
2943 return mTopPaddingOverflow;
2944 }
2945
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002946 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002947 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002948 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek567e8452016-03-24 10:54:56 -07002949 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08002950 : mCollapsedSize;
Selim Cinekdb167372016-11-17 15:41:17 -08002951 int shelfHeight = 0;
Gus Prevase2d6f042018-10-17 15:25:30 -04002952 if (getLastVisibleSection() != null && mShelf.getVisibility() != GONE) {
Selim Cinekdb167372016-11-17 15:41:17 -08002953 shelfHeight = mShelf.getIntrinsicHeight();
2954 }
2955 return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002956 }
2957
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002958 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1408eb52014-06-02 14:45:38 +02002959 private int clampPadding(int desiredPadding) {
2960 return Math.max(desiredPadding, mIntrinsicPadding);
2961 }
2962
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002963 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfed1ab62014-06-17 14:10:33 -07002964 private float getRubberBandFactor(boolean onTop) {
2965 if (!onTop) {
2966 return RUBBER_BAND_FACTOR_NORMAL;
2967 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002968 if (mExpandedInThisMotion) {
2969 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002970 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002971 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2972 } else if (mScrolledToTopOnFirstDown) {
2973 return 1.0f;
2974 }
2975 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02002976 }
2977
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002978 /**
2979 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2980 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2981 * overscroll view (e.g. expand QS).
2982 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002983 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002984 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002985 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002986 || !mScrolledToTopOnFirstDown;
2987 }
2988
Selim Cinek67b22602014-03-10 15:40:16 +01002989
Selim Cinek67b22602014-03-10 15:40:16 +01002990
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002991 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekef5127e2015-12-21 16:55:58 -08002992 public void setChildTransferInProgress(boolean childTransferInProgress) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04002993 Assert.isMainThread();
Selim Cinekef5127e2015-12-21 16:55:58 -08002994 mChildTransferInProgress = childTransferInProgress;
2995 }
2996
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002997 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Christoph Studer068f5922014-04-08 17:43:07 -04002998 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002999 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04003000 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01003001 // we only call our internal methods if this is actually a removal and not just a
3002 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08003003 if (!mChildTransferInProgress) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003004 onViewRemovedInternal((ExpandableView) child, this);
Selim Cinekb5605e52015-02-20 18:21:41 +01003005 }
3006 }
3007
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003008 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003009 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -05003010 public void cleanUpViewStateForEntry(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04003011 View child = entry.getRow();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04003012 if (child == mSwipeHelper.getTranslatingParentView()) {
3013 mSwipeHelper.clearTranslatingParentView();
Mady Mellor4c97b0a2017-02-15 11:16:13 -08003014 }
Mady Mellor4c97b0a2017-02-15 11:16:13 -08003015 }
3016
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003017 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003018 private void onViewRemovedInternal(ExpandableView child, ViewGroup container) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003019 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003020 // This is only a position change, don't do anything special
3021 return;
3022 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003023 child.setOnHeightChangedListener(null);
3024 updateScrollStateForRemovedChild(child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07003025 boolean animationGenerated = generateRemoveAnimation(child);
Selim Cinekd1395642016-04-28 12:22:42 -07003026 if (animationGenerated) {
Rohan Shah524cf7b2018-03-15 14:40:02 -07003027 if (!mSwipedOutViews.contains(child)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003028 || Math.abs(child.getTranslation()) != child.getWidth()) {
Selim Cineka5703182016-05-11 21:23:16 -04003029 container.addTransientView(child, 0);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003030 child.setTransientContainer(container);
Selim Cinekd1395642016-04-28 12:22:42 -07003031 }
3032 } else {
3033 mSwipedOutViews.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003034 }
Selim Cinekcab4a602014-09-03 14:47:57 +02003035 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02003036
Selim Cineke9bad242016-06-15 11:46:37 -07003037 focusNextViewIfFocused(child);
3038 }
3039
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003040 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineke9bad242016-06-15 11:46:37 -07003041 private void focusNextViewIfFocused(View view) {
3042 if (view instanceof ExpandableNotificationRow) {
3043 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
3044 if (row.shouldRefocusOnDismiss()) {
3045 View nextView = row.getChildAfterViewWhenDismissed();
3046 if (nextView == null) {
3047 View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
3048 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
3049 ? groupParentWhenDismissed.getTranslationY()
Selim Cinekef8c2252017-02-10 14:52:18 -08003050 : view.getTranslationY(), true /* ignoreChildren */);
Selim Cineke9bad242016-06-15 11:46:37 -07003051 }
3052 if (nextView != null) {
3053 nextView.requestAccessibilityFocus();
3054 }
3055 }
3056 }
3057
Selim Cinekc27437b2014-05-14 10:23:33 +02003058 }
3059
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003060 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekb5605e52015-02-20 18:21:41 +01003061 private boolean isChildInGroup(View child) {
3062 return child instanceof ExpandableNotificationRow
3063 && mGroupManager.isChildInGroupWithSummary(
Ned Burns1c2b85a42019-11-14 15:37:03 -05003064 ((ExpandableNotificationRow) child).getEntry().getSbn());
Selim Cinekb5605e52015-02-20 18:21:41 +01003065 }
3066
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003067 /**
3068 * Generate a remove animation for a child view.
3069 *
3070 * @param child The view to generate the remove animation for.
3071 * @return Whether an animation was generated.
3072 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003073 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003074 private boolean generateRemoveAnimation(ExpandableView child) {
Selim Cinekf0f74952020-04-21 11:45:16 -07003075 if (!child.wantsAddAndRemoveAnimations()) {
3076 return false;
3077 }
Selim Cineke0890e52015-06-17 11:17:08 -07003078 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07003079 mAddedHeadsUpChildren.remove(child);
3080 return false;
3081 }
Selim Cinek0fccc722015-07-29 17:04:36 -07003082 if (isClickedHeadsUp(child)) {
Selim Cinek9dd0d042018-05-14 18:12:42 -07003083 // An animation is already running, add it transiently
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003084 mClearTransientViewsWhenFinished.add(child);
Selim Cinek0fccc722015-07-29 17:04:36 -07003085 return true;
3086 }
Selim Cinekb5605e52015-02-20 18:21:41 +01003087 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07003088 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02003089 // Generate Animations
3090 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003091 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003092 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02003093 } else {
3094 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003095 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003096 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02003097 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003098 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003099 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02003100 }
3101
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003102 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek0fccc722015-07-29 17:04:36 -07003103 private boolean isClickedHeadsUp(View child) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09003104 return HeadsUpUtil.isClickedHeadsUpNotification(child);
Selim Cinek0fccc722015-07-29 17:04:36 -07003105 }
3106
Selim Cineke0890e52015-06-17 11:17:08 -07003107 /**
3108 * Remove a removed child view from the heads up animations if it was just added there
3109 *
3110 * @return whether any child was removed from the list to animate
3111 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003112 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineke0890e52015-06-17 11:17:08 -07003113 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
3114 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003115 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
3116 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07003117 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003118 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07003119 mTmpList.add(eventPair);
3120 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003121 }
3122 }
Selim Cineke0890e52015-06-17 11:17:08 -07003123 if (hasAddEvent) {
3124 // This child was just added lets remove all events.
3125 mHeadsUpChangeAnimations.removeAll(mTmpList);
Jason Monke59dc402018-08-16 12:05:01 -04003126 ((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
Selim Cineke0890e52015-06-17 11:17:08 -07003127 }
3128 mTmpList.clear();
3129 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07003130 }
3131
Selim Cinek572bbd42014-04-25 16:43:27 +02003132 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01003133 * @param child the child to query
3134 * @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 -04003135 * not expanded
Selim Cinekb5605e52015-02-20 18:21:41 +01003136 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003137 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekb5605e52015-02-20 18:21:41 +01003138 private boolean isChildInInvisibleGroup(View child) {
3139 if (child instanceof ExpandableNotificationRow) {
3140 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Ned Burnsf81c4c42019-01-07 14:10:43 -05003141 NotificationEntry groupSummary =
Ned Burns1c2b85a42019-11-14 15:37:03 -05003142 mGroupManager.getGroupSummary(row.getEntry().getSbn());
Evan Laird94492852018-10-25 13:43:01 -04003143 if (groupSummary != null && groupSummary.getRow() != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07003144 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01003145 }
3146 }
3147 return false;
3148 }
3149
3150 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02003151 * Updates the scroll position when a child was removed
3152 *
3153 * @param removedChild the removed child
3154 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003155 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek61633a82016-01-25 15:54:10 -08003156 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003157 if (ANCHOR_SCROLLING) {
3158 if (removedChild == mScrollAnchorView) {
3159 ExpandableView firstChild = getFirstChildNotGone();
3160 if (firstChild != null) {
3161 mScrollAnchorView = firstChild;
3162 } else {
3163 mScrollAnchorView = mShelf;
3164 }
3165 // Adjust anchor view Y by the distance between the old and new anchors
3166 // so that there's no visible change.
3167 mScrollAnchorViewY +=
3168 mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
3169 }
3170 updateScrollAnchor();
3171 // TODO: once we're recycling this will need to check the adapter position of the child
3172 if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
3173 mScrollAnchorViewY = 0;
3174 }
3175 updateOnScrollChange();
Selim Cineka7ed2c12017-01-23 20:47:24 -08003176 } else {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003177 int startingPosition = getPositionInLinearLayout(removedChild);
3178 float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
3179 int padding;
3180 if (increasedPaddingAmount >= 0) {
3181 padding = (int) NotificationUtils.interpolate(
3182 mPaddingBetweenElements,
3183 mIncreasedPaddingBetweenElements,
3184 increasedPaddingAmount);
3185 } else {
3186 padding = (int) NotificationUtils.interpolate(
3187 0,
3188 mPaddingBetweenElements,
3189 1.0f + increasedPaddingAmount);
3190 }
3191 int childHeight = getIntrinsicHeight(removedChild) + padding;
3192 int endPosition = startingPosition + childHeight;
3193 if (endPosition <= mOwnScrollY) {
3194 // This child is fully scrolled of the top, so we have to deduct its height from the
3195 // scrollPosition
3196 setOwnScrollY(mOwnScrollY - childHeight);
3197 } else if (startingPosition < mOwnScrollY) {
3198 // This child is currently being scrolled into, set the scroll position to the
3199 // start of this child
3200 setOwnScrollY(startingPosition);
3201 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003202 }
3203 }
3204
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003205 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd7c4e002014-07-04 18:36:42 +02003206 private int getIntrinsicHeight(View view) {
3207 if (view instanceof ExpandableView) {
3208 ExpandableView expandableView = (ExpandableView) view;
3209 return expandableView.getIntrinsicHeight();
3210 }
3211 return view.getHeight();
3212 }
3213
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003214 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1f624952017-06-08 19:11:50 -07003215 public int getPositionInLinearLayout(View requestedView) {
Adrian Roos4a579672016-05-24 16:54:37 -07003216 ExpandableNotificationRow childInGroup = null;
3217 ExpandableNotificationRow requestedRow = null;
3218 if (isChildInGroup(requestedView)) {
3219 // We're asking for a child in a group. Calculate the position of the parent first,
3220 // then within the parent.
3221 childInGroup = (ExpandableNotificationRow) requestedView;
3222 requestedView = requestedRow = childInGroup.getNotificationParent();
3223 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003224 int position = 0;
Selim Cineka7ed2c12017-01-23 20:47:24 -08003225 float previousPaddingRequest = mPaddingBetweenElements;
3226 float previousPaddingAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02003227 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08003228 ExpandableView child = (ExpandableView) getChildAt(i);
3229 boolean notGone = child.getVisibility() != View.GONE;
Selim Cinek281c2022016-10-13 19:14:43 -07003230 if (notGone && !child.hasNoContentHeight()) {
Selim Cinek42357e02016-02-24 18:48:01 -08003231 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cineka7ed2c12017-01-23 20:47:24 -08003232 float padding;
3233 if (increasedPaddingAmount >= 0.0f) {
3234 padding = (int) NotificationUtils.interpolate(
3235 previousPaddingRequest,
3236 mIncreasedPaddingBetweenElements,
3237 increasedPaddingAmount);
3238 previousPaddingRequest = (int) NotificationUtils.interpolate(
Selim Cinek42357e02016-02-24 18:48:01 -08003239 mPaddingBetweenElements,
3240 mIncreasedPaddingBetweenElements,
Selim Cineka7ed2c12017-01-23 20:47:24 -08003241 increasedPaddingAmount);
3242 } else {
3243 int ownPadding = (int) NotificationUtils.interpolate(
3244 0,
3245 mPaddingBetweenElements,
3246 1.0f + increasedPaddingAmount);
3247 if (previousPaddingAmount > 0.0f) {
3248 padding = (int) NotificationUtils.interpolate(
3249 ownPadding,
3250 mIncreasedPaddingBetweenElements,
3251 previousPaddingAmount);
3252 } else {
3253 padding = ownPadding;
3254 }
3255 previousPaddingRequest = ownPadding;
Selim Cinek61633a82016-01-25 15:54:10 -08003256 }
Selim Cineka7ed2c12017-01-23 20:47:24 -08003257 if (position != 0) {
3258 position += padding;
3259 }
3260 previousPaddingAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08003261 }
Adrian Roos4a579672016-05-24 16:54:37 -07003262 if (child == requestedView) {
3263 if (requestedRow != null) {
3264 position += requestedRow.getPositionOfChild(childInGroup);
3265 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003266 return position;
3267 }
Selim Cinek61633a82016-01-25 15:54:10 -08003268 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02003269 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02003270 }
3271 }
3272 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02003273 }
3274
3275 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003276 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adam Powell6690d012015-06-17 16:41:56 -07003277 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02003278 super.onViewAdded(child);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003279 onViewAddedInternal((ExpandableView) child);
Selim Cinekb5605e52015-02-20 18:21:41 +01003280 }
3281
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003282 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek614576e2016-01-20 10:54:09 -08003283 private void updateFirstAndLastBackgroundViews() {
Gus Prevase2d6f042018-10-17 15:25:30 -04003284 NotificationSection firstSection = getFirstVisibleSection();
3285 NotificationSection lastSection = getLastVisibleSection();
Gus Prevasda13cfa2018-11-20 14:58:48 -05003286 ActivatableNotificationView previousFirstChild =
3287 firstSection == null ? null : firstSection.getFirstVisibleChild();
3288 ActivatableNotificationView previousLastChild =
3289 lastSection == null ? null : lastSection.getLastVisibleChild();
Gus Prevase2d6f042018-10-17 15:25:30 -04003290
Selim Cinek614576e2016-01-20 10:54:09 -08003291 ActivatableNotificationView firstChild = getFirstChildWithBackground();
3292 ActivatableNotificationView lastChild = getLastChildWithBackground();
Evan Laird25f02752019-08-14 19:25:06 -04003293 boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections(
3294 mSections, getChildrenWithBackground());
Gus Prevase2d6f042018-10-17 15:25:30 -04003295
Selim Cinek614576e2016-01-20 10:54:09 -08003296 if (mAnimationsEnabled && mIsExpanded) {
Gus Prevasda13cfa2018-11-20 14:58:48 -05003297 mAnimateNextBackgroundTop = firstChild != previousFirstChild;
Selim Cinek6f0a62a2019-04-09 18:40:12 -07003298 mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
Gus Prevase2d6f042018-10-17 15:25:30 -04003299 mAnimateNextSectionBoundsChange = sectionViewsChanged;
Selim Cinek614576e2016-01-20 10:54:09 -08003300 } else {
3301 mAnimateNextBackgroundTop = false;
3302 mAnimateNextBackgroundBottom = false;
Gus Prevase2d6f042018-10-17 15:25:30 -04003303 mAnimateNextSectionBoundsChange = false;
Selim Cinek614576e2016-01-20 10:54:09 -08003304 }
Selim Cinekdb167372016-11-17 15:41:17 -08003305 mAmbientState.setLastVisibleBackgroundChild(lastChild);
Gus Prevase2d6f042018-10-17 15:25:30 -04003306 mRoundnessManager.updateRoundedChildren(mSections);
Selim Cinek6f0a62a2019-04-09 18:40:12 -07003307 mAnimateBottomOnLayout = false;
Selim Cinek515b2032017-11-15 10:20:19 -08003308 invalidate();
Selim Cinek614576e2016-01-20 10:54:09 -08003309 }
3310
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003311 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003312 private void onViewAddedInternal(ExpandableView child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07003313 updateHideSensitiveForChild(child);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003314 child.setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00003315 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02003316 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02003317 updateChronometerForChild(child);
Gus Prevasa18dc572019-01-14 16:11:22 -05003318 if (child instanceof ExpandableNotificationRow) {
3319 ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
3320 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05003321 if (ANCHOR_SCROLLING) {
3322 // TODO: once we're recycling this will need to check the adapter position of the child
3323 if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
3324 // New child was added at the top while we're scrolled to the top;
3325 // make it the new anchor view so that we stay at the top.
3326 mScrollAnchorView = child;
3327 }
3328 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003329 }
3330
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003331 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003332 private void updateHideSensitiveForChild(ExpandableView child) {
3333 child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
Selim Cinekd06c41c2015-07-06 14:51:36 -07003334 }
3335
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003336 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003337 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003338 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
Selim Cinekd1395642016-04-28 12:22:42 -07003339 onViewRemovedInternal(row, childrenContainer);
Selim Cinekb5605e52015-02-20 18:21:41 +01003340 }
3341
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003342 @Override
Evan Laird9a9a6192019-12-19 10:44:21 -05003343 public void notifyGroupChildRemoved(View child, ViewGroup parent) {
3344 notifyGroupChildRemoved((ExpandableView) child, parent);
3345 }
3346
3347 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003348 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003349 public void notifyGroupChildAdded(ExpandableView row) {
Selim Cinekb5605e52015-02-20 18:21:41 +01003350 onViewAddedInternal(row);
3351 }
3352
Evan Laird9a9a6192019-12-19 10:44:21 -05003353 @Override
3354 public void notifyGroupChildAdded(View view) {
3355 notifyGroupChildAdded((ExpandableView) view);
3356 }
3357
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003358 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi75c95042014-05-16 19:09:59 +02003359 public void setAnimationsEnabled(boolean animationsEnabled) {
3360 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02003361 updateNotificationAnimationStates();
Rohan Shah8ee53652018-04-05 11:13:50 -07003362 if (!animationsEnabled) {
3363 mSwipedOutViews.clear();
3364 mChildrenToRemoveAnimated.clear();
3365 clearTemporaryViewsInGroup(this);
3366 }
Selim Cinekcab4a602014-09-03 14:47:57 +02003367 }
3368
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003369 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcab4a602014-09-03 14:47:57 +02003370 private void updateNotificationAnimationStates() {
Selim Cinekbe2c4432017-05-30 12:11:09 -07003371 boolean running = mAnimationsEnabled || hasPulsingNotifications();
Selim Cinek09bd29d2017-02-03 15:30:28 -08003372 mShelf.setAnimationsEnabled(running);
Selim Cinekcab4a602014-09-03 14:47:57 +02003373 int childCount = getChildCount();
3374 for (int i = 0; i < childCount; i++) {
3375 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07003376 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02003377 updateAnimationState(running, child);
3378 }
3379 }
3380
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003381 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek51ae05d2014-09-09 15:51:38 +02003382 private void updateAnimationState(View child) {
Selim Cinekbe2c4432017-05-30 12:11:09 -07003383 updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003384 && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02003385 }
3386
Selim Cinek2627d722018-01-19 12:16:49 -08003387 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003388 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek2627d722018-01-19 12:16:49 -08003389 public void setExpandingNotification(ExpandableNotificationRow row) {
3390 mAmbientState.setExpandingNotification(row);
3391 requestChildrenUpdate();
3392 }
3393
3394 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003395 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek8875de12018-03-22 10:14:32 -07003396 public void bindRow(ExpandableNotificationRow row) {
Selim Cinekf0c79e12018-05-14 17:17:31 -07003397 row.setHeadsUpAnimatingAwayListener(animatingAway -> {
3398 mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway);
3399 mHeadsUpAppearanceController.updateHeader(row.getEntry());
3400 });
Selim Cinek8875de12018-03-22 10:14:32 -07003401 }
3402
3403 @Override
Selim Cinekfdf80332019-03-07 17:29:55 -08003404 public boolean containsView(View v) {
3405 return v.getParent() == this;
3406 }
3407
3408 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003409 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek2627d722018-01-19 12:16:49 -08003410 public void applyExpandAnimationParams(ExpandAnimationParameters params) {
3411 mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
3412 requestChildrenUpdate();
3413 }
Selim Cinek51ae05d2014-09-09 15:51:38 +02003414
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003415 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcab4a602014-09-03 14:47:57 +02003416 private void updateAnimationState(boolean running, View child) {
3417 if (child instanceof ExpandableNotificationRow) {
3418 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3419 row.setIconAnimationRunning(running);
3420 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02003421 }
3422
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003423 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi75c95042014-05-16 19:09:59 +02003424 public boolean isAddOrRemoveAnimationPending() {
3425 return mNeedsAnimation
3426 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
3427 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003428
3429 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003430 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003431 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
Selim Cinekf0f74952020-04-21 11:45:16 -07003432 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress && !isFullyHidden()
3433 && child.wantsAddAndRemoveAnimations()) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003434 // Generate Animations
3435 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00003436 if (fromMoreCard) {
3437 mFromMoreCardAdditions.add(child);
3438 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003439 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02003440 }
Selim Cinek820ba2d2019-06-18 18:59:09 -07003441 if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress
3442 && !isFullyHidden()) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003443 mAddedHeadsUpChildren.add(child);
3444 mChildrenToAddAnimated.remove(child);
3445 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003446 }
3447
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003448 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003449 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003450 public void changeViewPosition(ExpandableView child, int newIndex) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04003451 Assert.isMainThread();
3452 if (mChangePositionInProgress) {
3453 throw new IllegalStateException("Reentrant call to changeViewPosition");
3454 }
3455
Dan Sandlereceda3d2014-07-21 15:35:01 -04003456 int currentIndex = indexOfChild(child);
Rohan Shah8ee53652018-04-05 11:13:50 -07003457
3458 if (currentIndex == -1) {
3459 boolean isTransient = false;
3460 if (child instanceof ExpandableNotificationRow
Evan Laird04373662020-01-24 17:37:39 -05003461 && child.getTransientContainer() != null) {
Rohan Shah8ee53652018-04-05 11:13:50 -07003462 isTransient = true;
3463 }
3464 Log.e(TAG, "Attempting to re-position "
3465 + (isTransient ? "transient" : "")
3466 + " view {"
3467 + child
3468 + "}");
3469 return;
3470 }
3471
Dan Sandlereceda3d2014-07-21 15:35:01 -04003472 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003473 mChangePositionInProgress = true;
Evan Laird04373662020-01-24 17:37:39 -05003474 child.setChangingPosition(true);
Selim Cinekc27437b2014-05-14 10:23:33 +02003475 removeView(child);
3476 addView(child, newIndex);
Evan Laird04373662020-01-24 17:37:39 -05003477 child.setChangingPosition(false);
Selim Cinek159ffdb2014-06-04 22:24:18 +02003478 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04003479 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003480 mChildrenChangingPositions.add(child);
3481 mNeedsAnimation = true;
3482 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003483 }
3484 }
3485
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003486 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekf4c19962014-05-01 21:55:31 +02003487 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003488 if (mNeedsAnimation) {
Rohan Shahb9d500a2018-06-25 16:27:16 -07003489 generateAllAnimationEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003490 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02003491 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003492 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Selim Cinekea66ca02016-05-24 13:33:47 -07003493 setAnimationRunning(true);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003494 mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003495 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08003496 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08003497 updateViewShadows();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08003498 updateClippingToTopRoundedCorner();
Selim Cinekf4c19962014-05-01 21:55:31 +02003499 } else {
3500 applyCurrentState();
3501 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003502 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02003503 }
3504
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003505 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Rohan Shahb9d500a2018-06-25 16:27:16 -07003506 private void generateAllAnimationEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07003507 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02003508 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003509 generateChildAdditionEvents();
3510 generatePositionChangeEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003511 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003512 generateActivateEvent();
3513 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02003514 generateHideSensitiveEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003515 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02003516 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01003517 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02003518 generateAnimateEverythingEvent();
Selim Cinek572bbd42014-04-25 16:43:27 +02003519 }
3520
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003521 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003522 private void generateHeadsUpAnimationEvents() {
3523 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003524 ExpandableNotificationRow row = eventPair.first;
3525 boolean isHeadsUp = eventPair.second;
Selim Cineke3c6e462019-06-24 19:37:06 -07003526 if (isHeadsUp != row.isHeadsUp()) {
3527 // For cases where we have a heads up showing and appearing again we shouldn't
3528 // do the animations at all.
3529 continue;
3530 }
Selim Cineka59ecc32015-04-07 10:51:49 -07003531 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
3532 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07003533 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cineke3c6e462019-06-24 19:37:06 -07003534 boolean performDisappearAnimation = !mIsExpanded
3535 // Only animate if we still have pinned heads up, otherwise we just have the
3536 // regular collapse animation of the lock screen
3537 || (mKeyguardBypassController.getBypassEnabled() && onKeyguard()
3538 && mHeadsUpManager.hasPinnedHeadsUp());
3539 if (performDisappearAnimation && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003540 type = row.wasJustClicked()
3541 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3542 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinek76e813c2016-07-14 11:16:58 -07003543 if (row.isChildInGroup()) {
3544 // We can otherwise get stuck in there if it was just isolated
Selim Cinekcafa87f2016-10-26 17:00:17 -07003545 row.setHeadsUpAnimatingAway(false);
Selim Cinekf93bf3e2018-05-08 14:43:21 -07003546 continue;
Selim Cinek76e813c2016-07-14 11:16:58 -07003547 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04003548 } else {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003549 ExpandableViewState viewState = row.getViewState();
Selim Cinekeaee9c02015-06-25 11:04:20 -04003550 if (viewState == null) {
3551 // A view state was never generated for this view, so we don't need to animate
3552 // this. This may happen with notification children.
3553 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07003554 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04003555 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
3556 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
3557 // Our custom add animation
3558 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
3559 } else {
3560 // Normal add animation
3561 type = AnimationEvent.ANIMATION_TYPE_ADD;
3562 }
3563 onBottom = !pinnedAndClosed;
3564 }
Selim Cineka59ecc32015-04-07 10:51:49 -07003565 }
3566 AnimationEvent event = new AnimationEvent(row, type);
3567 event.headsUpFromBottom = onBottom;
3568 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003569 }
3570 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07003571 mAddedHeadsUpChildren.clear();
3572 }
3573
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003574 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekbbcebde2016-11-09 18:28:20 -08003575 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003576 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
3577 return false;
3578 }
3579 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003580 }
3581
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003582 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb5605e52015-02-20 18:21:41 +01003583 private void generateGroupExpansionEvent() {
3584 // Generate a group expansion/collapsing event if there is such a group at all
3585 if (mExpandedGroupView != null) {
3586 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
3587 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
3588 mExpandedGroupView = null;
3589 }
3590 }
3591
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003592 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineka5e211b2014-08-11 17:35:48 +02003593 private void generateViewResizeEvent() {
3594 if (mNeedViewResizeAnimation) {
felkachangd7835b02018-07-17 18:18:13 +08003595 boolean hasDisappearAnimation = false;
3596 for (AnimationEvent animationEvent : mAnimationEvents) {
3597 final int type = animationEvent.animationType;
3598 if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
Jason Monke59dc402018-08-16 12:05:01 -04003599 || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
felkachangd7835b02018-07-17 18:18:13 +08003600 hasDisappearAnimation = true;
3601 break;
3602 }
3603 }
3604
3605 if (!hasDisappearAnimation) {
3606 mAnimationEvents.add(
3607 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
3608 }
Selim Cineka5e211b2014-08-11 17:35:48 +02003609 }
3610 mNeedViewResizeAnimation = false;
3611 }
3612
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003613 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02003614 private void generateChildRemovalEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003615 for (ExpandableView child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003616 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003617
3618 // we need to know the view after this one
Selim Cinekef8c2252017-02-10 14:52:18 -08003619 float removedTranslation = child.getTranslationY();
3620 boolean ignoreChildren = true;
3621 if (child instanceof ExpandableNotificationRow) {
3622 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3623 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
3624 removedTranslation = row.getTranslationWhenRemoved();
3625 ignoreChildren = false;
3626 }
Selim Cinek51052042017-07-04 12:07:55 +02003627 childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth();
Selim Cinekef8c2252017-02-10 14:52:18 -08003628 }
Selim Cinek51052042017-07-04 12:07:55 +02003629 if (!childWasSwipedOut) {
3630 Rect clipBounds = child.getClipBounds();
Selim Cineke5832ee2017-11-06 14:46:48 -08003631 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
Rohan Shaha7594962018-05-22 10:59:30 -07003632
3633 if (childWasSwipedOut && child instanceof ExpandableView) {
3634 // Clean up any potential transient views if the child has already been swiped
3635 // out, as we won't be animating it further (due to its height already being
3636 // clipped to 0.
3637 ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer();
3638 if (transientContainer != null) {
3639 transientContainer.removeTransientView(child);
3640 }
3641 }
Selim Cinek51052042017-07-04 12:07:55 +02003642 }
3643 int animationType = childWasSwipedOut
3644 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
3645 : AnimationEvent.ANIMATION_TYPE_REMOVE;
3646 AnimationEvent event = new AnimationEvent(child, animationType);
Selim Cinekef8c2252017-02-10 14:52:18 -08003647 event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation,
3648 ignoreChildren);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003649 mAnimationEvents.add(event);
Selim Cinekd1395642016-04-28 12:22:42 -07003650 mSwipedOutViews.remove(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02003651 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003652 mChildrenToRemoveAnimated.clear();
3653 }
3654
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003655 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003656 private void generatePositionChangeEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003657 for (ExpandableView child : mChildrenChangingPositions) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003658 mAnimationEvents.add(new AnimationEvent(child,
3659 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3660 }
3661 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01003662 if (mGenerateChildOrderChangedEvent) {
3663 mAnimationEvents.add(new AnimationEvent(null,
3664 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3665 mGenerateChildOrderChangedEvent = false;
3666 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003667 }
3668
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003669 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02003670 private void generateChildAdditionEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003671 for (ExpandableView child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003672 if (mFromMoreCardAdditions.contains(child)) {
3673 mAnimationEvents.add(new AnimationEvent(child,
3674 AnimationEvent.ANIMATION_TYPE_ADD,
3675 StackStateAnimator.ANIMATION_DURATION_STANDARD));
3676 } else {
3677 mAnimationEvents.add(new AnimationEvent(child,
3678 AnimationEvent.ANIMATION_TYPE_ADD));
3679 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003680 }
3681 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003682 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04003683 }
3684
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003685 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003686 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02003687 if (mTopPaddingNeedsAnimation) {
Lucas Dupinc445dcc2018-05-07 16:00:11 -07003688 AnimationEvent event;
Selim Cinekc1d9ab22019-05-21 18:08:30 -07003689 if (mAmbientState.isDozing()) {
Lucas Dupinc445dcc2018-05-07 16:00:11 -07003690 event = new AnimationEvent(null /* view */,
3691 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED,
3692 KeyguardSliceView.DEFAULT_ANIM_DURATION);
3693 } else {
3694 event = new AnimationEvent(null /* view */,
3695 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED);
3696 }
3697 mAnimationEvents.add(event);
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02003698 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003699 mTopPaddingNeedsAnimation = false;
3700 }
3701
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003702 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003703 private void generateActivateEvent() {
3704 if (mActivateNeedsAnimation) {
3705 mAnimationEvents.add(
3706 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
3707 }
3708 mActivateNeedsAnimation = false;
3709 }
3710
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003711 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekd9acca52014-09-01 22:33:25 +02003712 private void generateAnimateEverythingEvent() {
3713 if (mEverythingNeedsAnimation) {
3714 mAnimationEvents.add(
3715 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
3716 }
3717 mEverythingNeedsAnimation = false;
3718 }
3719
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003720 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003721 private void generateDimmedEvent() {
3722 if (mDimmedNeedsAnimation) {
3723 mAnimationEvents.add(
3724 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
3725 }
3726 mDimmedNeedsAnimation = false;
3727 }
3728
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003729 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggiae441282014-08-01 02:45:18 +02003730 private void generateHideSensitiveEvent() {
3731 if (mHideSensitiveNeedsAnimation) {
3732 mAnimationEvents.add(
3733 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
3734 }
3735 mHideSensitiveNeedsAnimation = false;
3736 }
3737
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003738 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003739 private void generateGoToFullShadeEvent() {
3740 if (mGoToFullShadeNeedsAnimation) {
3741 mAnimationEvents.add(
3742 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
3743 }
3744 mGoToFullShadeNeedsAnimation = false;
3745 }
3746
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003747 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
3748 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003749 return new StackScrollAlgorithm(context, this);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003750 }
3751
3752 /**
3753 * @return Whether a y coordinate is inside the content.
3754 */
3755 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3756 public boolean isInContentBounds(float y) {
3757 return y < getHeight() - getEmptyBottomMargin();
3758 }
3759
3760 @ShadeViewRefactor(RefactorComponent.INPUT)
3761 public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
3762 mLongPressListener = listener;
3763 }
3764
Philip Quinn47169132020-03-23 19:04:55 -07003765 private float getTouchSlop(MotionEvent event) {
3766 // Adjust the touch slop if another gesture may be being performed.
3767 return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
3768 ? mTouchSlop * mSlopMultiplier
3769 : mTouchSlop;
3770 }
3771
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003772 @Override
3773 @ShadeViewRefactor(RefactorComponent.INPUT)
3774 public boolean onTouchEvent(MotionEvent ev) {
Philip Quinn97f2e742020-03-23 19:07:09 -07003775 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003776 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
3777 || ev.getActionMasked() == MotionEvent.ACTION_UP;
3778 handleEmptySpaceClick(ev);
3779 boolean expandWantsIt = false;
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003780 boolean swipingInProgress = mSwipingInProgress;
Philip Quinn97f2e742020-03-23 19:07:09 -07003781 if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003782 if (isCancelOrUp) {
3783 mExpandHelper.onlyObserveMovements(false);
3784 }
3785 boolean wasExpandingBefore = mExpandingNotification;
3786 expandWantsIt = mExpandHelper.onTouchEvent(ev);
3787 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
3788 && !mDisallowScrollingInThisMotion) {
3789 dispatchDownEventToScroller(ev);
3790 }
3791 }
3792 boolean scrollerWantsIt = false;
3793 if (mIsExpanded && !swipingInProgress && !mExpandingNotification
3794 && !mDisallowScrollingInThisMotion) {
3795 scrollerWantsIt = onScrollTouch(ev);
3796 }
3797 boolean horizontalSwipeWantsIt = false;
3798 if (!mIsBeingDragged
3799 && !mExpandingNotification
3800 && !mExpandedInThisMotion
3801 && !mOnlyScrollingInThisMotion
3802 && !mDisallowDismissInThisMotion) {
3803 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
3804 }
3805
3806 // Check if we need to clear any snooze leavebehinds
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003807 if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
3808 && guts.getGutsContent() instanceof NotificationSnooze) {
3809 NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
3810 if ((ns.isExpanded() && isCancelOrUp)
3811 || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
3812 // If the leavebehind is expanded we clear it on the next up event, otherwise we
3813 // clear it on the next non-horizontal swipe or expand event.
3814 checkSnoozeLeavebehind();
3815 }
3816 }
3817 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
3818 mCheckForLeavebehind = true;
3819 }
3820 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
3821 }
3822
3823 @ShadeViewRefactor(RefactorComponent.INPUT)
3824 private void dispatchDownEventToScroller(MotionEvent ev) {
3825 MotionEvent downEvent = MotionEvent.obtain(ev);
3826 downEvent.setAction(MotionEvent.ACTION_DOWN);
3827 onScrollTouch(downEvent);
3828 downEvent.recycle();
3829 }
3830
3831 @Override
3832 @ShadeViewRefactor(RefactorComponent.INPUT)
3833 public boolean onGenericMotionEvent(MotionEvent event) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003834 if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003835 || mDisallowScrollingInThisMotion) {
3836 return false;
3837 }
3838 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3839 switch (event.getAction()) {
3840 case MotionEvent.ACTION_SCROLL: {
3841 if (!mIsBeingDragged) {
3842 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3843 if (vscroll != 0) {
3844 final int delta = (int) (vscroll * getVerticalScrollFactor());
Gus Prevas0fa58d62019-01-11 13:58:40 -05003845 if (ANCHOR_SCROLLING) {
3846 mScrollAnchorViewY -= delta;
3847 updateScrollAnchor();
3848 clampScrollPosition();
3849 updateOnScrollChange();
3850 } else {
3851 final int range = getScrollRange();
3852 int oldScrollY = mOwnScrollY;
3853 int newScrollY = oldScrollY - delta;
3854 if (newScrollY < 0) {
3855 newScrollY = 0;
3856 } else if (newScrollY > range) {
3857 newScrollY = range;
3858 }
3859 if (newScrollY != oldScrollY) {
3860 setOwnScrollY(newScrollY);
3861 return true;
3862 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003863 }
3864 }
3865 }
3866 }
3867 }
3868 }
3869 return super.onGenericMotionEvent(event);
3870 }
3871
3872 @ShadeViewRefactor(RefactorComponent.INPUT)
3873 private boolean onScrollTouch(MotionEvent ev) {
3874 if (!isScrollingEnabled()) {
3875 return false;
3876 }
3877 if (isInsideQsContainer(ev) && !mIsBeingDragged) {
3878 return false;
3879 }
3880 mForcedScroll = null;
3881 initVelocityTrackerIfNotExists();
3882 mVelocityTracker.addMovement(ev);
3883
Ned Burnsc8048da2020-04-02 17:22:40 -04003884 final int action = ev.getActionMasked();
3885 if (ev.findPointerIndex(mActivePointerId) == -1 && action != MotionEvent.ACTION_DOWN) {
3886 // Incomplete gesture, possibly due to window swap mid-gesture. Ignore until a new
3887 // one starts.
3888 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent "
3889 + MotionEvent.actionToString(ev.getActionMasked()));
3890 return true;
3891 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003892
Ned Burnsc8048da2020-04-02 17:22:40 -04003893 switch (action) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003894 case MotionEvent.ACTION_DOWN: {
3895 if (getChildCount() == 0 || !isInContentBounds(ev)) {
3896 return false;
3897 }
3898 boolean isBeingDragged = !mScroller.isFinished();
3899 setIsBeingDragged(isBeingDragged);
3900 /*
3901 * If being flinged and user touches, stop the fling. isFinished
3902 * will be false if being flinged.
3903 */
3904 if (!mScroller.isFinished()) {
3905 mScroller.forceFinished(true);
3906 }
3907
3908 // Remember where the motion event started
3909 mLastMotionY = (int) ev.getY();
3910 mDownX = (int) ev.getX();
3911 mActivePointerId = ev.getPointerId(0);
3912 break;
3913 }
3914 case MotionEvent.ACTION_MOVE:
3915 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
3916 if (activePointerIndex == -1) {
3917 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
3918 break;
3919 }
3920
3921 final int y = (int) ev.getY(activePointerIndex);
3922 final int x = (int) ev.getX(activePointerIndex);
3923 int deltaY = mLastMotionY - y;
3924 final int xDiff = Math.abs(x - mDownX);
3925 final int yDiff = Math.abs(deltaY);
Philip Quinn47169132020-03-23 19:04:55 -07003926 final float touchSlop = getTouchSlop(ev);
3927 if (!mIsBeingDragged && yDiff > touchSlop && yDiff > xDiff) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003928 setIsBeingDragged(true);
3929 if (deltaY > 0) {
Philip Quinn47169132020-03-23 19:04:55 -07003930 deltaY -= touchSlop;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003931 } else {
Philip Quinn47169132020-03-23 19:04:55 -07003932 deltaY += touchSlop;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003933 }
3934 }
3935 if (mIsBeingDragged) {
3936 // Scroll to follow the motion event
3937 mLastMotionY = y;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003938 float scrollAmount;
Gus Prevas0fa58d62019-01-11 13:58:40 -05003939 int range;
3940 if (ANCHOR_SCROLLING) {
3941 range = 0; // unused in the methods it's being passed to
3942 } else {
3943 range = getScrollRange();
3944 if (mExpandedInThisMotion) {
3945 range = Math.min(range, mMaxScrollAfterExpand);
3946 }
3947 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003948 if (deltaY < 0) {
3949 scrollAmount = overScrollDown(deltaY);
3950 } else {
3951 scrollAmount = overScrollUp(deltaY, range);
3952 }
3953
3954 // Calling customOverScrollBy will call onCustomOverScrolled, which
3955 // sets the scrolling if applicable.
3956 if (scrollAmount != 0.0f) {
3957 // The scrolling motion could not be compensated with the
3958 // existing overScroll, we have to scroll the view
3959 customOverScrollBy((int) scrollAmount, mOwnScrollY,
3960 range, getHeight() / 2);
3961 // If we're scrolling, leavebehinds should be dismissed
3962 checkSnoozeLeavebehind();
3963 }
3964 }
3965 break;
3966 case MotionEvent.ACTION_UP:
3967 if (mIsBeingDragged) {
3968 final VelocityTracker velocityTracker = mVelocityTracker;
3969 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3970 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3971
3972 if (shouldOverScrollFling(initialVelocity)) {
3973 onOverScrollFling(true, initialVelocity);
3974 } else {
3975 if (getChildCount() > 0) {
3976 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
3977 float currentOverScrollTop = getCurrentOverScrollAmount(true);
3978 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
3979 fling(-initialVelocity);
3980 } else {
3981 onOverScrollFling(false, initialVelocity);
3982 }
3983 } else {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003984 if (ANCHOR_SCROLLING) {
3985 // TODO
3986 } else {
3987 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
3988 getScrollRange())) {
3989 animateScroll();
3990 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003991 }
3992 }
3993 }
3994 }
3995 mActivePointerId = INVALID_POINTER;
3996 endDrag();
3997 }
3998
3999 break;
4000 case MotionEvent.ACTION_CANCEL:
4001 if (mIsBeingDragged && getChildCount() > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004002 if (ANCHOR_SCROLLING) {
4003 // TODO
4004 } else {
4005 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
4006 getScrollRange())) {
4007 animateScroll();
4008 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004009 }
4010 mActivePointerId = INVALID_POINTER;
4011 endDrag();
4012 }
4013 break;
4014 case MotionEvent.ACTION_POINTER_DOWN: {
4015 final int index = ev.getActionIndex();
4016 mLastMotionY = (int) ev.getY(index);
4017 mDownX = (int) ev.getX(index);
4018 mActivePointerId = ev.getPointerId(index);
4019 break;
4020 }
4021 case MotionEvent.ACTION_POINTER_UP:
4022 onSecondaryPointerUp(ev);
4023 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
4024 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
4025 break;
4026 }
4027 return true;
4028 }
4029
4030 @ShadeViewRefactor(RefactorComponent.INPUT)
4031 protected boolean isInsideQsContainer(MotionEvent ev) {
4032 return ev.getY() < mQsContainer.getBottom();
4033 }
4034
4035 @ShadeViewRefactor(RefactorComponent.INPUT)
4036 private void onOverScrollFling(boolean open, int initialVelocity) {
4037 if (mOverscrollTopChangedListener != null) {
4038 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
4039 }
4040 mDontReportNextOverScroll = true;
4041 setOverScrollAmount(0.0f, true, false);
4042 }
4043
4044
4045 @ShadeViewRefactor(RefactorComponent.INPUT)
4046 private void onSecondaryPointerUp(MotionEvent ev) {
4047 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
4048 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
4049 final int pointerId = ev.getPointerId(pointerIndex);
4050 if (pointerId == mActivePointerId) {
4051 // This was our active pointer going up. Choose a new
4052 // active pointer and adjust accordingly.
4053 // TODO: Make this decision more intelligent.
4054 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
4055 mLastMotionY = (int) ev.getY(newPointerIndex);
4056 mActivePointerId = ev.getPointerId(newPointerIndex);
4057 if (mVelocityTracker != null) {
4058 mVelocityTracker.clear();
4059 }
4060 }
4061 }
4062
4063 @ShadeViewRefactor(RefactorComponent.INPUT)
4064 private void endDrag() {
4065 setIsBeingDragged(false);
4066
4067 recycleVelocityTracker();
4068
4069 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
4070 setOverScrollAmount(0, true /* onTop */, true /* animate */);
4071 }
4072 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
4073 setOverScrollAmount(0, false /* onTop */, true /* animate */);
4074 }
4075 }
4076
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004077 @Override
4078 @ShadeViewRefactor(RefactorComponent.INPUT)
4079 public boolean onInterceptTouchEvent(MotionEvent ev) {
4080 initDownStates(ev);
4081 handleEmptySpaceClick(ev);
Philip Quinn97f2e742020-03-23 19:07:09 -07004082
4083 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004084 boolean expandWantsIt = false;
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05004085 boolean swipingInProgress = mSwipingInProgress;
Philip Quinn97f2e742020-03-23 19:07:09 -07004086 if (!swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004087 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
4088 }
4089 boolean scrollWantsIt = false;
4090 if (!swipingInProgress && !mExpandingNotification) {
4091 scrollWantsIt = onInterceptTouchEventScroll(ev);
4092 }
4093 boolean swipeWantsIt = false;
4094 if (!mIsBeingDragged
4095 && !mExpandingNotification
4096 && !mExpandedInThisMotion
4097 && !mOnlyScrollingInThisMotion
4098 && !mDisallowDismissInThisMotion) {
4099 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
4100 }
4101 // Check if we need to clear any snooze leavebehinds
4102 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004103 if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
4104 !expandWantsIt && !scrollWantsIt) {
4105 mCheckForLeavebehind = false;
4106 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
4107 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
4108 false /* resetMenu */);
4109 }
4110 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
4111 mCheckForLeavebehind = true;
4112 }
4113 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
4114 }
4115
4116 @ShadeViewRefactor(RefactorComponent.INPUT)
4117 private void handleEmptySpaceClick(MotionEvent ev) {
4118 switch (ev.getActionMasked()) {
4119 case MotionEvent.ACTION_MOVE:
Philip Quinn47169132020-03-23 19:04:55 -07004120 final float touchSlop = getTouchSlop(ev);
4121 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > touchSlop
4122 || Math.abs(ev.getX() - mInitialTouchX) > touchSlop)) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004123 mTouchIsClick = false;
4124 }
4125 break;
4126 case MotionEvent.ACTION_UP:
4127 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
4128 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
4129 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
4130 }
4131 break;
4132 }
4133 }
4134
4135 @ShadeViewRefactor(RefactorComponent.INPUT)
4136 private void initDownStates(MotionEvent ev) {
4137 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
4138 mExpandedInThisMotion = false;
4139 mOnlyScrollingInThisMotion = !mScroller.isFinished();
4140 mDisallowScrollingInThisMotion = false;
4141 mDisallowDismissInThisMotion = false;
4142 mTouchIsClick = true;
4143 mInitialTouchX = ev.getX();
4144 mInitialTouchY = ev.getY();
4145 }
4146 }
4147
4148 @Override
4149 @ShadeViewRefactor(RefactorComponent.INPUT)
4150 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
4151 super.requestDisallowInterceptTouchEvent(disallowIntercept);
4152 if (disallowIntercept) {
4153 cancelLongPress();
4154 }
4155 }
4156
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004157 @ShadeViewRefactor(RefactorComponent.INPUT)
Selim Cinek67b22602014-03-10 15:40:16 +01004158 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02004159 if (!isScrollingEnabled()) {
4160 return false;
4161 }
Selim Cinek67b22602014-03-10 15:40:16 +01004162 /*
4163 * This method JUST determines whether we want to intercept the motion.
4164 * If we return true, onMotionEvent will be called and we do the actual
4165 * scrolling there.
4166 */
4167
4168 /*
Jason Monke59dc402018-08-16 12:05:01 -04004169 * Shortcut the most recurring case: the user is in the dragging
4170 * state and is moving their finger. We want to intercept this
4171 * motion.
4172 */
Selim Cinek67b22602014-03-10 15:40:16 +01004173 final int action = ev.getAction();
4174 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
4175 return true;
4176 }
4177
Selim Cinek67b22602014-03-10 15:40:16 +01004178 switch (action & MotionEvent.ACTION_MASK) {
4179 case MotionEvent.ACTION_MOVE: {
4180 /*
4181 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04004182 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01004183 */
4184
4185 /*
Jason Monke59dc402018-08-16 12:05:01 -04004186 * Locally do absolute value. mLastMotionY is set to the y value
4187 * of the down event.
4188 */
Selim Cinek67b22602014-03-10 15:40:16 +01004189 final int activePointerId = mActivePointerId;
4190 if (activePointerId == INVALID_POINTER) {
4191 // If we don't have a valid id, the touch down wasn't on content.
4192 break;
4193 }
4194
4195 final int pointerIndex = ev.findPointerIndex(activePointerId);
4196 if (pointerIndex == -1) {
4197 Log.e(TAG, "Invalid pointerId=" + activePointerId
4198 + " in onInterceptTouchEvent");
4199 break;
4200 }
4201
4202 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02004203 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01004204 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02004205 final int xDiff = Math.abs(x - mDownX);
Philip Quinn47169132020-03-23 19:04:55 -07004206 if (yDiff > getTouchSlop(ev) && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01004207 setIsBeingDragged(true);
4208 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02004209 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01004210 initVelocityTrackerIfNotExists();
4211 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01004212 }
4213 break;
4214 }
4215
4216 case MotionEvent.ACTION_DOWN: {
4217 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05304218 mScrolledToTopOnFirstDown = isScrolledToTop();
Steve Elliottb0940382020-02-20 14:24:02 -05004219 final ExpandableView childAtTouchPos = getChildAtPosition(
4220 ev.getX(), y, false /* requireMinHeight */, false /* ignoreDecors */);
4221 if (childAtTouchPos == null) {
Selim Cinek67b22602014-03-10 15:40:16 +01004222 setIsBeingDragged(false);
4223 recycleVelocityTracker();
4224 break;
4225 }
4226
4227 /*
4228 * Remember location of down touch.
4229 * ACTION_DOWN always refers to pointer index 0.
4230 */
4231 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02004232 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01004233 mActivePointerId = ev.getPointerId(0);
4234
4235 initOrResetVelocityTracker();
4236 mVelocityTracker.addMovement(ev);
4237 /*
Jason Monke59dc402018-08-16 12:05:01 -04004238 * If being flinged and user touches the screen, initiate drag;
4239 * otherwise don't. mScroller.isFinished should be false when
4240 * being flinged.
4241 */
Selim Cinek67b22602014-03-10 15:40:16 +01004242 boolean isBeingDragged = !mScroller.isFinished();
4243 setIsBeingDragged(isBeingDragged);
4244 break;
4245 }
4246
4247 case MotionEvent.ACTION_CANCEL:
4248 case MotionEvent.ACTION_UP:
4249 /* Release the drag */
4250 setIsBeingDragged(false);
4251 mActivePointerId = INVALID_POINTER;
4252 recycleVelocityTracker();
Gus Prevas0fa58d62019-01-11 13:58:40 -05004253 if (ANCHOR_SCROLLING) {
4254 // TODO
4255 } else {
4256 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
4257 animateScroll();
4258 }
Selim Cinek67b22602014-03-10 15:40:16 +01004259 }
4260 break;
4261 case MotionEvent.ACTION_POINTER_UP:
4262 onSecondaryPointerUp(ev);
4263 break;
4264 }
4265
4266 /*
Jason Monke59dc402018-08-16 12:05:01 -04004267 * The only time we want to intercept motion events is if we are in the
4268 * drag mode.
4269 */
Selim Cinek67b22602014-03-10 15:40:16 +01004270 return mIsBeingDragged;
4271 }
4272
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004273 /**
4274 * @return Whether the specified motion event is actually happening over the content.
4275 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004276 @ShadeViewRefactor(RefactorComponent.INPUT)
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004277 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01004278 return isInContentBounds(event.getY());
4279 }
4280
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004281
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004282 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004283 @ShadeViewRefactor(RefactorComponent.INPUT)
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004284 void setIsBeingDragged(boolean isDragged) {
Selim Cinek67b22602014-03-10 15:40:16 +01004285 mIsBeingDragged = isDragged;
4286 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004287 requestDisallowInterceptTouchEvent(true);
Geoffrey Pitsch409db272017-08-28 14:51:52 +00004288 cancelLongPress();
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004289 resetExposedMenuView(true /* animate */, true /* force */);
Selim Cinek67b22602014-03-10 15:40:16 +01004290 }
4291 }
4292
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004293 @ShadeViewRefactor(RefactorComponent.INPUT)
4294 public void requestDisallowLongPress() {
4295 cancelLongPress();
4296 }
4297
4298 @ShadeViewRefactor(RefactorComponent.INPUT)
4299 public void requestDisallowDismiss() {
4300 mDisallowDismissInThisMotion = true;
4301 }
4302
4303 @ShadeViewRefactor(RefactorComponent.INPUT)
4304 public void cancelLongPress() {
4305 mSwipeHelper.cancelLongPress();
4306 }
4307
4308 @ShadeViewRefactor(RefactorComponent.INPUT)
4309 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
4310 mOnEmptySpaceClickListener = listener;
4311 }
4312
4313 /** @hide */
4314 @Override
4315 @ShadeViewRefactor(RefactorComponent.INPUT)
4316 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4317 if (super.performAccessibilityActionInternal(action, arguments)) {
4318 return true;
4319 }
4320 if (!isEnabled()) {
4321 return false;
4322 }
4323 int direction = -1;
4324 switch (action) {
4325 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
4326 // fall through
4327 case android.R.id.accessibilityActionScrollDown:
4328 direction = 1;
4329 // fall through
4330 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
4331 // fall through
4332 case android.R.id.accessibilityActionScrollUp:
Gus Prevas0fa58d62019-01-11 13:58:40 -05004333 if (ANCHOR_SCROLLING) {
4334 // TODO
4335 } else {
4336 final int viewportHeight =
4337 getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
4338 - mShelf.getIntrinsicHeight();
4339 final int targetScrollY = Math.max(0,
4340 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
4341 if (targetScrollY != mOwnScrollY) {
4342 mScroller.startScroll(mScrollX, mOwnScrollY, 0,
4343 targetScrollY - mOwnScrollY);
4344 animateScroll();
4345 return true;
4346 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004347 }
4348 break;
4349 }
4350 return false;
4351 }
4352
4353 @ShadeViewRefactor(RefactorComponent.INPUT)
4354 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4355 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
4356 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
4357 View translatingParentView = mSwipeHelper.getTranslatingParentView();
4358 View view = null;
4359 if (guts != null && !guts.getGutsContent().isLeavebehind()) {
4360 // Only close visible guts if they're not a leavebehind.
4361 view = guts;
4362 } else if (menuRow != null && menuRow.isMenuVisible()
4363 && translatingParentView != null) {
4364 // Checking menu
4365 view = translatingParentView;
4366 }
4367 if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
4368 // Touch was outside visible guts / menu notification, close what's visible
4369 mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
4370 false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
4371 false /* resetMenu */);
4372 resetExposedMenuView(true /* animate */, true /* force */);
4373 }
4374 }
4375
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05004376 @ShadeViewRefactor(RefactorComponent.INPUT)
4377 private void setSwipingInProgress(boolean swiping) {
4378 mSwipingInProgress = swiping;
4379 if (swiping) {
4380 requestDisallowInterceptTouchEvent(true);
4381 }
4382 }
4383
Selim Cinek67b22602014-03-10 15:40:16 +01004384 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004385 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01004386 public void onWindowFocusChanged(boolean hasWindowFocus) {
4387 super.onWindowFocusChanged(hasWindowFocus);
4388 if (!hasWindowFocus) {
Geoffrey Pitsch409db272017-08-28 14:51:52 +00004389 cancelLongPress();
Selim Cinek67b22602014-03-10 15:40:16 +01004390 }
4391 }
Selim Cinekfab078b2014-03-27 22:45:58 +01004392
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07004393 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004394 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04004395 public void clearChildFocus(View child) {
4396 super.clearChildFocus(child);
4397 if (mForcedScroll == child) {
4398 mForcedScroll = null;
4399 }
4400 }
4401
Selim Cinekfab078b2014-03-27 22:45:58 +01004402 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004403 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekfab078b2014-03-27 22:45:58 +01004404 public boolean isScrolledToTop() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004405 if (ANCHOR_SCROLLING) {
4406 updateScrollAnchor();
4407 // TODO: once we're recycling this will need to check the adapter position of the child
4408 return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
4409 } else {
4410 return mOwnScrollY == 0;
4411 }
Selim Cinekfab078b2014-03-27 22:45:58 +01004412 }
4413
4414 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004415 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004416 public boolean isScrolledToBottom() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004417 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05004418 return getMaxPositiveScrollAmount() <= 0;
Gus Prevas0fa58d62019-01-11 13:58:40 -05004419 } else {
4420 return mOwnScrollY >= getScrollRange();
4421 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004422 }
4423
4424 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004425 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfab078b2014-03-27 22:45:58 +01004426 public View getHostView() {
4427 return this;
4428 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02004429
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004430 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004431 public int getEmptyBottomMargin() {
Selim Cinekdb167372016-11-17 15:41:17 -08004432 return Math.max(mMaxLayoutHeight - mContentHeight, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004433 }
4434
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004435 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellorc2dbe492017-03-30 13:22:03 -07004436 public void checkSnoozeLeavebehind() {
4437 if (mCheckForLeavebehind) {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04004438 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
Eliot Courtney47098cb2017-10-18 17:30:30 +09004439 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
4440 false /* resetMenu */);
Mady Mellorc2dbe492017-03-30 13:22:03 -07004441 mCheckForLeavebehind = false;
4442 }
4443 }
4444
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004445 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellorc2dbe492017-03-30 13:22:03 -07004446 public void resetCheckSnoozeLeavebehind() {
4447 mCheckForLeavebehind = true;
4448 }
4449
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004450 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek1685e632014-04-08 02:27:49 +02004451 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02004452 mIsExpansionChanging = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004453 mAmbientState.setExpansionChanging(true);
Mady Mellorc2dbe492017-03-30 13:22:03 -07004454 checkSnoozeLeavebehind();
Selim Cinek1685e632014-04-08 02:27:49 +02004455 }
4456
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004457 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek1685e632014-04-08 02:27:49 +02004458 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02004459 mIsExpansionChanging = false;
Mady Mellorc2dbe492017-03-30 13:22:03 -07004460 resetCheckSnoozeLeavebehind();
Selim Cinekd5ab6452016-12-08 16:34:00 -08004461 mAmbientState.setExpansionChanging(false);
Selim Cinek4fe3e472014-07-03 16:32:54 +02004462 if (!mIsExpanded) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004463 resetScrollPosition();
Jason Monk2a6ea9c2017-01-26 11:14:51 -05004464 mStatusBar.resetUserExpandedStates();
Selim Cinek5b1591a2017-07-03 17:05:01 +02004465 clearTemporaryViews();
4466 clearUserLockedViews();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05004467 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
Selim Cinekd4c32302018-11-19 19:43:14 -08004468 if (draggedViews.size() > 0) {
4469 draggedViews.clear();
4470 updateContinuousShadowDrawing();
4471 }
Selim Cinek5b1591a2017-07-03 17:05:01 +02004472 }
4473 }
Selim Cinekf336f4c2014-11-12 16:58:16 +01004474
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004475 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5b1591a2017-07-03 17:05:01 +02004476 private void clearUserLockedViews() {
4477 for (int i = 0; i < getChildCount(); i++) {
4478 ExpandableView child = (ExpandableView) getChildAt(i);
4479 if (child instanceof ExpandableNotificationRow) {
4480 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4481 row.setUserLocked(false);
4482 }
4483 }
4484 }
4485
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004486 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5b1591a2017-07-03 17:05:01 +02004487 private void clearTemporaryViews() {
Selim Cinek9dd0d042018-05-14 18:12:42 -07004488 // lets make sure nothing is transient anymore
Rohan Shah8ee53652018-04-05 11:13:50 -07004489 clearTemporaryViewsInGroup(this);
Selim Cinek5b1591a2017-07-03 17:05:01 +02004490 for (int i = 0; i < getChildCount(); i++) {
4491 ExpandableView child = (ExpandableView) getChildAt(i);
4492 if (child instanceof ExpandableNotificationRow) {
4493 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Rohan Shah8ee53652018-04-05 11:13:50 -07004494 clearTemporaryViewsInGroup(row.getChildrenContainer());
Selim Cinekd1395642016-04-28 12:22:42 -07004495 }
Selim Cinek4fe3e472014-07-03 16:32:54 +02004496 }
Selim Cinek1685e632014-04-08 02:27:49 +02004497 }
4498
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004499 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Rohan Shah8ee53652018-04-05 11:13:50 -07004500 private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
Selim Cinekd1395642016-04-28 12:22:42 -07004501 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
Selim Cinek81f26d32016-05-09 18:54:10 -04004502 viewGroup.removeTransientView(viewGroup.getTransientView(0));
Selim Cinekd1395642016-04-28 12:22:42 -07004503 }
4504 }
4505
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004506 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004507 public void onPanelTrackingStarted() {
4508 mPanelTracking = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004509 mAmbientState.setPanelTracking(true);
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004510 resetExposedMenuView(true /* animate */, true /* force */);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004511 }
Jason Monke59dc402018-08-16 12:05:01 -04004512
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004513 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004514 public void onPanelTrackingStopped() {
4515 mPanelTracking = false;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004516 mAmbientState.setPanelTracking(false);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004517 }
4518
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004519 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb24e0a92015-06-09 20:17:30 -07004520 public void resetScrollPosition() {
4521 mScroller.abortAnimation();
Gus Prevas0fa58d62019-01-11 13:58:40 -05004522 if (ANCHOR_SCROLLING) {
4523 // TODO: once we're recycling this will need to modify the adapter position instead
4524 mScrollAnchorView = getFirstChildNotGone();
4525 mScrollAnchorViewY = 0;
4526 updateOnScrollChange();
4527 } else {
4528 setOwnScrollY(0);
4529 }
Selim Cinekb24e0a92015-06-09 20:17:30 -07004530 }
4531
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004532 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek3d6ae232019-01-04 14:14:33 -08004533 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02004534 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02004535 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02004536 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinek3d6ae232019-01-04 14:14:33 -08004537 mAmbientState.setShadeExpanded(isExpanded);
4538 mStateAnimator.setShadeExpanded(isExpanded);
shawnlin3a950c32019-05-15 20:06:10 +08004539 mSwipeHelper.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02004540 if (changed) {
Selim Cinekaae9ad62019-07-22 18:36:54 -07004541 mWillExpand = false;
Selim Cinek9184f9c2016-02-02 17:36:53 -08004542 if (!mIsExpanded) {
4543 mGroupManager.collapseAllGroups();
Selim Cinek5b1591a2017-07-03 17:05:01 +02004544 mExpandHelper.cancelImmediately();
Selim Cinek9184f9c2016-02-02 17:36:53 -08004545 }
Selim Cinekcab4a602014-09-03 14:47:57 +02004546 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02004547 updateChronometers();
Selim Cinekf3fa6852016-12-20 18:36:02 +01004548 requestChildrenUpdate();
Selim Cinek98713a42015-09-21 15:47:20 +02004549 }
4550 }
4551
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004552 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek98713a42015-09-21 15:47:20 +02004553 private void updateChronometers() {
4554 int childCount = getChildCount();
4555 for (int i = 0; i < childCount; i++) {
4556 updateChronometerForChild(getChildAt(i));
4557 }
4558 }
4559
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004560 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek98713a42015-09-21 15:47:20 +02004561 private void updateChronometerForChild(View child) {
4562 if (child instanceof ExpandableNotificationRow) {
4563 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4564 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02004565 }
Selim Cinek1685e632014-04-08 02:27:49 +02004566 }
4567
Jorim Jaggibe565df2014-04-28 17:51:23 +02004568 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01004569 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004570 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02004571 updateScrollPositionOnExpandInBottom(view);
4572 clampScrollPosition();
Lucas Dupin60661a62018-04-12 10:50:13 -07004573 notifyHeightChangeListener(view, needsAnimation);
Selim Cinekbc243a92016-09-27 16:35:13 -07004574 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
4575 ? (ExpandableNotificationRow) view
4576 : null;
Gus Prevase2d6f042018-10-17 15:25:30 -04004577 NotificationSection firstSection = getFirstVisibleSection();
4578 ActivatableNotificationView firstVisibleChild =
4579 firstSection == null ? null : firstSection.getFirstVisibleChild();
4580 if (row != null) {
4581 if (row == firstVisibleChild
4582 || row.getNotificationParent() == firstVisibleChild) {
4583 updateAlgorithmLayoutMinHeight();
4584 }
Selim Cinekbc243a92016-09-27 16:35:13 -07004585 }
Selim Cinekb5605e52015-02-20 18:21:41 +01004586 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08004587 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01004588 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004589 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02004590 }
4591
Selim Cineka5e211b2014-08-11 17:35:48 +02004592 @Override
4593 public void onReset(ExpandableView view) {
Selim Cinek51ae05d2014-09-09 15:51:38 +02004594 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02004595 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02004596 }
4597
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004598 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekf7a14c02014-07-07 14:01:46 +02004599 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
Selim Cinek51d21972017-07-19 17:39:20 -07004600 if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02004601 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Gus Prevas0fa58d62019-01-11 13:58:40 -05004602 // TODO: once we're recycling this will need to check the adapter position of the child
Selim Cinekcb9400a2015-06-03 16:56:13 +02004603 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08004604 if (row.isSummaryWithChildren()) {
4605 return;
4606 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02004607 // We are actually expanding this view
Selim Cinek4e4cac32016-03-11 16:45:52 -08004608 float endPosition = row.getTranslationY() + row.getActualHeight();
Selim Cinek388df6d2015-10-22 13:25:11 -07004609 if (row.isChildInGroup()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08004610 endPosition += row.getNotificationParent().getTranslationY();
Selim Cinek388df6d2015-10-22 13:25:11 -07004611 }
Selim Cinekdb167372016-11-17 15:41:17 -08004612 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
Gus Prevase2d6f042018-10-17 15:25:30 -04004613 NotificationSection lastSection = getLastVisibleSection();
4614 ActivatableNotificationView lastVisibleChild =
4615 lastSection == null ? null : lastSection.getLastVisibleChild();
4616 if (row != lastVisibleChild && mShelf.getVisibility() != GONE) {
Selim Cinekdb167372016-11-17 15:41:17 -08004617 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
4618 }
4619 if (endPosition > layoutEnd) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004620 if (ANCHOR_SCROLLING) {
4621 mScrollAnchorViewY -= (endPosition - layoutEnd);
4622 updateScrollAnchor();
4623 updateOnScrollChange();
4624 } else {
4625 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
4626 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02004627 mDisallowScrollingInThisMotion = true;
4628 }
4629 }
4630 }
4631 }
4632
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004633 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggibe565df2014-04-28 17:51:23 +02004634 public void setOnHeightChangedListener(
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004635 ExpandableView.OnHeightChangedListener onHeightChangedListener) {
4636 this.mOnHeightChangedListener = onHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01004637 }
4638
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004639 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02004640 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08004641 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02004642 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07004643 runAnimationFinishedRunnables();
Selim Cinek9dd0d042018-05-14 18:12:42 -07004644 clearTransient();
Selim Cinek8fc78752016-07-13 14:34:56 -07004645 clearHeadsUpDisappearRunning();
4646 }
4647
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004648 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8fc78752016-07-13 14:34:56 -07004649 private void clearHeadsUpDisappearRunning() {
4650 for (int i = 0; i < getChildCount(); i++) {
4651 View view = getChildAt(i);
4652 if (view instanceof ExpandableNotificationRow) {
Selim Cinek76e813c2016-07-14 11:16:58 -07004653 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcafa87f2016-10-26 17:00:17 -07004654 row.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07004655 if (row.isSummaryWithChildren()) {
Kevin Han43077f92020-02-28 12:51:53 -08004656 for (ExpandableNotificationRow child : row.getAttachedChildren()) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07004657 child.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07004658 }
4659 }
Selim Cinek8fc78752016-07-13 14:34:56 -07004660 }
4661 }
Selim Cinek0fccc722015-07-29 17:04:36 -07004662 }
4663
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004664 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek9dd0d042018-05-14 18:12:42 -07004665 private void clearTransient() {
4666 for (ExpandableView view : mClearTransientViewsWhenFinished) {
4667 StackStateAnimator.removeTransientView(view);
Selim Cinek0fccc722015-07-29 17:04:36 -07004668 }
Selim Cinek9dd0d042018-05-14 18:12:42 -07004669 mClearTransientViewsWhenFinished.clear();
Selim Cinek32a59fd32015-06-10 13:54:42 -07004670 }
4671
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004672 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek32a59fd32015-06-10 13:54:42 -07004673 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004674 for (Runnable runnable : mAnimationFinishedRunnables) {
4675 runnable.run();
4676 }
4677 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02004678 }
4679
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004680 /**
4681 * See {@link AmbientState#setDimmed}.
4682 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004683 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004684 public void setDimmed(boolean dimmed, boolean animate) {
Selim Cinek8a9308d2017-08-24 09:31:08 -07004685 dimmed &= onKeyguard();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004686 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02004687 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004688 mDimmedNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004689 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08004690 animateDimmed(dimmed);
4691 } else {
4692 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004693 }
4694 requestChildrenUpdate();
4695 }
4696
Selim Cinek8a9308d2017-08-24 09:31:08 -07004697 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004698 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8a9308d2017-08-24 09:31:08 -07004699 boolean isDimmed() {
4700 return mAmbientState.isDimmed();
4701 }
4702
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004703 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd35c2792016-01-21 13:20:57 -08004704 private void setDimAmount(float dimAmount) {
4705 mDimAmount = dimAmount;
4706 updateBackgroundDimming();
4707 }
4708
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004709 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekd35c2792016-01-21 13:20:57 -08004710 private void animateDimmed(boolean dimmed) {
4711 if (mDimAnimator != null) {
4712 mDimAnimator.cancel();
4713 }
4714 float target = dimmed ? 1.0f : 0.0f;
4715 if (target == mDimAmount) {
4716 return;
4717 }
4718 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
4719 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
4720 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
4721 mDimAnimator.addListener(mDimEndListener);
4722 mDimAnimator.addUpdateListener(mDimUpdateListener);
4723 mDimAnimator.start();
4724 }
Evan Laird91d0f102018-09-18 17:39:55 -04004725
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004726 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupin28790c02020-01-30 14:18:41 -08004727 private void updateSensitiveness(boolean animate) {
4728 boolean hideSensitive = mLockscreenUserManager.isAnyProfilePublicMode();
Jorim Jaggiae441282014-08-01 02:45:18 +02004729 if (hideSensitive != mAmbientState.isHideSensitive()) {
4730 int childCount = getChildCount();
4731 for (int i = 0; i < childCount; i++) {
4732 ExpandableView v = (ExpandableView) getChildAt(i);
4733 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
4734 }
4735 mAmbientState.setHideSensitive(hideSensitive);
4736 if (animate && mAnimationsEnabled) {
4737 mHideSensitiveNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004738 mNeedsAnimation = true;
Jorim Jaggiae441282014-08-01 02:45:18 +02004739 }
Selim Cinek0b9cf462017-12-07 16:31:03 -08004740 updateContentHeight();
Jorim Jaggiae441282014-08-01 02:45:18 +02004741 requestChildrenUpdate();
4742 }
4743 }
4744
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004745 /**
4746 * See {@link AmbientState#setActivatedChild}.
4747 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004748 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka32ab602014-06-11 15:06:01 +02004749 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004750 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02004751 if (mAnimationsEnabled) {
4752 mActivateNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004753 mNeedsAnimation = true;
Jorim Jaggi75c95042014-05-16 19:09:59 +02004754 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004755 requestChildrenUpdate();
4756 }
4757
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004758 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka32ab602014-06-11 15:06:01 +02004759 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004760 return mAmbientState.getActivatedChild();
4761 }
4762
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004763 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02004764 private void applyCurrentState() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05004765 int numChildren = getChildCount();
4766 for (int i = 0; i < numChildren; i++) {
4767 ExpandableView child = (ExpandableView) getChildAt(i);
4768 child.applyViewState();
4769 }
4770
Selim Cinekf4c19962014-05-01 21:55:31 +02004771 if (mListener != null) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09004772 mListener.onChildLocationsChanged();
Selim Cinekf4c19962014-05-01 21:55:31 +02004773 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07004774 runAnimationFinishedRunnables();
Selim Cinekea66ca02016-05-24 13:33:47 -07004775 setAnimationRunning(false);
Selim Cinek6811d722016-01-19 17:53:12 -08004776 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08004777 updateViewShadows();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08004778 updateClippingToTopRoundedCorner();
Selim Cinek33223572016-02-19 19:32:22 -08004779 }
4780
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004781 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek33223572016-02-19 19:32:22 -08004782 private void updateViewShadows() {
4783 // we need to work around an issue where the shadow would not cast between siblings when
4784 // their z difference is between 0 and 0.1
4785
4786 // Lefts first sort by Z difference
4787 for (int i = 0; i < getChildCount(); i++) {
4788 ExpandableView child = (ExpandableView) getChildAt(i);
4789 if (child.getVisibility() != GONE) {
4790 mTmpSortedChildren.add(child);
4791 }
4792 }
4793 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
4794
4795 // Now lets update the shadow for the views
4796 ExpandableView previous = null;
4797 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
4798 ExpandableView expandableView = mTmpSortedChildren.get(i);
4799 float translationZ = expandableView.getTranslationZ();
4800 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
4801 float diff = otherZ - translationZ;
4802 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
4803 // There is no fake shadow to be drawn
4804 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
4805 } else {
4806 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
Mady Mellorb0a82462016-04-30 17:31:02 -07004807 expandableView.getTranslationY() - previous.getExtraBottomPadding();
4808 expandableView.setFakeShadowIntensity(
4809 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
Selim Cinek33223572016-02-19 19:32:22 -08004810 previous.getOutlineAlpha(), (int) yLocation,
4811 previous.getOutlineTranslation());
4812 }
4813 previous = expandableView;
4814 }
4815
4816 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02004817 }
4818
Lucas Dupine17ce522017-07-17 15:45:06 -07004819 /**
4820 * Update colors of "dismiss" and "empty shade" views.
4821 *
4822 * @param lightTheme True if light theme should be used.
4823 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004824 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Lucas Dupine17ce522017-07-17 15:45:06 -07004825 public void updateDecorViews(boolean lightTheme) {
4826 if (lightTheme == mUsingLightTheme) {
4827 return;
4828 }
4829 mUsingLightTheme = lightTheme;
4830 Context context = new ContextThemeWrapper(mContext,
4831 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI);
Steve Elliott577ef792020-04-02 13:49:36 -04004832 final @ColorInt int textColor =
4833 Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor);
4834 mSectionsManager.setHeaderForegroundColor(textColor);
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004835 mFooterView.setTextColor(textColor);
Lucas Dupine17ce522017-07-17 15:45:06 -07004836 mEmptyShadeView.setTextColor(textColor);
4837 }
4838
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004839 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004840 public void goToFullShade(long delay) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004841 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004842 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004843 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004844 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02004845 }
4846
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004847 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02004848 public void cancelExpandHelper() {
4849 mExpandHelper.cancel();
4850 }
4851
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004852 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1408eb52014-06-02 14:45:38 +02004853 public void setIntrinsicPadding(int intrinsicPadding) {
4854 mIntrinsicPadding = intrinsicPadding;
Selim Cinek1f624952017-06-08 19:11:50 -07004855 mAmbientState.setIntrinsicPadding(intrinsicPadding);
Selim Cinek1408eb52014-06-02 14:45:38 +02004856 }
4857
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004858 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi30c305c2014-07-01 23:34:41 +02004859 public int getIntrinsicPadding() {
4860 return mIntrinsicPadding;
4861 }
4862
Selim Cinekc0ce82d2014-06-10 13:21:15 +02004863 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004864 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc0ce82d2014-06-10 13:21:15 +02004865 public boolean shouldDelayChildPressedState() {
4866 return true;
4867 }
4868
Jorim Jaggi457cc352014-06-02 22:47:42 +02004869 /**
Selim Cinekc1d9ab22019-05-21 18:08:30 -07004870 * See {@link AmbientState#setDozing}.
John Spurlockbf370992014-06-17 13:58:31 -04004871 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004872 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc1d9ab22019-05-21 18:08:30 -07004873 public void setDozing(boolean dozing, boolean animate,
4874 @Nullable PointF touchWakeUpScreenLocation) {
4875 if (mAmbientState.isDozing() == dozing) {
Adrian Roos260c1f72017-08-07 15:52:26 +02004876 return;
4877 }
Selim Cinekc1d9ab22019-05-21 18:08:30 -07004878 mAmbientState.setDozing(dozing);
Lucas Dupin8e9fa2d2018-01-29 15:36:35 -08004879 requestChildrenUpdate();
Adrian Roos7a9551a2017-01-11 12:27:49 -08004880 notifyHeightChangeListener(mShelf);
John Spurlockbf370992014-06-17 13:58:31 -04004881 }
4882
Anthony Chen3cb3ad92016-12-01 10:58:47 -08004883 /**
Selim Cinek195dfc52019-05-30 19:35:05 -07004884 * Sets the current hide amount.
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004885 *
Selim Cinek195dfc52019-05-30 19:35:05 -07004886 * @param linearHideAmount The hide amount that follows linear interpoloation in the
Jason Monke59dc402018-08-16 12:05:01 -04004887 * animation,
4888 * i.e. animates from 0 to 1 or vice-versa in a linear manner.
Selim Cinek195dfc52019-05-30 19:35:05 -07004889 * @param interpolatedHideAmount The hide amount that follows the actual interpolation of the
Jason Monke59dc402018-08-16 12:05:01 -04004890 * animation curve.
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004891 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004892 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek195dfc52019-05-30 19:35:05 -07004893 public void setHideAmount(float linearHideAmount, float interpolatedHideAmount) {
4894 mLinearHideAmount = linearHideAmount;
4895 mInterpolatedHideAmount = interpolatedHideAmount;
4896 boolean wasFullyHidden = mAmbientState.isFullyHidden();
4897 boolean wasHiddenAtAll = mAmbientState.isHiddenAtAll();
4898 mAmbientState.setHideAmount(interpolatedHideAmount);
4899 boolean nowFullyHidden = mAmbientState.isFullyHidden();
4900 boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll();
4901 if (nowFullyHidden != wasFullyHidden) {
Selim Cinekd09ec7e2019-06-26 11:34:57 -07004902 updateVisibility();
Lucas Dupin16cfe452018-02-08 13:14:50 -08004903 }
Selim Cinek195dfc52019-05-30 19:35:05 -07004904 if (!wasHiddenAtAll && nowHiddenAtAll) {
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004905 resetExposedMenuView(true /* animate */, true /* animate */);
4906 }
Selim Cinek195dfc52019-05-30 19:35:05 -07004907 if (nowFullyHidden != wasFullyHidden || wasHiddenAtAll != nowHiddenAtAll) {
Lucas Dupin64e2f572019-03-21 14:21:14 -07004908 invalidateOutline();
4909 }
Lucas Dupin60661a62018-04-12 10:50:13 -07004910 updateAlgorithmHeightAndPadding();
Selim Cinek972123d2016-05-03 14:25:58 -07004911 updateBackgroundDimming();
Lucas Dupinb561eda2018-04-09 17:25:04 -07004912 requestChildrenUpdate();
Selim Cinek33295a42019-07-26 19:49:08 -07004913 updateOwnTranslationZ();
4914 }
4915
4916 private void updateOwnTranslationZ() {
4917 // Since we are clipping to the outline we need to make sure that the shadows aren't
4918 // clipped when pulsing
4919 float ownTranslationZ = 0;
4920 if (mKeyguardBypassController.getBypassEnabled() && mAmbientState.isHiddenAtAll()) {
4921 ExpandableView firstChildNotGone = getFirstChildNotGone();
4922 if (firstChildNotGone != null && firstChildNotGone.showingPulsing()) {
4923 ownTranslationZ = firstChildNotGone.getTranslationZ();
4924 }
4925 }
4926 setTranslationZ(ownTranslationZ);
Selim Cinek972123d2016-05-03 14:25:58 -07004927 }
4928
Selim Cinekd09ec7e2019-06-26 11:34:57 -07004929 private void updateVisibility() {
4930 boolean shouldShow = !mAmbientState.isFullyHidden() || !onKeyguard();
4931 setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
4932 }
4933
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004934 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek195dfc52019-05-30 19:35:05 -07004935 public void notifyHideAnimationStart(boolean hide) {
4936 // We only swap the scaling factor if we're fully hidden or fully awake to avoid
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004937 // interpolation issues when playing with the power button.
Selim Cinek195dfc52019-05-30 19:35:05 -07004938 if (mInterpolatedHideAmount == 0 || mInterpolatedHideAmount == 1) {
4939 mBackgroundXFactor = hide ? 1.8f : 1.5f;
4940 mHideXInterpolator = hide
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004941 ? Interpolators.FAST_OUT_SLOW_IN_REVERSE
4942 : Interpolators.FAST_OUT_SLOW_IN;
4943 }
Selim Cinek972123d2016-05-03 14:25:58 -07004944 }
4945
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004946 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004947 private int getNotGoneIndex(View child) {
4948 int count = getChildCount();
4949 int notGoneIndex = 0;
4950 for (int i = 0; i < count; i++) {
4951 View v = getChildAt(i);
4952 if (child == v) {
4953 return notGoneIndex;
4954 }
4955 if (v.getVisibility() != View.GONE) {
4956 notGoneIndex++;
4957 }
4958 }
4959 return -1;
4960 }
4961
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004962 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004963 public void setFooterView(@NonNull FooterView footerView) {
Selim Cinek01af3342016-02-09 19:25:31 -08004964 int index = -1;
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004965 if (mFooterView != null) {
4966 index = indexOfChild(mFooterView);
4967 removeView(mFooterView);
Selim Cinek01af3342016-02-09 19:25:31 -08004968 }
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004969 mFooterView = footerView;
4970 addView(mFooterView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04004971 }
4972
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004973 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004974 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08004975 int index = -1;
4976 if (mEmptyShadeView != null) {
4977 index = indexOfChild(mEmptyShadeView);
4978 removeView(mEmptyShadeView);
4979 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004980 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08004981 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004982 }
4983
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004984 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004985 public void updateEmptyShadeView(boolean visible) {
Selim Cinekd60ef9e2018-05-16 16:01:05 -07004986 mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
Lucas Dupinc9274ff2018-05-09 17:40:20 -07004987
4988 int oldTextRes = mEmptyShadeView.getTextResource();
Beverly482ad6a2019-11-06 16:57:05 -05004989 int newTextRes = mZenController.areNotificationsHiddenInShade()
Lucas Dupinc9274ff2018-05-09 17:40:20 -07004990 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
4991 if (oldTextRes != newTextRes) {
4992 mEmptyShadeView.setText(newTextRes);
4993 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004994 }
4995
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004996 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsda7c2452020-04-16 16:44:58 -04004997 public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004998 if (mFooterView == null) {
Anthony Chen5e3742e2017-04-07 14:28:44 -07004999 return;
5000 }
Selim Cinekd60ef9e2018-05-16 16:01:05 -07005001 boolean animate = mIsExpanded && mAnimationsEnabled;
5002 mFooterView.setVisible(visible, animate);
5003 mFooterView.setSecondaryVisible(showDismissView, animate);
Julia Reynoldsda7c2452020-04-16 16:44:58 -04005004 mFooterView.showHistory(showHistory);
Dan Sandlereceda3d2014-07-21 15:35:01 -04005005 }
5006
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005007 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dan Sandlereceda3d2014-07-21 15:35:01 -04005008 public void setDismissAllInProgress(boolean dismissAllInProgress) {
5009 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07005010 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07005011 handleDismissAllClipping();
5012 }
5013
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005014 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek9c17b772015-07-07 20:37:09 -07005015 private void handleDismissAllClipping() {
5016 final int count = getChildCount();
5017 boolean previousChildWillBeDismissed = false;
5018 for (int i = 0; i < count; i++) {
5019 ExpandableView child = (ExpandableView) getChildAt(i);
5020 if (child.getVisibility() == GONE) {
5021 continue;
5022 }
5023 if (mDismissAllInProgress && previousChildWillBeDismissed) {
5024 child.setMinClipTopAmount(child.getClipTopAmount());
5025 } else {
5026 child.setMinClipTopAmount(0);
5027 }
Steve Elliott946b00c2019-10-28 11:20:24 -04005028 previousChildWillBeDismissed = canChildBeDismissed(child);
Selim Cinek9c17b772015-07-07 20:37:09 -07005029 }
Selim Cineka272dfe2015-02-20 18:12:28 +01005030 }
5031
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005032 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04005033 public boolean isFooterViewNotGone() {
5034 return mFooterView != null
5035 && mFooterView.getVisibility() != View.GONE
5036 && !mFooterView.willBeGone();
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02005037 }
5038
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005039 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek38475442018-05-18 11:11:46 -07005040 public boolean isFooterViewContentVisible() {
5041 return mFooterView != null && mFooterView.isContentVisible();
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02005042 }
5043
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005044 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04005045 public int getFooterViewHeight() {
5046 return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02005047 }
5048
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005049 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01005050 public int getEmptyShadeViewHeight() {
5051 return mEmptyShadeView.getHeight();
5052 }
5053
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005054 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggie0640dd2014-08-05 23:12:40 +02005055 public float getBottomMostNotificationBottom() {
5056 final int count = getChildCount();
5057 float max = 0;
5058 for (int childIdx = 0; childIdx < count; childIdx++) {
5059 ExpandableView child = (ExpandableView) getChildAt(childIdx);
5060 if (child.getVisibility() == GONE) {
5061 continue;
5062 }
Selim Cineka686b2c2016-10-26 13:58:27 -07005063 float bottom = child.getTranslationY() + child.getActualHeight()
5064 - child.getClipBottomAmount();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02005065 if (bottom > max) {
5066 max = bottom;
5067 }
5068 }
Selim Cinekd2281152015-04-10 14:37:46 -07005069 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02005070 }
5071
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005072 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk2a6ea9c2017-01-26 11:14:51 -05005073 public void setStatusBar(StatusBar statusBar) {
5074 this.mStatusBar = statusBar;
Selim Cinek19c8c702014-08-25 22:09:19 +02005075 }
5076
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005077 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb5605e52015-02-20 18:21:41 +01005078 public void setGroupManager(NotificationGroupManager groupManager) {
5079 this.mGroupManager = groupManager;
Kevin01a53cb2018-11-09 18:19:54 -08005080 mGroupManager.addOnGroupChangeListener(mOnGroupChangeListener);
Selim Cinek379ff8f2015-02-20 17:03:16 +01005081 }
5082
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005083 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek379ff8f2015-02-20 17:03:16 +01005084 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02005085 if (mIsExpanded && mAnimationsEnabled) {
5086 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01005087 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02005088 requestChildrenUpdate();
5089 }
5090 }
5091
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005092 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek04fb2582015-06-02 19:58:09 +02005093 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01005094 int childCount = getChildCount();
5095 for (int i = childCount - 1; i >= 0; i--) {
5096 ExpandableView child = (ExpandableView) getChildAt(i);
5097 if (child.getVisibility() != View.GONE) {
5098 float childTop = child.getY();
5099 if (childTop > touchY) {
5100 // we are above a notification entirely let's abort
5101 return false;
5102 }
Selim Cineka686b2c2016-10-26 13:58:27 -07005103 boolean belowChild = touchY > childTop + child.getActualHeight()
5104 - child.getClipBottomAmount();
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04005105 if (child == mFooterView) {
Jason Monke59dc402018-08-16 12:05:01 -04005106 if (!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(),
5107 touchY - childTop)) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01005108 // We clicked on the dismiss button
5109 return false;
5110 }
5111 } else if (child == mEmptyShadeView) {
5112 // We arrived at the empty shade view, for which we accept all clicks
5113 return true;
Jason Monke59dc402018-08-16 12:05:01 -04005114 } else if (!belowChild) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01005115 // We are on a child
5116 return false;
5117 }
5118 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005119 }
Selim Cinek04fb2582015-06-02 19:58:09 +02005120 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005121 }
5122
Selim Cinekc22fff62016-05-20 12:44:30 -07005123 /** @hide */
5124 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005125 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07005126 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
5127 super.onInitializeAccessibilityEventInternal(event);
5128 event.setScrollable(mScrollable);
Selim Cinekc22fff62016-05-20 12:44:30 -07005129 event.setMaxScrollX(mScrollX);
Gus Prevas0fa58d62019-01-11 13:58:40 -05005130 if (ANCHOR_SCROLLING) {
5131 // TODO
5132 } else {
5133 event.setScrollY(mOwnScrollY);
5134 event.setMaxScrollY(getScrollRange());
5135 }
Selim Cinekc22fff62016-05-20 12:44:30 -07005136 }
5137
5138 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005139 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07005140 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
5141 super.onInitializeAccessibilityNodeInfoInternal(info);
Selim Cinekef406062016-09-29 17:33:13 -07005142 if (mScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005143 info.setScrollable(true);
Selim Cinekef406062016-09-29 17:33:13 -07005144 if (mBackwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005145 info.addAction(
5146 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
5147 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
5148 }
Selim Cinekef406062016-09-29 17:33:13 -07005149 if (mForwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005150 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
5151 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
5152 }
5153 }
Selim Cinek41fe89a2016-06-02 15:27:56 -07005154 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
5155 info.setClassName(ScrollView.class.getName());
Selim Cinekc22fff62016-05-20 12:44:30 -07005156 }
5157
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005158 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb5605e52015-02-20 18:21:41 +01005159 public void generateChildOrderChangedEvent() {
5160 if (mIsExpanded && mAnimationsEnabled) {
5161 mGenerateChildOrderChangedEvent = true;
5162 mNeedsAnimation = true;
5163 requestChildrenUpdate();
5164 }
5165 }
5166
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005167 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005168 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005169 public int getContainerChildCount() {
5170 return getChildCount();
5171 }
5172
5173 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005174 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005175 public View getContainerChildAt(int i) {
5176 return getChildAt(i);
5177 }
5178
5179 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005180 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005181 public void removeContainerView(View v) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04005182 Assert.isMainThread();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005183 removeView(v);
5184 }
5185
5186 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005187 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Evan Laird9a9a6192019-12-19 10:44:21 -05005188 public void removeListItem(NotificationListItem v) {
5189 removeContainerView(v.getView());
5190 }
5191
5192 @Override
5193 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005194 public void addContainerView(View v) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04005195 Assert.isMainThread();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005196 addView(v);
5197 }
5198
Evan Laird9a9a6192019-12-19 10:44:21 -05005199 @Override
5200 public void addListItem(NotificationListItem v) {
5201 addContainerView(v.getView());
5202 }
5203
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005204 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek684a4422015-04-15 16:18:39 -07005205 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005206 mAnimationFinishedRunnables.add(runnable);
5207 }
5208
Ned Burnsf81c4c42019-01-07 14:10:43 -05005209 public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
Evan Laird94492852018-10-25 13:43:01 -04005210 ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
5211 generateHeadsUpAnimation(row, isHeadsUp);
5212 }
5213
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005214 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005215 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
Selim Cinek5cf1d052017-06-01 17:36:46 -07005216 if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005217 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
5218 mNeedsAnimation = true;
Selim Cinekaae9ad62019-07-22 18:36:54 -07005219 if (!mIsExpanded && !mWillExpand && !isHeadsUp) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07005220 row.setHeadsUpAnimatingAway(true);
Selim Cinek73cf02a2016-06-17 13:08:00 -07005221 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005222 requestChildrenUpdate();
5223 }
5224 }
5225
Selim Cineka59ecc32015-04-07 10:51:49 -07005226 /**
5227 * Set the boundary for the bottom heads up position. The heads up will always be above this
5228 * position.
5229 *
Jason Monke59dc402018-08-16 12:05:01 -04005230 * @param height the height of the screen
Selim Cineka59ecc32015-04-07 10:51:49 -07005231 * @param bottomBarHeight the height of the bar on the bottom
5232 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005233 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka59ecc32015-04-07 10:51:49 -07005234 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
5235 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
5236 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005237 requestChildrenUpdate();
5238 }
5239
Selim Cinekaae9ad62019-07-22 18:36:54 -07005240 @Override
5241 public void setWillExpand(boolean willExpand) {
5242 mWillExpand = willExpand;
5243 }
5244
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005245 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005246 public void setTrackingHeadsUp(ExpandableNotificationRow row) {
5247 mTrackingHeadsUp = row != null;
5248 mRoundnessManager.setTrackingHeadsUp(row);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005249 }
5250
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005251 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaac93252015-04-14 20:04:12 -07005252 public void setScrimController(ScrimController scrimController) {
5253 mScrimController = scrimController;
Lucas Dupin8da8f2e92017-04-21 14:02:16 -07005254 mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming);
Selim Cinekaac93252015-04-14 20:04:12 -07005255 }
5256
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005257 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbbc580b2015-06-03 14:11:03 +02005258 public void forceNoOverlappingRendering(boolean force) {
5259 mForceNoOverlappingRendering = force;
5260 }
5261
5262 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005263 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbbc580b2015-06-03 14:11:03 +02005264 public boolean hasOverlappingRendering() {
5265 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
5266 }
5267
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005268 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek6811d722016-01-19 17:53:12 -08005269 public void setAnimationRunning(boolean animationRunning) {
5270 if (animationRunning != mAnimationRunning) {
5271 if (animationRunning) {
Selim Cinekc383fd02016-10-21 15:31:26 -07005272 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08005273 } else {
Selim Cinekc383fd02016-10-21 15:31:26 -07005274 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08005275 }
5276 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08005277 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08005278 }
5279 }
5280
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005281 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek3776fe02016-02-04 13:32:43 -08005282 public boolean isExpanded() {
5283 return mIsExpanded;
5284 }
5285
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005286 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupin4e023812018-04-02 21:19:23 -07005287 public void setPulsing(boolean pulsing, boolean animated) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09005288 if (!mPulsing && !pulsing) {
Adrian Roosb2a87292017-02-13 15:05:03 +01005289 return;
5290 }
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005291 mPulsing = pulsing;
Selim Cinekebf42342017-07-13 15:46:10 +02005292 mAmbientState.setPulsing(pulsing);
Selim Cinekd0b48e32019-05-24 20:49:23 -07005293 mSwipeHelper.setPulsing(pulsing);
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005294 updateNotificationAnimationStates();
Lucas Dupin6bf7b642018-01-22 18:56:24 -08005295 updateAlgorithmHeightAndPadding();
Adrian Roos7a9551a2017-01-11 12:27:49 -08005296 updateContentHeight();
Adrian Roosd83e9992017-03-16 15:17:57 -07005297 requestChildrenUpdate();
Lucas Dupin4e023812018-04-02 21:19:23 -07005298 notifyHeightChangeListener(null, animated);
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005299 }
5300
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005301 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbc243a92016-09-27 16:35:13 -07005302 public void setQsExpanded(boolean qsExpanded) {
5303 mQsExpanded = qsExpanded;
5304 updateAlgorithmLayoutMinHeight();
Riddle Hsu065c01c2018-05-10 23:14:19 +08005305 updateScrollability();
Selim Cinekbc243a92016-09-27 16:35:13 -07005306 }
5307
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005308 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
shawnlin8e4e92c2018-04-12 18:47:24 +08005309 public void setQsExpansionFraction(float qsExpansionFraction) {
5310 mQsExpansionFraction = qsExpansionFraction;
5311 }
5312
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005313 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Gus Prevas0fa58d62019-01-11 13:58:40 -05005314 private void setOwnScrollY(int ownScrollY) {
5315 assert !ANCHOR_SCROLLING;
Selim Cinekef406062016-09-29 17:33:13 -07005316 if (ownScrollY != mOwnScrollY) {
Selim Cinek9212de82017-02-06 16:04:28 -08005317 // We still want to call the normal scrolled changed for accessibility reasons
5318 onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
Selim Cinekef406062016-09-29 17:33:13 -07005319 mOwnScrollY = ownScrollY;
Gus Prevas0fa58d62019-01-11 13:58:40 -05005320 updateOnScrollChange();
5321 }
5322 }
5323
5324 private void updateOnScrollChange() {
5325 updateForwardAndBackwardScrollability();
5326 requestChildrenUpdate();
5327 }
5328
5329 private void updateScrollAnchor() {
5330 int anchorIndex = indexOfChild(mScrollAnchorView);
5331 // If the anchor view has been scrolled off the top, move to the next view.
5332 while (mScrollAnchorViewY < 0) {
5333 View nextAnchor = null;
5334 for (int i = anchorIndex + 1; i < getChildCount(); i++) {
5335 View child = getChildAt(i);
5336 if (child.getVisibility() != View.GONE
5337 && child instanceof ExpandableNotificationRow) {
5338 anchorIndex = i;
5339 nextAnchor = child;
5340 break;
5341 }
5342 }
5343 if (nextAnchor == null) {
5344 break;
5345 }
5346 mScrollAnchorViewY +=
5347 (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
5348 mScrollAnchorView = nextAnchor;
5349 }
5350 // If the view above the anchor view is fully visible, make it the anchor view.
5351 while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
5352 View prevAnchor = null;
5353 for (int i = anchorIndex - 1; i >= 0; i--) {
5354 View child = getChildAt(i);
5355 if (child.getVisibility() != View.GONE
5356 && child instanceof ExpandableNotificationRow) {
5357 anchorIndex = i;
5358 prevAnchor = child;
5359 break;
5360 }
5361 }
5362 if (prevAnchor == null) {
5363 break;
5364 }
5365 float distanceToPreviousAnchor =
5366 mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
5367 if (distanceToPreviousAnchor < mScrollAnchorViewY) {
5368 mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
5369 mScrollAnchorView = prevAnchor;
5370 }
Selim Cinekef406062016-09-29 17:33:13 -07005371 }
5372 }
5373
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005374 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek281c2022016-10-13 19:14:43 -07005375 public void setShelf(NotificationShelf shelf) {
Selim Cinek281c2022016-10-13 19:14:43 -07005376 int index = -1;
5377 if (mShelf != null) {
5378 index = indexOfChild(mShelf);
5379 removeView(mShelf);
5380 }
Selim Cinek0e8d77e2016-11-29 10:35:42 -08005381 mShelf = shelf;
Selim Cinek281c2022016-10-13 19:14:43 -07005382 addView(mShelf, index);
5383 mAmbientState.setShelf(shelf);
Selim Cinekeccb5de2016-10-28 15:04:05 -07005384 mStateAnimator.setShelf(shelf);
Selim Cinekc383fd02016-10-21 15:31:26 -07005385 shelf.bind(mAmbientState, this);
Gus Prevas0fa58d62019-01-11 13:58:40 -05005386 if (ANCHOR_SCROLLING) {
5387 mScrollAnchorView = mShelf;
5388 }
Selim Cinek281c2022016-10-13 19:14:43 -07005389 }
5390
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005391 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek281c2022016-10-13 19:14:43 -07005392 public NotificationShelf getNotificationShelf() {
5393 return mShelf;
5394 }
5395
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005396 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekad7fac02016-10-18 17:09:15 -07005397 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
5398 if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
5399 mMaxDisplayedNotifications = maxDisplayedNotifications;
5400 updateContentHeight();
5401 notifyHeightChangeListener(mShelf);
5402 }
5403 }
5404
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005405 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
shawnlin8e4e92c2018-04-12 18:47:24 +08005406 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
Jason Monke59dc402018-08-16 12:05:01 -04005407 mShouldShowShelfOnly = shouldShowShelfOnly;
shawnlin8e4e92c2018-04-12 18:47:24 +08005408 updateAlgorithmLayoutMinHeight();
5409 }
5410
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005411 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek48ff9b42016-11-09 19:31:51 -08005412 public int getMinExpansionHeight() {
shawnlinbd6d0ad2020-01-29 18:38:49 +08005413 return mShelf.getIntrinsicHeight()
5414 - (mShelf.getIntrinsicHeight() - mStatusBarHeight + mWaterfallTopInset) / 2
5415 + mWaterfallTopInset;
Selim Cinek48ff9b42016-11-09 19:31:51 -08005416 }
5417
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005418 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekcafa87f2016-10-26 17:00:17 -07005419 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
5420 mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
5421 updateClipping();
5422 }
5423
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005424 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekcafa87f2016-10-26 17:00:17 -07005425 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
5426 mHeadsUpAnimatingAway = headsUpAnimatingAway;
5427 updateClipping();
5428 }
5429
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005430 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk297c04e2018-08-23 17:16:59 -04005431 @VisibleForTesting
5432 protected void setStatusBarState(int statusBarState) {
Selim Cinek355652a2016-12-07 13:32:12 -08005433 mStatusBarState = statusBarState;
5434 mAmbientState.setStatusBarState(statusBarState);
Evan Laird91d0f102018-09-18 17:39:55 -04005435 }
5436
5437 private void onStatePostChange() {
Jason Monk1fd3fc32018-08-14 17:20:09 -04005438 boolean onKeyguard = onKeyguard();
Evan Laird91d0f102018-09-18 17:39:55 -04005439
Jason Monk1fd3fc32018-08-14 17:20:09 -04005440 if (mHeadsUpAppearanceController != null) {
Selim Cinekb2c5dc52019-06-24 15:46:52 -07005441 mHeadsUpAppearanceController.onStateChanged();
Jason Monk1fd3fc32018-08-14 17:20:09 -04005442 }
5443
Beverly8fdb5332019-02-04 14:29:49 -05005444 SysuiStatusBarStateController state = (SysuiStatusBarStateController)
5445 Dependency.get(StatusBarStateController.class);
Lucas Dupin28790c02020-01-30 14:18:41 -08005446 updateSensitiveness(state.goingToFullShade() /* animate */);
Jason Monk1fd3fc32018-08-14 17:20:09 -04005447 setDimmed(onKeyguard, state.fromShadeLocked() /* animate */);
5448 setExpandingEnabled(!onKeyguard);
5449 ActivatableNotificationView activatedChild = getActivatedChild();
5450 setActivatedChild(null);
5451 if (activatedChild != null) {
5452 activatedChild.makeInactive(false /* animate */);
5453 }
Jason Monke59dc402018-08-16 12:05:01 -04005454 updateFooter();
Dave Mankoff57445802018-10-10 14:47:34 -04005455 requestChildrenUpdate();
Jason Monke59dc402018-08-16 12:05:01 -04005456 onUpdateRowStates();
Evan Laird91d0f102018-09-18 17:39:55 -04005457
Beverly85d4c192019-09-30 11:40:39 -04005458 mEntryManager.updateNotifications("StatusBar state changed");
Selim Cinekd09ec7e2019-06-26 11:34:57 -07005459 updateVisibility();
Selim Cinek355652a2016-12-07 13:32:12 -08005460 }
5461
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005462 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd5ab6452016-12-08 16:34:00 -08005463 public void setExpandingVelocity(float expandingVelocity) {
5464 mAmbientState.setExpandingVelocity(expandingVelocity);
5465 }
5466
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005467 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekfcff4c62016-12-27 14:26:06 +01005468 public float getOpeningHeight() {
5469 if (mEmptyShadeView.getVisibility() == GONE) {
5470 return getMinExpansionHeight();
5471 } else {
5472 return getAppearEndPosition();
5473 }
5474 }
5475
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005476 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfcff4c62016-12-27 14:26:06 +01005477 public void setIsFullWidth(boolean isFullWidth) {
5478 mAmbientState.setPanelFullWidth(isFullWidth);
5479 }
5480
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005481 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekec29d342017-05-05 18:31:49 -07005482 public void setUnlockHintRunning(boolean running) {
5483 mAmbientState.setUnlockHintRunning(running);
5484 }
5485
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005486 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek5cf1d052017-06-01 17:36:46 -07005487 public void setQsCustomizerShowing(boolean isShowing) {
5488 mAmbientState.setQsCustomizerShowing(isShowing);
5489 requestChildrenUpdate();
5490 }
5491
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005492 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek5cf1d052017-06-01 17:36:46 -07005493 public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
5494 mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
5495 }
5496
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005497 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek707e2072017-06-30 18:32:40 +02005498 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
5499 pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
shawnlin8e4e92c2018-04-12 18:47:24 +08005500 + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s"
5501 + " qsExpandFraction=%f]",
Selim Cinek707e2072017-06-30 18:32:40 +02005502 this.getClass().getSimpleName(),
Jason Monke59dc402018-08-16 12:05:01 -04005503 mPulsing ? "T" : "f",
5504 mAmbientState.isQsCustomizerShowing() ? "T" : "f",
Selim Cinek707e2072017-06-30 18:32:40 +02005505 getVisibility() == View.VISIBLE ? "visible"
5506 : getVisibility() == View.GONE ? "gone"
5507 : "invisible",
5508 getAlpha(),
shawnlin8e4e92c2018-04-12 18:47:24 +08005509 mAmbientState.getScrollY(),
5510 mMaxTopPadding,
Jason Monke59dc402018-08-16 12:05:01 -04005511 mShouldShowShelfOnly ? "T" : "f",
shawnlin8e4e92c2018-04-12 18:47:24 +08005512 mQsExpansionFraction));
Selim Cinek30887662018-10-15 17:37:21 -07005513 int childCount = getChildCount();
5514 pw.println(" Number of children: " + childCount);
5515 pw.println();
5516
5517 for (int i = 0; i < childCount; i++) {
5518 ExpandableView child = (ExpandableView) getChildAt(i);
5519 child.dump(fd, pw, args);
5520 if (!(child instanceof ExpandableNotificationRow)) {
5521 pw.println(" " + child.getClass().getSimpleName());
5522 // Notifications dump it's viewstate as part of their dump to support children
Dave Mankoffa4d195d2018-11-16 13:33:27 -05005523 ExpandableViewState viewState = child.getViewState();
Selim Cinek30887662018-10-15 17:37:21 -07005524 if (viewState == null) {
5525 pw.println(" no viewState!!!");
5526 } else {
5527 pw.print(" ");
5528 viewState.dump(fd, pw, args);
5529 pw.println();
5530 pw.println();
5531 }
5532 }
5533 }
Selim Cinek30887662018-10-15 17:37:21 -07005534 int transientViewCount = getTransientViewCount();
Selim Cinekd4c32302018-11-19 19:43:14 -08005535 pw.println(" Transient Views: " + transientViewCount);
Selim Cinek30887662018-10-15 17:37:21 -07005536 for (int i = 0; i < transientViewCount; i++) {
5537 ExpandableView child = (ExpandableView) getTransientView(i);
5538 child.dump(fd, pw, args);
5539 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05005540 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
Selim Cinekd4c32302018-11-19 19:43:14 -08005541 int draggedCount = draggedViews.size();
5542 pw.println(" Dragged Views: " + draggedCount);
5543 for (int i = 0; i < draggedCount; i++) {
5544 ExpandableView child = (ExpandableView) draggedViews.get(i);
5545 child.dump(fd, pw, args);
5546 }
Selim Cinek707e2072017-06-30 18:32:40 +02005547 }
5548
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005549 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek195dfc52019-05-30 19:35:05 -07005550 public boolean isFullyHidden() {
5551 return mAmbientState.isFullyHidden();
Lucas Dupin16cfe452018-02-08 13:14:50 -08005552 }
5553
Selim Cinek7103fd42016-05-09 22:22:33 -04005554 /**
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005555 * Add a listener whenever the expanded height changes. The first value passed as an
5556 * argument is the expanded height and the second one is the appearFraction.
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005557 *
5558 * @param listener the listener to notify.
5559 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005560 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005561 public void addOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005562 mExpandedHeightListeners.add(listener);
5563 }
5564
5565 /**
Selim Cinek60ffea62018-03-22 13:16:44 -07005566 * Stop a listener from listening to the expandedHeight.
5567 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005568 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005569 public void removeOnExpandedHeightChangedListener(BiConsumer<Float, Float> listener) {
Selim Cinek60ffea62018-03-22 13:16:44 -07005570 mExpandedHeightListeners.remove(listener);
5571 }
5572
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005573 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekf0c79e12018-05-14 17:17:31 -07005574 public void setHeadsUpAppearanceController(
5575 HeadsUpAppearanceController headsUpAppearanceController) {
5576 mHeadsUpAppearanceController = headsUpAppearanceController;
5577 }
5578
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005579 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek9bfc7a52018-06-11 16:09:00 -07005580 public void setIconAreaController(NotificationIconAreaController controller) {
5581 mIconAreaController = controller;
5582 }
5583
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005584 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Will Brockmanb65faa82020-03-06 10:26:04 -05005585 @VisibleForTesting
5586 void clearNotifications(
Ned Burns61269442019-05-02 18:27:23 -04005587 @SelectedRows int selection,
5588 boolean closeShade) {
Jason Monke59dc402018-08-16 12:05:01 -04005589 // animate-swipe all dismissable notifications, then animate the shade closed
5590 int numChildren = getChildCount();
5591
5592 final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
5593 final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
5594 for (int i = 0; i < numChildren; i++) {
5595 final View child = getChildAt(i);
5596 if (child instanceof ExpandableNotificationRow) {
5597 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
5598 boolean parentVisible = false;
5599 boolean hasClipBounds = child.getClipBounds(mTmpRect);
Ned Burns61269442019-05-02 18:27:23 -04005600 if (includeChildInDismissAll(row, selection)) {
Jason Monke59dc402018-08-16 12:05:01 -04005601 viewsToRemove.add(row);
5602 if (child.getVisibility() == View.VISIBLE
5603 && (!hasClipBounds || mTmpRect.height() > 0)) {
5604 viewsToHide.add(child);
5605 parentVisible = true;
5606 }
5607 } else if (child.getVisibility() == View.VISIBLE
5608 && (!hasClipBounds || mTmpRect.height() > 0)) {
5609 parentVisible = true;
5610 }
Kevin Han43077f92020-02-28 12:51:53 -08005611 List<ExpandableNotificationRow> children = row.getAttachedChildren();
Jason Monke59dc402018-08-16 12:05:01 -04005612 if (children != null) {
5613 for (ExpandableNotificationRow childRow : children) {
Ned Burns61269442019-05-02 18:27:23 -04005614 if (includeChildInDismissAll(row, selection)) {
5615 viewsToRemove.add(childRow);
5616 if (parentVisible && row.areChildrenExpanded()) {
5617 hasClipBounds = childRow.getClipBounds(mTmpRect);
5618 if (childRow.getVisibility() == View.VISIBLE
5619 && (!hasClipBounds || mTmpRect.height() > 0)) {
5620 viewsToHide.add(childRow);
5621 }
Jason Monke59dc402018-08-16 12:05:01 -04005622 }
5623 }
5624 }
5625 }
5626 }
5627 }
Ned Burns61269442019-05-02 18:27:23 -04005628
Will Brockmanb65faa82020-03-06 10:26:04 -05005629 // Log dismiss event even if there's nothing to dismiss
5630 mUiEventLogger.log(NotificationPanelEvent.fromSelection(selection));
5631
Jason Monke59dc402018-08-16 12:05:01 -04005632 if (viewsToRemove.isEmpty()) {
Ned Burns61269442019-05-02 18:27:23 -04005633 if (closeShade) {
Dave Mankoffaf8163f2020-01-08 14:24:35 -05005634 Dependency.get(ShadeController.class).animateCollapsePanels(
5635 CommandQueue.FLAG_EXCLUDE_NONE);
Ned Burns61269442019-05-02 18:27:23 -04005636 }
Jason Monke59dc402018-08-16 12:05:01 -04005637 return;
5638 }
5639
Beverly7e0d6492020-02-07 16:22:14 -05005640 performDismissAllAnimations(
5641 viewsToHide,
5642 closeShade,
5643 () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
Jason Monke59dc402018-08-16 12:05:01 -04005644 }
5645
Ned Burns61269442019-05-02 18:27:23 -04005646 private boolean includeChildInDismissAll(
5647 ExpandableNotificationRow row,
5648 @SelectedRows int selection) {
Steve Elliott946b00c2019-10-28 11:20:24 -04005649 return canChildBeDismissed(row) && matchesSelection(row, selection);
Ned Burns61269442019-05-02 18:27:23 -04005650 }
5651
5652 /**
5653 * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
5654 * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
5655 * handler.
5656 *
5657 * @param hideAnimatedList List of rows to animated away. Should only be views that are
5658 * currently visible, or else the stagger will look funky.
5659 * @param closeShade Whether to close the shade after the stagger animation completes.
5660 * @param onAnimationComplete Called after the entire animation completes (including the shade
5661 * closing if appropriate). The rows must be dismissed for real here.
5662 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005663 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -04005664 private void performDismissAllAnimations(
5665 final ArrayList<View> hideAnimatedList,
5666 final boolean closeShade,
5667 final Runnable onAnimationComplete) {
5668
5669 final Runnable onSlideAwayAnimationComplete = () -> {
5670 if (closeShade) {
Dave Mankoffaf8163f2020-01-08 14:24:35 -05005671 Dependency.get(ShadeController.class).addPostCollapseAction(() -> {
Ned Burns61269442019-05-02 18:27:23 -04005672 setDismissAllInProgress(false);
5673 onAnimationComplete.run();
5674 });
Dave Mankoffaf8163f2020-01-08 14:24:35 -05005675 Dependency.get(ShadeController.class).animateCollapsePanels(
5676 CommandQueue.FLAG_EXCLUDE_NONE);
Ned Burns61269442019-05-02 18:27:23 -04005677 } else {
5678 setDismissAllInProgress(false);
5679 onAnimationComplete.run();
5680 }
Jason Monke59dc402018-08-16 12:05:01 -04005681 };
5682
5683 if (hideAnimatedList.isEmpty()) {
Ned Burns61269442019-05-02 18:27:23 -04005684 onSlideAwayAnimationComplete.run();
Jason Monke59dc402018-08-16 12:05:01 -04005685 return;
5686 }
5687
5688 // let's disable our normal animations
5689 setDismissAllInProgress(true);
5690
5691 // Decrease the delay for every row we animate to give the sense of
5692 // accelerating the swipes
5693 int rowDelayDecrement = 10;
5694 int currentDelay = 140;
5695 int totalDelay = 180;
5696 int numItems = hideAnimatedList.size();
5697 for (int i = numItems - 1; i >= 0; i--) {
5698 View view = hideAnimatedList.get(i);
5699 Runnable endRunnable = null;
5700 if (i == 0) {
Ned Burns61269442019-05-02 18:27:23 -04005701 endRunnable = onSlideAwayAnimationComplete;
Jason Monke59dc402018-08-16 12:05:01 -04005702 }
Lucas Dupinfb8bdbb2018-12-02 15:09:37 -08005703 dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
Jason Monke59dc402018-08-16 12:05:01 -04005704 currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
5705 totalDelay += currentDelay;
5706 }
5707 }
5708
Julia Reynolds43689322020-04-28 18:48:54 -04005709 @Override
5710 public void setNotificationActivityStarter(
5711 NotificationActivityStarter notificationActivityStarter) {
5712 mNotificationActivityStarter = notificationActivityStarter;
5713 }
5714
Jason Monke59dc402018-08-16 12:05:01 -04005715 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005716 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -04005717 protected void inflateFooterView() {
5718 FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
5719 R.layout.status_bar_notification_footer, this, false);
5720 footerView.setDismissButtonClickListener(v -> {
5721 mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
Ned Burns61269442019-05-02 18:27:23 -04005722 clearNotifications(ROWS_ALL, true /* closeShade */);
Jason Monke59dc402018-08-16 12:05:01 -04005723 });
Julia Reynoldsda7c2452020-04-16 16:44:58 -04005724 footerView.setManageButtonClickListener(v -> {
Julia Reynolds43689322020-04-28 18:48:54 -04005725 mNotificationActivityStarter.startHistoryIntent(mFooterView.isHistoryShown());
Julia Reynoldsda7c2452020-04-16 16:44:58 -04005726 });
Jason Monke59dc402018-08-16 12:05:01 -04005727 setFooterView(footerView);
5728 }
5729
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005730 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5731 private void inflateEmptyShadeView() {
Jason Monke59dc402018-08-16 12:05:01 -04005732 EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
5733 R.layout.status_bar_no_notifications, this, false);
5734 view.setText(R.string.empty_shade_text);
Julia Reynoldse03eb752020-04-22 16:57:46 -04005735 view.setOnClickListener(v -> {
Julia Reynolds1f94a9d2020-05-05 12:40:21 -04005736 final boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
5737 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
Julia Reynoldse03eb752020-04-22 16:57:46 -04005738 Intent intent = showHistory ? new Intent(
5739 Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(
5740 Settings.ACTION_NOTIFICATION_SETTINGS);
5741 mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
5742 });
Jason Monke59dc402018-08-16 12:05:01 -04005743 setEmptyShadeView(view);
5744 }
5745
5746 /**
5747 * Updates expanded, dimmed and locked states of notification rows.
5748 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005749 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jason Monke59dc402018-08-16 12:05:01 -04005750 public void onUpdateRowStates() {
Jason Monke59dc402018-08-16 12:05:01 -04005751
5752 // The following views will be moved to the end of mStackScroller. This counter represents
5753 // the offset from the last child. Initialized to 1 for the very last position. It is post-
5754 // incremented in the following "changeViewPosition" calls so that its value is correct for
5755 // subsequent calls.
5756 int offsetFromEnd = 1;
Evan Laird04373662020-01-24 17:37:39 -05005757 if (mFgsSectionView != null) {
5758 changeViewPosition(mFgsSectionView, getChildCount() - offsetFromEnd++);
5759 }
5760 changeViewPosition(mFooterView, getChildCount() - offsetFromEnd++);
5761 changeViewPosition(mEmptyShadeView, getChildCount() - offsetFromEnd++);
Jason Monke59dc402018-08-16 12:05:01 -04005762
5763 // No post-increment for this call because it is the last one. Make sure to add one if
5764 // another "changeViewPosition" call is ever added.
5765 changeViewPosition(mShelf,
5766 getChildCount() - offsetFromEnd);
Jason Monke59dc402018-08-16 12:05:01 -04005767 }
5768
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005769 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dave Mankoffaf8163f2020-01-08 14:24:35 -05005770 public void setNotificationPanelController(
5771 NotificationPanelViewController notificationPanelViewController) {
5772 mNotificationPanelController = notificationPanelViewController;
Jason Monke59dc402018-08-16 12:05:01 -04005773 }
5774
Jason Monk297c04e2018-08-23 17:16:59 -04005775 public void updateIconAreaViews() {
5776 mIconAreaController.updateNotificationIcons();
5777 }
5778
Selim Cinek60ffea62018-03-22 13:16:44 -07005779 /**
Selim Cinek3d6ae232019-01-04 14:14:33 -08005780 * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
5781 * notification positions accordingly.
5782 * @param height the new wake up height
5783 * @return the overflow how much the height is further than he lowest notification
5784 */
Selim Cinek5040f2e2019-02-14 18:22:42 -08005785 public float setPulseHeight(float height) {
5786 mAmbientState.setPulseHeight(height);
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005787 if (mKeyguardBypassController.getBypassEnabled()) {
5788 notifyAppearChangedListeners();
5789 }
Selim Cinek3d6ae232019-01-04 14:14:33 -08005790 requestChildrenUpdate();
5791 return Math.max(0, height - mAmbientState.getInnerHeight(true /* ignorePulseHeight */));
5792 }
5793
Selim Cinekb0fada62019-06-17 19:03:59 -07005794 public float getPulseHeight() {
5795 return mAmbientState.getPulseHeight();
5796 }
5797
Selim Cinek3d6ae232019-01-04 14:14:33 -08005798 /**
Selim Cinek195dfc52019-05-30 19:35:05 -07005799 * 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 -08005800 * the notification is pulsing.
5801 */
5802 public void setDozeAmount(float dozeAmount) {
5803 mAmbientState.setDozeAmount(dozeAmount);
Selim Cinekae55d832019-02-22 17:43:43 -08005804 updateContinuousBackgroundDrawing();
Selim Cinek3d6ae232019-01-04 14:14:33 -08005805 requestChildrenUpdate();
5806 }
5807
Selim Cinek459aee32019-02-20 11:18:56 -08005808 public void wakeUpFromPulse() {
Selim Cinekb0fada62019-06-17 19:03:59 -07005809 setPulseHeight(getWakeUpHeight());
Selim Cinek459aee32019-02-20 11:18:56 -08005810 // Let's place the hidden views at the end of the pulsing notification to make sure we have
5811 // a smooth animation
5812 boolean firstVisibleView = true;
5813 float wakeUplocation = -1f;
5814 int childCount = getChildCount();
5815 for (int i = 0; i < childCount; i++) {
5816 ExpandableView view = (ExpandableView) getChildAt(i);
5817 if (view.getVisibility() == View.GONE) {
5818 continue;
5819 }
5820 boolean isShelf = view == mShelf;
5821 if (!(view instanceof ExpandableNotificationRow) && !isShelf) {
5822 continue;
5823 }
5824 if (view.getVisibility() == View.VISIBLE && !isShelf) {
5825 if (firstVisibleView) {
5826 firstVisibleView = false;
5827 wakeUplocation = view.getTranslationY()
5828 + view.getActualHeight() - mShelf.getIntrinsicHeight();
5829 }
5830 } else if (!firstVisibleView) {
5831 view.setTranslationY(wakeUplocation);
5832 }
5833 }
Selim Cinekf434a742019-05-28 17:39:49 -07005834 mDimmedNeedsAnimation = true;
Selim Cinek459aee32019-02-20 11:18:56 -08005835 }
5836
Selim Cinek6f0a62a2019-04-09 18:40:12 -07005837 @Override
5838 public void onDynamicPrivacyChanged() {
5839 if (mIsExpanded) {
5840 // The bottom might change because we're using the final actual height of the view
5841 mAnimateBottomOnLayout = true;
5842 }
Selim Cinekabcc2012019-07-23 18:44:07 -07005843 // Let's update the footer once the notifications have been updated (in the next frame)
Selim Cinek5454a0d2019-07-30 17:14:50 -07005844 post(() -> {
5845 updateFooter();
Steve Elliott1ba1b7c2020-05-05 14:16:32 -04005846 updateSectionBoundaries("dynamic privacy changed");
Selim Cinek5454a0d2019-07-30 17:14:50 -07005847 });
Selim Cinek6f0a62a2019-04-09 18:40:12 -07005848 }
5849
Selim Cinekb0fada62019-06-17 19:03:59 -07005850 public void setOnPulseHeightChangedListener(Runnable listener) {
5851 mAmbientState.setOnPulseHeightChangedListener(listener);
5852 }
5853
Selim Cinekc7e4cb52019-06-20 15:41:45 -07005854 public float calculateAppearFractionBypass() {
5855 float pulseHeight = getPulseHeight();
5856 float wakeUpHeight = getWakeUpHeight();
5857 float dragDownAmount = pulseHeight - wakeUpHeight;
5858
5859 // The total distance required to fully reveal the header
5860 float totalDistance = getIntrinsicPadding();
5861 return MathUtils.smoothStep(0, totalDistance, dragDownAmount);
5862 }
5863
Selim Cinek3d6ae232019-01-04 14:14:33 -08005864 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005865 * A listener that is notified when the empty space below the notifications is clicked on
5866 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005867 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005868 public interface OnEmptySpaceClickListener {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08005869 void onEmptySpaceClicked(float x, float y);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005870 }
5871
5872 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02005873 * A listener that gets notified when the overscroll at the top has changed.
5874 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005875 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi290600a2014-05-30 17:02:20 +02005876 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02005877
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005878 /**
5879 * Notifies a listener that the overscroll has changed.
5880 *
5881 * @param amount the amount of overscroll, in pixels
5882 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
5883 * unrubberbanded motion to directly expand overscroll view (e.g
5884 * expand
5885 * QS)
5886 */
5887 void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02005888
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005889 /**
5890 * Notify a listener that the scroller wants to escape from the scrolling motion and
5891 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
5892 *
5893 * @param velocity The velocity that the Scroller had when over flinging
5894 * @param open Should the fling open or close the overscroll view.
5895 */
5896 void flingTopOverscroll(float velocity, boolean open);
5897 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02005898
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005899 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005900 public void updateSpeedBumpIndex() {
Jason Monke59dc402018-08-16 12:05:01 -04005901 int speedBumpIndex = 0;
5902 int currentIndex = 0;
5903 final int N = getChildCount();
5904 for (int i = 0; i < N; i++) {
5905 View view = getChildAt(i);
5906 if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) {
5907 continue;
5908 }
5909 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
5910 currentIndex++;
Gus Prevas33619af2018-10-26 15:40:27 -04005911 boolean beforeSpeedBump;
Selim Cinekda330ff2019-06-11 15:41:28 -07005912 if (mHighPriorityBeforeSpeedBump) {
Evan Laird25f02752019-08-14 19:25:06 -04005913 beforeSpeedBump = row.getEntry().getBucket() < BUCKET_SILENT;
Selim Cinekda330ff2019-06-11 15:41:28 -07005914 } else {
Ned Burns60e94592019-09-06 14:47:25 -04005915 beforeSpeedBump = !row.getEntry().isAmbient();
Gus Prevas33619af2018-10-26 15:40:27 -04005916 }
5917 if (beforeSpeedBump) {
Jason Monke59dc402018-08-16 12:05:01 -04005918 speedBumpIndex = currentIndex;
5919 }
5920 }
5921 boolean noAmbient = speedBumpIndex == N;
5922 updateSpeedBumpIndex(speedBumpIndex, noAmbient);
5923 }
5924
Gus Prevase2d6f042018-10-17 15:25:30 -04005925 /** Updates the indices of the boundaries between sections. */
5926 @ShadeViewRefactor(RefactorComponent.INPUT)
Steve Elliott1ba1b7c2020-05-05 14:16:32 -04005927 public void updateSectionBoundaries(String reason) {
5928 mSectionsManager.updateSectionBoundaries(reason);
Gus Prevase2d6f042018-10-17 15:25:30 -04005929 }
5930
Selim Cinekae55d832019-02-22 17:43:43 -08005931 private void updateContinuousBackgroundDrawing() {
5932 boolean continuousBackground = !mAmbientState.isFullyAwake()
5933 && !mAmbientState.getDraggedViews().isEmpty();
5934 if (continuousBackground != mContinuousBackgroundUpdate) {
5935 mContinuousBackgroundUpdate = continuousBackground;
5936 if (continuousBackground) {
5937 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
5938 } else {
5939 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
5940 }
5941 }
5942 }
5943
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005944 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek33223572016-02-19 19:32:22 -08005945 private void updateContinuousShadowDrawing() {
5946 boolean continuousShadowUpdate = mAnimationRunning
5947 || !mAmbientState.getDraggedViews().isEmpty();
5948 if (continuousShadowUpdate != mContinuousShadowUpdate) {
5949 if (continuousShadowUpdate) {
5950 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
5951 } else {
5952 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
5953 }
Jorim Jaggi38b5ec92016-04-12 01:39:49 -07005954 mContinuousShadowUpdate = continuousShadowUpdate;
Selim Cinek33223572016-02-19 19:32:22 -08005955 }
5956 }
5957
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005958 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005959 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellor95d743c2017-01-10 12:05:27 -08005960 public void resetExposedMenuView(boolean animate, boolean force) {
5961 mSwipeHelper.resetExposedMenuView(animate, force);
Mady Mellor7a9b2a62016-03-23 07:41:47 -07005962 }
5963
Ned Burns61269442019-05-02 18:27:23 -04005964 private static boolean matchesSelection(
5965 ExpandableNotificationRow row,
5966 @SelectedRows int selection) {
5967 switch (selection) {
5968 case ROWS_ALL:
5969 return true;
5970 case ROWS_HIGH_PRIORITY:
Evan Laird25f02752019-08-14 19:25:06 -04005971 return row.getEntry().getBucket() < BUCKET_SILENT;
Ned Burns61269442019-05-02 18:27:23 -04005972 case ROWS_GENTLE:
Evan Laird25f02752019-08-14 19:25:06 -04005973 return row.getEntry().getBucket() == BUCKET_SILENT;
Ned Burns61269442019-05-02 18:27:23 -04005974 default:
5975 throw new IllegalArgumentException("Unknown selection: " + selection);
5976 }
5977 }
5978
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005979 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5980 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02005981
Jason Monke59dc402018-08-16 12:05:01 -04005982 static AnimationFilter[] FILTERS = new AnimationFilter[]{
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005983
5984 // ANIMATION_TYPE_ADD
5985 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005986 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005987 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005988 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005989 .animateZ()
5990 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005991
5992 // ANIMATION_TYPE_REMOVE
5993 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005994 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005995 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005996 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005997 .animateZ()
5998 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005999
6000 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
6001 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006002 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02006003 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006004 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006005 .animateZ()
6006 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006007
6008 // ANIMATION_TYPE_TOP_PADDING_CHANGED
6009 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006010 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02006011 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006012 .animateY()
6013 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006014 .animateZ(),
6015
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006016 // ANIMATION_TYPE_ACTIVATED_CHILD
6017 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08006018 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006019
6020 // ANIMATION_TYPE_DIMMED
6021 new AnimationFilter()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006022 .animateDimmed(),
6023
6024 // ANIMATION_TYPE_CHANGE_POSITION
6025 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08006026 .animateAlpha() // maybe the children change positions
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006027 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02006028 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006029 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04006030 .animateZ(),
6031
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006032 // ANIMATION_TYPE_GO_TO_FULL_SHADE
6033 new AnimationFilter()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006034 .animateHeight()
6035 .animateTopInset()
6036 .animateY()
6037 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02006038 .animateZ()
6039 .hasDelays(),
6040
6041 // ANIMATION_TYPE_HIDE_SENSITIVE
6042 new AnimationFilter()
6043 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02006044
6045 // ANIMATION_TYPE_VIEW_RESIZE
6046 new AnimationFilter()
Selim Cineka5e211b2014-08-11 17:35:48 +02006047 .animateHeight()
6048 .animateTopInset()
6049 .animateY()
6050 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02006051
Selim Cinekb5605e52015-02-20 18:21:41 +01006052 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
6053 new AnimationFilter()
6054 .animateAlpha()
6055 .animateHeight()
6056 .animateTopInset()
6057 .animateY()
6058 .animateZ(),
6059
Selim Cinekb8f09cf2015-03-16 17:09:28 -07006060 // ANIMATION_TYPE_HEADS_UP_APPEAR
6061 new AnimationFilter()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07006062 .animateHeight()
6063 .animateTopInset()
6064 .animateY()
6065 .animateZ(),
6066
6067 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
6068 new AnimationFilter()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07006069 .animateHeight()
6070 .animateTopInset()
6071 .animateY()
Selim Cinek332c23f2018-03-16 17:37:50 -07006072 .animateZ()
6073 .hasDelays(),
Selim Cinekb8f09cf2015-03-16 17:09:28 -07006074
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07006075 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
6076 new AnimationFilter()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07006077 .animateHeight()
6078 .animateTopInset()
6079 .animateY()
6080 .animateZ()
6081 .hasDelays(),
6082
Selim Cineka59ecc32015-04-07 10:51:49 -07006083 // ANIMATION_TYPE_HEADS_UP_OTHER
6084 new AnimationFilter()
Selim Cineka59ecc32015-04-07 10:51:49 -07006085 .animateHeight()
6086 .animateTopInset()
6087 .animateY()
6088 .animateZ(),
6089
Selim Cinekd9acca52014-09-01 22:33:25 +02006090 // ANIMATION_TYPE_EVERYTHING
6091 new AnimationFilter()
6092 .animateAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02006093 .animateDimmed()
6094 .animateHideSensitive()
6095 .animateHeight()
6096 .animateTopInset()
6097 .animateY()
6098 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006099 };
6100
Jason Monke59dc402018-08-16 12:05:01 -04006101 static int[] LENGTHS = new int[]{
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006102
6103 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006104 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006105
6106 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006107 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006108
6109 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
6110 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6111
6112 // ANIMATION_TYPE_TOP_PADDING_CHANGED
6113 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6114
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006115 // ANIMATION_TYPE_ACTIVATED_CHILD
6116 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
6117
6118 // ANIMATION_TYPE_DIMMED
6119 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006120
6121 // ANIMATION_TYPE_CHANGE_POSITION
6122 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04006123
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006124 // ANIMATION_TYPE_GO_TO_FULL_SHADE
6125 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02006126
6127 // ANIMATION_TYPE_HIDE_SENSITIVE
6128 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02006129
6130 // ANIMATION_TYPE_VIEW_RESIZE
6131 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02006132
Selim Cinekb5605e52015-02-20 18:21:41 +01006133 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08006134 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01006135
Selim Cinekb8f09cf2015-03-16 17:09:28 -07006136 // ANIMATION_TYPE_HEADS_UP_APPEAR
6137 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
6138
6139 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
6140 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6141
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07006142 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
6143 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6144
Selim Cineka59ecc32015-04-07 10:51:49 -07006145 // ANIMATION_TYPE_HEADS_UP_OTHER
6146 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6147
Selim Cinekd9acca52014-09-01 22:33:25 +02006148 // ANIMATION_TYPE_EVERYTHING
6149 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006150 };
6151
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006152 static final int ANIMATION_TYPE_ADD = 0;
6153 static final int ANIMATION_TYPE_REMOVE = 1;
6154 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
6155 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
Selim Cinekff2ffec2018-11-19 18:52:01 -08006156 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 4;
6157 static final int ANIMATION_TYPE_DIMMED = 5;
6158 static final int ANIMATION_TYPE_CHANGE_POSITION = 6;
Selim Cinekc3fec682019-06-06 18:11:07 -07006159 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 7;
6160 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 8;
6161 static final int ANIMATION_TYPE_VIEW_RESIZE = 9;
6162 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 10;
6163 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 11;
6164 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 12;
6165 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13;
6166 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14;
6167 static final int ANIMATION_TYPE_EVERYTHING = 15;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02006168
Selim Cinek572bbd42014-04-25 16:43:27 +02006169 final long eventStartTime;
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006170 final ExpandableView mChangingView;
Selim Cinek572bbd42014-04-25 16:43:27 +02006171 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006172 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006173 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006174 View viewAfterChangingView;
Selim Cineka59ecc32015-04-07 10:51:49 -07006175 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02006176
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006177 AnimationEvent(ExpandableView view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02006178 this(view, type, LENGTHS[type]);
6179 }
6180
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006181 AnimationEvent(ExpandableView view, int type, AnimationFilter filter) {
Adrian Roos28f90c72017-05-08 17:24:26 -07006182 this(view, type, LENGTHS[type], filter);
6183 }
6184
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006185 AnimationEvent(ExpandableView view, int type, long length) {
Adrian Roos28f90c72017-05-08 17:24:26 -07006186 this(view, type, length, FILTERS[type]);
6187 }
6188
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006189 AnimationEvent(ExpandableView view, int type, long length, AnimationFilter filter) {
Selim Cinek572bbd42014-04-25 16:43:27 +02006190 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006191 mChangingView = view;
Selim Cinek572bbd42014-04-25 16:43:27 +02006192 animationType = type;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02006193 this.length = length;
Adrian Roos28f90c72017-05-08 17:24:26 -07006194 this.filter = filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006195 }
6196
6197 /**
6198 * Combines the length of several animation events into a single value.
6199 *
6200 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006201 * @return The combined length. Depending on the event types, this might be the maximum of
Jason Monke59dc402018-08-16 12:05:01 -04006202 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006203 */
6204 static long combineLength(ArrayList<AnimationEvent> events) {
6205 long length = 0;
6206 int size = events.size();
6207 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006208 AnimationEvent event = events.get(i);
6209 length = Math.max(length, event.length);
6210 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
6211 return event.length;
6212 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006213 }
6214 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02006215 }
6216 }
Jason Monke59dc402018-08-16 12:05:01 -04006217
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006218 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
6219 private final StateListener mStateListener = new StateListener() {
Jason Monke59dc402018-08-16 12:05:01 -04006220 @Override
6221 public void onStatePreChange(int oldState, int newState) {
6222 if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) {
6223 requestAnimateEverything();
6224 }
6225 }
6226
6227 @Override
6228 public void onStateChanged(int newState) {
6229 setStatusBarState(newState);
6230 }
Evan Laird91d0f102018-09-18 17:39:55 -04006231
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006232 @Override
6233 public void onStatePostChange() {
Evan Laird91d0f102018-09-18 17:39:55 -04006234 NotificationStackScrollLayout.this.onStatePostChange();
6235 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006236 };
6237
Will Brockmane718d582019-01-17 16:38:38 -05006238 @VisibleForTesting
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006239 @ShadeViewRefactor(RefactorComponent.INPUT)
Will Brockmane718d582019-01-17 16:38:38 -05006240 protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006241 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006242 public void onMenuClicked(View view, int x, int y, MenuItem item) {
6243 if (mLongPressListener == null) {
6244 return;
6245 }
6246 if (view instanceof ExpandableNotificationRow) {
6247 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Ned Burns1c2b85a42019-11-14 15:37:03 -05006248 mMetricsLogger.write(row.getEntry().getSbn().getLogMaker()
Will Brockmane718d582019-01-17 16:38:38 -05006249 .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
6250 .setType(MetricsEvent.TYPE_ACTION)
6251 );
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006252 }
6253 mLongPressListener.onLongPress(view, x, y, item);
6254 }
6255
6256 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006257 public void onMenuReset(View row) {
6258 View translatingParentView = mSwipeHelper.getTranslatingParentView();
6259 if (translatingParentView != null && row == translatingParentView) {
6260 mSwipeHelper.clearExposedMenuView();
6261 mSwipeHelper.clearTranslatingParentView();
Gus Prevas211181532018-12-13 14:49:33 -05006262 if (row instanceof ExpandableNotificationRow) {
6263 mHeadsUpManager.setMenuShown(
6264 ((ExpandableNotificationRow) row).getEntry(), false);
6265
6266 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006267 }
6268 }
6269
6270 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006271 public void onMenuShown(View row) {
6272 if (row instanceof ExpandableNotificationRow) {
Gus Prevas211181532018-12-13 14:49:33 -05006273 ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
Ned Burns1c2b85a42019-11-14 15:37:03 -05006274 mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker()
Will Brockmane718d582019-01-17 16:38:38 -05006275 .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
6276 .setType(MetricsEvent.TYPE_ACTION));
Gus Prevas211181532018-12-13 14:49:33 -05006277 mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
Evan Lairde55c6012019-03-13 12:54:37 -04006278 mSwipeHelper.onMenuShown(row);
Steve Elliottff6c25e2019-05-30 17:38:52 -04006279 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6280 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6281 false /* resetMenu */);
Evan Lairde55c6012019-03-13 12:54:37 -04006282
6283 // Check to see if we want to go directly to the notfication guts
6284 NotificationMenuRowPlugin provider = notificationRow.getProvider();
6285 if (provider.shouldShowGutsOnSnapOpen()) {
6286 MenuItem item = provider.menuItemToExposeOnSnap();
6287 if (item != null) {
6288 Point origin = provider.getRevealAnimationOrigin();
Steve Elliottff6c25e2019-05-30 17:38:52 -04006289 mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
Evan Lairde55c6012019-03-13 12:54:37 -04006290 } else {
6291 Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
6292 + "menu item in menuItemtoExposeOnSnap. Skipping.");
6293 }
6294
6295 // Close the menu row since we went directly to the guts
6296 resetExposedMenuView(false, true);
6297 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006298 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006299 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006300 };
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006301
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006302 @ShadeViewRefactor(RefactorComponent.INPUT)
6303 private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
6304 new NotificationSwipeHelper.NotificationCallback() {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006305 @Override
6306 public void onDismiss() {
6307 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6308 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6309 false /* resetMenu */);
6310 }
6311
6312 @Override
6313 public void onSnooze(StatusBarNotification sbn,
6314 NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
6315 mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
6316 }
6317
6318 @Override
Julia Reynolds12ba4cf2020-01-10 16:01:38 -05006319 public void onSnooze(StatusBarNotification sbn, int hours) {
6320 mStatusBar.setNotificationSnoozed(sbn, hours);
6321 }
6322
6323 @Override
Selim Cinekae55d832019-02-22 17:43:43 -08006324 public boolean shouldDismissQuickly() {
6325 return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006326 }
6327
6328 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006329 public void onDragCancelled(View v) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006330 setSwipingInProgress(false);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006331 mFalsingManager.onNotificatonStopDismissing();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006332 }
6333
6334 /**
6335 * Handles cleanup after the given {@code view} has been fully swiped out (including
6336 * re-invoking dismiss logic in case the notification has not made its way out yet).
6337 */
6338 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006339 public void onChildDismissed(View view) {
Steve Elliott946b00c2019-10-28 11:20:24 -04006340 if (!(view instanceof ActivatableNotificationView)) {
6341 return;
6342 }
6343 ActivatableNotificationView row = (ActivatableNotificationView) view;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006344 if (!row.isDismissed()) {
6345 handleChildViewDismissed(view);
6346 }
6347 ViewGroup transientContainer = row.getTransientContainer();
6348 if (transientContainer != null) {
6349 transientContainer.removeTransientView(view);
6350 }
6351 }
6352
6353 /**
6354 * Starts up notification dismiss and tells the notification, if any, to remove itself from
6355 * layout.
6356 *
6357 * @param view view (e.g. notification) to dismiss from the layout
6358 */
6359
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006360 public void handleChildViewDismissed(View view) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006361 setSwipingInProgress(false);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006362 if (mDismissAllInProgress) {
6363 return;
6364 }
6365
6366 boolean isBlockingHelperShown = false;
6367
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006368 mAmbientState.onDragFinished(view);
6369 updateContinuousShadowDrawing();
6370
6371 if (view instanceof ExpandableNotificationRow) {
6372 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6373 if (row.isHeadsUp()) {
6374 mHeadsUpManager.addSwipedOutNotification(
Ned Burns1c2b85a42019-11-14 15:37:03 -05006375 row.getEntry().getSbn().getKey());
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006376 }
6377 isBlockingHelperShown =
6378 row.performDismissWithBlockingHelper(false /* fromAccessibility */);
6379 }
6380
Steve Elliott946b00c2019-10-28 11:20:24 -04006381 if (view instanceof PeopleHubView) {
Steve Elliott946b00c2019-10-28 11:20:24 -04006382 mSectionsManager.hidePeopleRow();
6383 }
6384
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006385 if (!isBlockingHelperShown) {
6386 mSwipedOutViews.add(view);
6387 }
6388 mFalsingManager.onNotificationDismissed();
6389 if (mFalsingManager.shouldEnforceBouncer()) {
6390 mStatusBar.executeRunnableDismissingKeyguard(
6391 null,
6392 null /* cancelAction */,
6393 false /* dismissShade */,
6394 true /* afterKeyguardGone */,
6395 false /* deferred */);
6396 }
6397 }
6398
6399 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006400 public boolean isAntiFalsingNeeded() {
6401 return onKeyguard();
6402 }
6403
6404 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006405 public View getChildAtPosition(MotionEvent ev) {
Steve Elliottb0940382020-02-20 14:24:02 -05006406 View child = NotificationStackScrollLayout.this.getChildAtPosition(
6407 ev.getX(),
6408 ev.getY(),
6409 true /* requireMinHeight */,
6410 false /* ignoreDecors */);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006411 if (child instanceof ExpandableNotificationRow) {
6412 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
6413 ExpandableNotificationRow parent = row.getNotificationParent();
6414 if (parent != null && parent.areChildrenExpanded()
6415 && (parent.areGutsExposed()
6416 || mSwipeHelper.getExposedMenuView() == parent
Kevin Han43077f92020-02-28 12:51:53 -08006417 || (parent.getAttachedChildren().size() == 1
Evan Laird94492852018-10-25 13:43:01 -04006418 && parent.getEntry().isClearable()))) {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006419 // In this case the group is expanded and showing the menu for the
6420 // group, further interaction should apply to the group, not any
6421 // child notifications so we use the parent of the child. We also do the same
6422 // if we only have a single child.
6423 child = parent;
6424 }
6425 }
6426 return child;
6427 }
6428
6429 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006430 public void onBeginDrag(View v) {
6431 mFalsingManager.onNotificatonStartDismissing();
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006432 setSwipingInProgress(true);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006433 mAmbientState.onBeginDrag((ExpandableView) v);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006434 updateContinuousShadowDrawing();
Selim Cinekae55d832019-02-22 17:43:43 -08006435 updateContinuousBackgroundDrawing();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006436 requestChildrenUpdate();
6437 }
6438
6439 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006440 public void onChildSnappedBack(View animView, float targetLeft) {
6441 mAmbientState.onDragFinished(animView);
6442 updateContinuousShadowDrawing();
Selim Cinekae55d832019-02-22 17:43:43 -08006443 updateContinuousBackgroundDrawing();
Selim Cineke3c6e462019-06-24 19:37:06 -07006444 if (animView instanceof ExpandableNotificationRow) {
6445 ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
6446 if (row.isPinned() && !canChildBeDismissed(row)
Ned Burns1c2b85a42019-11-14 15:37:03 -05006447 && row.getEntry().getSbn().getNotification().fullScreenIntent
Selim Cineke3c6e462019-06-24 19:37:06 -07006448 == null) {
Ned Burns1c2b85a42019-11-14 15:37:03 -05006449 mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(),
Selim Cineke3c6e462019-06-24 19:37:06 -07006450 true /* removeImmediately */);
6451 }
6452 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006453 }
6454
6455 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006456 public boolean updateSwipeProgress(View animView, boolean dismissable,
6457 float swipeProgress) {
6458 // Returning true prevents alpha fading.
6459 return !mFadeNotificationsOnDismiss;
6460 }
6461
6462 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006463 public float getFalsingThresholdFactor() {
6464 return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
6465 }
6466
6467 @Override
Gus Prevasc4e68d42019-01-17 15:45:21 -05006468 public int getConstrainSwipeStartPosition() {
6469 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
6470 if (menuRow != null) {
6471 return Math.abs(menuRow.getMenuSnapTarget());
6472 }
6473 return 0;
6474 }
6475
Steve Elliott946b00c2019-10-28 11:20:24 -04006476 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006477 public boolean canChildBeDismissed(View v) {
Steve Elliott946b00c2019-10-28 11:20:24 -04006478 return NotificationStackScrollLayout.canChildBeDismissed(v);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006479 }
Gus Prevas37d67e22018-11-02 14:48:55 -04006480
6481 @Override
6482 public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
Evan Laird30b9b162019-04-24 15:22:24 -04006483 //TODO: b/131242807 for why this doesn't do anything with direction
6484 return canChildBeDismissed(v);
Gus Prevas37d67e22018-11-02 14:48:55 -04006485 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006486 };
6487
Steve Elliott946b00c2019-10-28 11:20:24 -04006488 private static boolean canChildBeDismissed(View v) {
6489 if (v instanceof ExpandableNotificationRow) {
6490 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
6491 if (row.isBlockingHelperShowingAndTranslationFinished()) {
6492 return true;
6493 }
6494 if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
6495 return false;
6496 }
6497 return row.canViewBeDismissed();
6498 }
6499 if (v instanceof PeopleHubView) {
6500 return true;
6501 }
6502 return false;
6503 }
6504
Beverly7e0d6492020-02-07 16:22:14 -05006505 // --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
6506
6507 private void onEntryUpdated(NotificationEntry entry) {
6508 // If the row already exists, the user may have performed a dismiss action on the
6509 // notification. Since it's not clearable we should snap it back.
6510 if (entry.rowExists() && !entry.getSbn().isClearable()) {
6511 snapViewIfNeeded(entry);
6512 }
6513 }
6514
6515 private boolean hasActiveNotifications() {
6516 if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
Kevin Han4943b862020-03-23 17:14:39 -07006517 return !mNotifPipeline.getShadeList().isEmpty();
Beverly7e0d6492020-02-07 16:22:14 -05006518 } else {
6519 return mEntryManager.hasActiveNotifications();
6520 }
6521 }
6522
6523 /**
6524 * Called after the animations for a "clear all notifications" action has ended.
6525 */
6526 private void onDismissAllAnimationsEnd(
6527 List<ExpandableNotificationRow> viewsToRemove,
6528 @SelectedRows int selectedRows) {
6529 if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
6530 if (selectedRows == ROWS_ALL) {
6531 mNotifCollection.dismissAllNotifications(mLockscreenUserManager.getCurrentUserId());
6532 } else {
6533 final List<Pair<NotificationEntry, DismissedByUserStats>>
6534 entriesWithRowsDismissedFromShade = new ArrayList<>();
6535 final List<DismissedByUserStats> dismissalUserStats = new ArrayList<>();
6536 final int numVisibleEntries = mNotifPipeline.getShadeListCount();
6537 for (int i = 0; i < viewsToRemove.size(); i++) {
6538 final NotificationEntry entry = viewsToRemove.get(i).getEntry();
6539 final DismissedByUserStats stats =
6540 new DismissedByUserStats(
6541 DISMISSAL_SHADE,
6542 DISMISS_SENTIMENT_NEUTRAL,
6543 NotificationVisibility.obtain(
6544 entry.getKey(),
6545 entry.getRanking().getRank(),
6546 numVisibleEntries,
6547 true,
6548 NotificationLogger.getNotificationLocation(entry)));
6549 entriesWithRowsDismissedFromShade.add(
6550 new Pair<NotificationEntry, DismissedByUserStats>(entry, stats));
6551 }
6552 mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
6553 }
6554 } else {
6555 for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
6556 if (canChildBeDismissed(rowToRemove)) {
6557 if (selectedRows == ROWS_ALL) {
6558 // TODO: This is a listener method; we shouldn't be calling it. Can we just
6559 // call performRemoveNotification as below?
6560 mEntryManager.removeNotification(
6561 rowToRemove.getEntry().getKey(),
6562 null /* ranking */,
6563 NotificationListenerService.REASON_CANCEL_ALL);
6564 } else {
6565 mEntryManager.performRemoveNotification(
6566 rowToRemove.getEntry().getSbn(),
6567 NotificationListenerService.REASON_CANCEL_ALL);
6568 }
6569 } else {
6570 rowToRemove.resetTranslation();
6571 }
6572 }
6573 if (selectedRows == ROWS_ALL) {
6574 try {
6575 mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
6576 } catch (Exception ex) {
6577 }
6578 }
6579 }
6580 }
6581
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006582 // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
6583
6584 @ShadeViewRefactor(RefactorComponent.INPUT)
6585 private final DragDownCallback mDragDownCallback = new DragDownCallback() {
6586
6587 /* Only ever called as a consequence of a lockscreen expansion gesture. */
6588 @Override
6589 public boolean onDraggedDown(View startingChild, int dragLengthY) {
Beverly7e0d6492020-02-07 16:22:14 -05006590 if (mStatusBarState == StatusBarState.KEYGUARD && hasActiveNotifications()) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006591 mLockscreenGestureLogger.write(
6592 MetricsEvent.ACTION_LS_SHADE,
6593 (int) (dragLengthY / mDisplayMetrics.density),
6594 0 /* velocityDp - N/A */);
6595
Selim Cinekc1d9ab22019-05-21 18:08:30 -07006596 if (!mAmbientState.isDozing() || startingChild != null) {
Lucas Dupin55c6e802018-09-27 18:07:36 -07006597 // We have notifications, go to locked shade.
Dave Mankoffaf8163f2020-01-08 14:24:35 -05006598 Dependency.get(ShadeController.class).goToLockedShade(startingChild);
Lucas Dupin55c6e802018-09-27 18:07:36 -07006599 if (startingChild instanceof ExpandableNotificationRow) {
6600 ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
6601 row.onExpandedByGesture(true /* drag down is always an open */);
6602 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006603 }
Lucas Dupin55c6e802018-09-27 18:07:36 -07006604
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006605 return true;
Selim Cinek5454a0d2019-07-30 17:14:50 -07006606 } else if (mDynamicPrivacyController.isInLockedDownShade()) {
6607 mStatusbarStateController.setLeaveOpenOnKeyguardHide(true);
6608 mStatusBar.dismissKeyguardThenExecute(() -> false /* dismissAction */,
6609 null /* cancelRunnable */, false /* afterKeyguardGone */);
6610 return true;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006611 } else {
6612 // abort gesture.
6613 return false;
6614 }
6615 }
6616
6617 @Override
6618 public void onDragDownReset() {
6619 setDimmed(true /* dimmed */, true /* animated */);
6620 resetScrollPosition();
6621 resetCheckSnoozeLeavebehind();
6622 }
6623
6624 @Override
6625 public void onCrossedThreshold(boolean above) {
6626 setDimmed(!above /* dimmed */, true /* animate */);
6627 }
6628
6629 @Override
6630 public void onTouchSlopExceeded() {
6631 cancelLongPress();
6632 checkSnoozeLeavebehind();
6633 }
6634
6635 @Override
6636 public void setEmptyDragAmount(float amount) {
Dave Mankoffaf8163f2020-01-08 14:24:35 -05006637 mNotificationPanelController.setEmptyDragAmount(amount);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006638 }
6639
6640 @Override
6641 public boolean isFalsingCheckNeeded() {
6642 return mStatusBarState == StatusBarState.KEYGUARD;
6643 }
Selim Cinek5454a0d2019-07-30 17:14:50 -07006644
6645 @Override
6646 public boolean isDragDownEnabledForView(ExpandableView view) {
6647 if (isDragDownAnywhereEnabled()) {
6648 return true;
6649 }
6650 if (mDynamicPrivacyController.isInLockedDownShade()) {
6651 if (view == null) {
6652 // Dragging down is allowed in general
6653 return true;
6654 }
6655 if (view instanceof ExpandableNotificationRow) {
6656 // Only drag down on sensitive views, otherwise the ExpandHelper will take this
6657 return ((ExpandableNotificationRow) view).getEntry().isSensitive();
6658 }
6659 }
6660 return false;
6661 }
6662
6663 @Override
6664 public boolean isDragDownAnywhereEnabled() {
6665 return mStatusbarStateController.getState() == StatusBarState.KEYGUARD
6666 && !mKeyguardBypassController.getBypassEnabled();
6667 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006668 };
6669
6670 public DragDownCallback getDragDownCallback() { return mDragDownCallback; }
6671
6672 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6673 private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() {
6674 @Override
6675 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6676 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6677 }
6678
6679 @Override
6680 public boolean isExpanded() {
6681 return mIsExpanded;
6682 }
6683
6684 @Override
6685 public Context getContext() {
6686 return mContext;
6687 }
6688 };
6689
6690 public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
6691
6692
6693 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6694 private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
6695 @Override
6696 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
6697 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
6698 && (mIsExpanded || changedRow.isPinned());
6699 if (animated) {
6700 mExpandedGroupView = changedRow;
6701 mNeedsAnimation = true;
6702 }
6703 changedRow.setChildrenExpanded(expanded, animated);
6704 if (!mGroupExpandedForMeasure) {
6705 onHeightChanged(changedRow, false /* needsAnimation */);
6706 }
6707 runAfterAnimationFinished(new Runnable() {
6708 @Override
6709 public void run() {
6710 changedRow.onFinishedExpansionChange();
6711 }
6712 });
6713 }
6714
6715 @Override
6716 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Beverly85d4c192019-09-30 11:40:39 -04006717 mStatusBar.requestNotificationUpdate("onGroupCreatedFromChildren");
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006718 }
6719
6720 @Override
6721 public void onGroupsChanged() {
Beverly85d4c192019-09-30 11:40:39 -04006722 mStatusBar.requestNotificationUpdate("onGroupsChanged");
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006723 }
6724 };
6725
6726 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6727 private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
6728 @Override
6729 public ExpandableView getChildAtPosition(float touchX, float touchY) {
6730 return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY);
6731 }
6732
6733 @Override
6734 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6735 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6736 }
6737
6738 @Override
6739 public boolean canChildBeExpanded(View v) {
6740 return v instanceof ExpandableNotificationRow
6741 && ((ExpandableNotificationRow) v).isExpandable()
6742 && !((ExpandableNotificationRow) v).areGutsExposed()
6743 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
6744 }
6745
6746 /* Only ever called as a consequence of an expansion gesture in the shade. */
6747 @Override
6748 public void setUserExpandedChild(View v, boolean userExpanded) {
6749 if (v instanceof ExpandableNotificationRow) {
6750 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
6751 if (userExpanded && onKeyguard()) {
6752 // Due to a race when locking the screen while touching, a notification may be
6753 // expanded even after we went back to keyguard. An example of this happens if
6754 // you click in the empty space while expanding a group.
6755
6756 // We also need to un-user lock it here, since otherwise the content height
6757 // calculated might be wrong. We also can't invert the two calls since
6758 // un-userlocking it will trigger a layout switch in the content view.
6759 row.setUserLocked(false);
6760 updateContentHeight();
6761 notifyHeightChangeListener(row);
6762 return;
6763 }
6764 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
6765 row.onExpandedByGesture(userExpanded);
6766 }
6767 }
6768
6769 @Override
6770 public void setExpansionCancelled(View v) {
6771 if (v instanceof ExpandableNotificationRow) {
6772 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
6773 }
6774 }
6775
6776 @Override
6777 public void setUserLockedChild(View v, boolean userLocked) {
6778 if (v instanceof ExpandableNotificationRow) {
6779 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
6780 }
6781 cancelLongPress();
6782 requestDisallowInterceptTouchEvent(true);
6783 }
6784
6785 @Override
6786 public void expansionStateChanged(boolean isExpanding) {
6787 mExpandingNotification = isExpanding;
6788 if (!mExpandedInThisMotion) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05006789 if (ANCHOR_SCROLLING) {
6790 // TODO
6791 } else {
6792 mMaxScrollAfterExpand = mOwnScrollY;
6793 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006794 mExpandedInThisMotion = true;
6795 }
6796 }
6797
6798 @Override
6799 public int getMaxExpandHeight(ExpandableView view) {
6800 return view.getMaxContentHeight();
6801 }
6802 };
6803
6804 public ExpandHelper.Callback getExpandHelperCallback() {
6805 return mExpandHelperCallback;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006806 }
Ned Burns61269442019-05-02 18:27:23 -04006807
6808 /** Enum for selecting some or all notification rows (does not included non-notif views). */
6809 @Retention(SOURCE)
6810 @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
6811 public @interface SelectedRows {}
6812 /** All rows representing notifs. */
6813 public static final int ROWS_ALL = 0;
6814 /** Only rows where entry.isHighPriority() is true. */
6815 public static final int ROWS_HIGH_PRIORITY = 1;
6816 /** Only rows where entry.isHighPriority() is false. */
6817 public static final int ROWS_GENTLE = 2;
Will Brockmanb65faa82020-03-06 10:26:04 -05006818
6819 /**
6820 * Enum for UiEvent logged from this class
6821 */
6822 enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
6823 INVALID(0),
6824 @UiEvent(doc = "User dismissed all notifications from notification panel.")
6825 DISMISS_ALL_NOTIFICATIONS_PANEL(312),
6826 @UiEvent(doc = "User dismissed all silent notifications from notification panel.")
6827 DISMISS_SILENT_NOTIFICATIONS_PANEL(314);
6828 private final int mId;
6829 NotificationPanelEvent(int id) {
6830 mId = id;
6831 }
6832 @Override public int getId() {
6833 return mId;
6834 }
6835
6836 public static UiEventLogger.UiEventEnum fromSelection(@SelectedRows int selection) {
6837 if (selection == ROWS_ALL) {
6838 return DISMISS_ALL_NOTIFICATIONS_PANEL;
6839 }
6840 if (selection == ROWS_GENTLE) {
6841 return DISMISS_SILENT_NOTIFICATIONS_PANEL;
6842 }
6843 if (NotificationStackScrollLayout.DEBUG) {
6844 throw new IllegalArgumentException("Unexpected selection" + selection);
6845 }
6846 return INVALID;
6847 }
6848 }
Selim Cinek67b22602014-03-10 15:40:16 +01006849}