blob: 8fe34180203f34e065c8963aa3ac77eb18a9a2a5 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.stack;
Selim Cinek67b22602014-03-10 15:40:16 +010018
Gus Prevas59ec2ff2018-12-28 16:20:28 -050019import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
Gus Prevase83700cb2018-12-14 11:42:51 -050020import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
Gus Prevas0fa58d62019-01-11 13:58:40 -050021import static com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.ANCHOR_SCROLLING;
Gus Prevase83700cb2018-12-14 11:42:51 -050022import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
Selim Cinek3c9b0542019-06-11 15:41:28 -070023import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
Gus Prevas59ec2ff2018-12-28 16:20:28 -050024import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
Selim Cinek2627d722018-01-19 12:16:49 -080025
Ned Burns61269442019-05-02 18:27:23 -040026import static java.lang.annotation.RetentionPolicy.SOURCE;
27
Selim Cinek614576e2016-01-20 10:54:09 -080028import android.animation.Animator;
29import android.animation.AnimatorListenerAdapter;
Selim Cinekd35c2792016-01-21 13:20:57 -080030import android.animation.TimeAnimator;
31import android.animation.ValueAnimator;
Ned Burns61269442019-05-02 18:27:23 -040032import android.annotation.IntDef;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040033import android.annotation.NonNull;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010034import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010035import android.content.Context;
Jason Monke59dc402018-08-16 12:05:01 -040036import android.content.Intent;
Selim Cinek67b22602014-03-10 15:40:16 +010037import android.content.res.Configuration;
Anthony Chen3cb3ad92016-12-01 10:58:47 -080038import android.content.res.Resources;
Selim Cinek67b22602014-03-10 15:40:16 +010039import android.graphics.Canvas;
Lucas Dupind285cf02018-01-18 09:18:23 -080040import android.graphics.Color;
Lucas Dupin64e2f572019-03-21 14:21:14 -070041import android.graphics.Outline;
Selim Cinek67b22602014-03-10 15:40:16 +010042import android.graphics.Paint;
Evan Lairde55c6012019-03-13 12:54:37 -040043import android.graphics.Point;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010044import android.graphics.PointF;
Selim Cinek6811d722016-01-19 17:53:12 -080045import android.graphics.PorterDuff;
46import android.graphics.PorterDuffXfermode;
47import android.graphics.Rect;
Selim Cinekc22fff62016-05-20 12:44:30 -070048import android.os.Bundle;
Jason Monke59dc402018-08-16 12:05:01 -040049import android.os.ServiceManager;
50import android.provider.Settings;
Mady Mellorc2ff0112019-03-28 14:18:06 -070051import android.service.notification.NotificationListenerService;
Mady Mellor95d743c2017-01-10 12:05:27 -080052import android.service.notification.StatusBarNotification;
Selim Cinek67b22602014-03-10 15:40:16 +010053import android.util.AttributeSet;
Jason Monke59dc402018-08-16 12:05:01 -040054import android.util.DisplayMetrics;
Selim Cinek67b22602014-03-10 15:40:16 +010055import android.util.Log;
Lucas Dupind285cf02018-01-18 09:18:23 -080056import android.util.MathUtils;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070057import android.util.Pair;
Lucas Dupine17ce522017-07-17 15:45:06 -070058import android.view.ContextThemeWrapper;
Selim Cinek11e33232016-08-05 15:30:53 -070059import android.view.InputDevice;
Jason Monke59dc402018-08-16 12:05:01 -040060import android.view.LayoutInflater;
Selim Cinek67b22602014-03-10 15:40:16 +010061import android.view.MotionEvent;
62import android.view.VelocityTracker;
63import android.view.View;
64import android.view.ViewConfiguration;
65import android.view.ViewGroup;
Lucas Dupin64e2f572019-03-21 14:21:14 -070066import android.view.ViewOutlineProvider;
Selim Cinek343e6e22014-04-11 21:23:30 +020067import android.view.ViewTreeObserver;
Adrian Roos5153d4a2016-03-22 10:01:56 -070068import android.view.WindowInsets;
Selim Cinekc22fff62016-05-20 12:44:30 -070069import android.view.accessibility.AccessibilityEvent;
70import android.view.accessibility.AccessibilityNodeInfo;
Selim Cinek572bbd42014-04-25 16:43:27 +020071import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080072import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010073import android.widget.OverScroller;
Selim Cinek41fe89a2016-06-02 15:27:56 -070074import android.widget.ScrollView;
Lucas Dupin8da8f2e92017-04-21 14:02:16 -070075
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040076import com.android.internal.annotations.VisibleForTesting;
77import com.android.internal.graphics.ColorUtils;
Mady Mellora41587b2016-02-11 18:43:06 -080078import com.android.internal.logging.MetricsLogger;
Tamas Berghammer383db5eb2016-06-22 15:21:38 +010079import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Jason Monke59dc402018-08-16 12:05:01 -040080import com.android.internal.statusbar.IStatusBarService;
Lucas Dupin4e023812018-04-02 21:19:23 -070081import com.android.keyguard.KeyguardSliceView;
Lucas Dupine17ce522017-07-17 15:45:06 -070082import com.android.settingslib.Utils;
Rohan Shah524cf7b2018-03-15 14:40:02 -070083import com.android.systemui.Dependency;
Jason Monke59dc402018-08-16 12:05:01 -040084import com.android.systemui.Dumpable;
Selim Cinek67b22602014-03-10 15:40:16 +010085import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080086import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010087import com.android.systemui.R;
88import com.android.systemui.SwipeHelper;
Dave Mankoffdde5ee62019-05-02 17:36:11 -040089import com.android.systemui.classifier.FalsingManagerFactory;
Jason Monke59dc402018-08-16 12:05:01 -040090import com.android.systemui.colorextraction.SysuiColorExtractor;
Ned Burns9eb06332019-04-23 16:02:12 -040091import com.android.systemui.plugins.ActivityStarter;
Dave Mankoff468d4f62019-05-08 14:56:29 -040092import com.android.systemui.plugins.FalsingManager;
Mady Mellor95d743c2017-01-10 12:05:27 -080093import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
94import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -040095import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
Mady Mellor95d743c2017-01-10 12:05:27 -080096import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
Beverly8fdb5332019-02-04 14:29:49 -050097import com.android.systemui.plugins.statusbar.StatusBarStateController;
98import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
Selim Cinek3d6ae232019-01-04 14:14:33 -080099import com.android.systemui.statusbar.AmbientPulseManager;
Jason Monke59dc402018-08-16 12:05:01 -0400100import com.android.systemui.statusbar.CommandQueue;
101import com.android.systemui.statusbar.DragDownHelper.DragDownCallback;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200102import com.android.systemui.statusbar.EmptyShadeView;
Jason Monk297c04e2018-08-23 17:16:59 -0400103import com.android.systemui.statusbar.NotificationLockscreenUserManager;
104import com.android.systemui.statusbar.NotificationRemoteInputManager;
105import com.android.systemui.statusbar.NotificationShelf;
106import com.android.systemui.statusbar.RemoteInputController;
107import com.android.systemui.statusbar.StatusBarState;
Beverly8fdb5332019-02-04 14:29:49 -0500108import com.android.systemui.statusbar.SysuiStatusBarStateController;
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700109import com.android.systemui.statusbar.notification.DynamicPrivacyController;
Jason Monk297c04e2018-08-23 17:16:59 -0400110import com.android.systemui.statusbar.notification.FakeShadowView;
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500111import com.android.systemui.statusbar.notification.NotificationEntryListener;
Jason Monk297c04e2018-08-23 17:16:59 -0400112import com.android.systemui.statusbar.notification.NotificationEntryManager;
113import com.android.systemui.statusbar.notification.NotificationUtils;
114import com.android.systemui.statusbar.notification.ShadeViewRefactor;
115import com.android.systemui.statusbar.notification.ShadeViewRefactor.RefactorComponent;
Jason Monk297c04e2018-08-23 17:16:59 -0400116import com.android.systemui.statusbar.notification.VisualStabilityManager;
Ned Burnsf81c4c42019-01-07 14:10:43 -0500117import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Jason Monk297c04e2018-08-23 17:16:59 -0400118import com.android.systemui.statusbar.notification.logging.NotificationLogger;
119import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
Rohan Shah20790b82018-07-02 17:21:04 -0700120import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
121import com.android.systemui.statusbar.notification.row.ExpandableView;
122import com.android.systemui.statusbar.notification.row.FooterView;
123import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
Rohan Shah20790b82018-07-02 17:21:04 -0700124import com.android.systemui.statusbar.notification.row.NotificationGuts;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400125import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
Rohan Shah20790b82018-07-02 17:21:04 -0700126import com.android.systemui.statusbar.notification.row.NotificationSnooze;
127import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800128import com.android.systemui.statusbar.phone.DozeParameters;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700129import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900130import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400131import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
Jason Monke59dc402018-08-16 12:05:01 -0400132import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
Selim Cinekb5605e52015-02-20 18:21:41 +0100133import com.android.systemui.statusbar.phone.NotificationGroupManager;
Jason Monke59dc402018-08-16 12:05:01 -0400134import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700135import com.android.systemui.statusbar.phone.NotificationIconAreaController;
Jason Monke59dc402018-08-16 12:05:01 -0400136import com.android.systemui.statusbar.phone.NotificationPanelView;
Selim Cinekaac93252015-04-14 20:04:12 -0700137import com.android.systemui.statusbar.phone.ScrimController;
Jason Monk297c04e2018-08-23 17:16:59 -0400138import com.android.systemui.statusbar.phone.ShadeController;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800139import com.android.systemui.statusbar.phone.StatusBar;
Jason Monke59dc402018-08-16 12:05:01 -0400140import com.android.systemui.statusbar.policy.ConfigurationController;
141import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900142import com.android.systemui.statusbar.policy.HeadsUpUtil;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100143import com.android.systemui.statusbar.policy.ScrollAdapter;
Gus Prevas33619af2018-10-26 15:40:27 -0400144import com.android.systemui.tuner.TunerService;
Ned Burnsd4a69f72019-06-19 19:49:19 -0400145import com.android.systemui.util.Assert;
Selim Cinek67b22602014-03-10 15:40:16 +0100146
Selim Cinek707e2072017-06-30 18:32:40 +0200147import java.io.FileDescriptor;
148import java.io.PrintWriter;
Ned Burns61269442019-05-02 18:27:23 -0400149import java.lang.annotation.Retention;
Selim Cinek572bbd42014-04-25 16:43:27 +0200150import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -0800151import java.util.Collections;
152import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200153import java.util.HashSet;
Selim Cinekef8c2252017-02-10 14:52:18 -0800154import java.util.List;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800155import java.util.function.BiConsumer;
Selim Cinek572bbd42014-04-25 16:43:27 +0200156
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500157import javax.inject.Inject;
158import javax.inject.Named;
159
Selim Cinek67b22602014-03-10 15:40:16 +0100160/**
161 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
162 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400163public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700164 NotificationListContainer, ConfigurationListener, Dumpable,
165 DynamicPrivacyController.Listener {
Selim Cinek67b22602014-03-10 15:40:16 +0100166
Selim Cinekd35c2792016-01-21 13:20:57 -0800167 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -0800168 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +0100169 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200170 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
171 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200172 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +0100173 /**
174 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
175 */
Lucas Dupind285cf02018-01-18 09:18:23 -0800176 private static final int INVALID_POINTER = -1;
Gus Prevase2d6f042018-10-17 15:25:30 -0400177 static final int NUM_SECTIONS = 2;
178 /**
179 * The distance in pixels between sections when the sections are directly adjacent (no visible
180 * gap is drawn between them). In this case we don't want to round their corners.
181 */
182 private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
Selim Cinek3d6ae232019-01-04 14:14:33 -0800183 private final AmbientPulseManager mAmbientPulseManager;
Selim Cinek67b22602014-03-10 15:40:16 +0100184
Selim Cinek1408eb52014-06-02 14:45:38 +0200185 private ExpandHelper mExpandHelper;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400186 private final NotificationSwipeHelper mSwipeHelper;
Selim Cinek67b22602014-03-10 15:40:16 +0100187 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800188 private final Paint mBackgroundPaint = new Paint();
Adrian Roosf0b4f962017-05-25 11:53:11 -0700189 private final boolean mShouldDrawNotificationBackground;
Selim Cinek3c9b0542019-06-11 15:41:28 -0700190 private boolean mHighPriorityBeforeSpeedBump;
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500191 private final boolean mAllowLongPress;
Gus Prevasa18dc572019-01-14 16:11:22 -0500192 private boolean mDismissRtl;
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100193
Selim Cinekbc243a92016-09-27 16:35:13 -0700194 private float mExpandedHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100195 private int mOwnScrollY;
Gus Prevas0fa58d62019-01-11 13:58:40 -0500196 private View mScrollAnchorView;
197 private int mScrollAnchorViewY;
Selim Cinek67b22602014-03-10 15:40:16 +0100198 private int mMaxLayoutHeight;
199
200 private VelocityTracker mVelocityTracker;
201 private OverScroller mScroller;
Gus Prevascdc98342019-01-14 14:29:44 -0500202 /** Last Y position reported by {@link #mScroller}, used to calculate scroll delta. */
203 private int mLastScrollerY;
204 /**
205 * True if the max position was set to a known position on the last call to {@link #mScroller}.
206 */
207 private boolean mIsScrollerBoundSet;
Ricky Waicd35def2016-05-03 11:07:07 +0100208 private Runnable mFinishScrollingCallback;
Selim Cinek67b22602014-03-10 15:40:16 +0100209 private int mTouchSlop;
210 private int mMinimumVelocity;
211 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100212 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200213 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100214 private boolean mIsBeingDragged;
215 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200216 private int mDownX;
Dong-wan Kimb9266662016-09-21 13:08:30 -0700217 private int mActivePointerId = INVALID_POINTER;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100218 private boolean mTouchIsClick;
219 private float mInitialTouchX;
220 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100221
Selim Cinek67b22602014-03-10 15:40:16 +0100222 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100223 private int mContentHeight;
Lucas Dupin60661a62018-04-12 10:50:13 -0700224 private int mIntrinsicContentHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100225 private int mCollapsedSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100226 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800227 private int mIncreasedPaddingBetweenElements;
shawnlin8e4e92c2018-04-12 18:47:24 +0800228 private int mMaxTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200229 private int mTopPadding;
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700230 private int mBottomMargin;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700231 private int mBottomInset = 0;
shawnlin8e4e92c2018-04-12 18:47:24 +0800232 private float mQsExpansionFraction;
Selim Cinek67b22602014-03-10 15:40:16 +0100233
234 /**
235 * The algorithm which calculates the properties for our children
236 */
Muyuan Li87798022016-04-07 17:51:25 -0700237 protected final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100238
Selim Cinek281c2022016-10-13 19:14:43 -0700239 private final AmbientState mAmbientState;
Selim Cinekb5605e52015-02-20 18:21:41 +0100240 private NotificationGroupManager mGroupManager;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500241 private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700242 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500243 private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
244 private ArrayList<ExpandableView> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200245 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700246 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
247 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200248 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200249 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200250 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800251 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200252
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200253 /**
254 * The raw amount of the overScroll on the top, which is not rubber-banded.
255 */
256 private float mOverScrolledTopPixels;
257
258 /**
259 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
260 */
261 private float mOverScrolledBottomPixels;
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900262 private NotificationLogger.OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200263 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200264 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100265 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200266 private boolean mNeedsAnimation;
267 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200268 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200269 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400270 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100271 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200272 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200273 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200274 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200275 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200276 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700277 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200278 private boolean mExpandingNotification;
279 private boolean mExpandedInThisMotion;
shawnlin8e4e92c2018-04-12 18:47:24 +0800280 private boolean mShouldShowShelfOnly;
Muyuan Li84b45612016-06-01 11:05:08 -0700281 protected boolean mScrollingEnabled;
Julia Reynoldsed1c9af2018-03-21 15:21:09 -0400282 protected FooterView mFooterView;
Muyuan Lidd9ae752016-05-13 16:45:43 -0700283 protected EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400284 private boolean mDismissAllInProgress;
Anthony Chen7acbb772017-04-07 16:45:25 -0700285 private boolean mFadeNotificationsOnDismiss;
Selim Cinek1408eb52014-06-02 14:45:38 +0200286
287 /**
288 * Was the scroller scrolled to the top when the down motion was observed?
289 */
290 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200291 /**
292 * The minimal amount of over scroll which is needed in order to switch to the quick settings
293 * when over scrolling on a expanded card.
294 */
295 private float mMinTopOverScrollToEscape;
296 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700297 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200298 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200299 private boolean mDontReportNextOverScroll;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700300 private boolean mDontClampNextScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200301 private boolean mNeedViewResizeAnimation;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500302 private ExpandableView mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700303 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700304
Selim Cinek1408eb52014-06-02 14:45:38 +0200305 /**
306 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
307 * This is needed to avoid scrolling too far after the notification was collapsed in the same
308 * motion.
309 */
310 private int mMaxScrollAfterExpand;
Geoffrey Pitsch409db272017-08-28 14:51:52 +0000311 private ExpandableNotificationRow.LongPressListener mLongPressListener;
Mady Mellorc2dbe492017-03-30 13:22:03 -0700312 boolean mCheckForLeavebehind;
Selim Cinek1408eb52014-06-02 14:45:38 +0200313
314 /**
315 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
316 * animating.
317 */
318 private boolean mOnlyScrollingInThisMotion;
Adrian Roosfa139752016-04-27 09:59:08 -0700319 private boolean mDisallowDismissInThisMotion;
Selim Cineka59ecc32015-04-07 10:51:49 -0700320 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700321 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200322 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200323 = new ViewTreeObserver.OnPreDrawListener() {
324 @Override
325 public boolean onPreDraw() {
Adrian Roos181385c2016-05-05 17:45:44 -0400326 updateForcedScroll();
Selim Cinek1f553cf2014-05-02 12:01:36 +0200327 updateChildren();
328 mChildrenUpdateRequested = false;
329 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200330 return true;
331 }
332 };
Jason Monk2a6ea9c2017-01-26 11:14:51 -0500333 private StatusBar mStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100334 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100335 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700336 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek9dd0d042018-05-14 18:12:42 -0700337 private HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700338 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
339 = new HashSet<>();
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900340 private HeadsUpManagerPhone mHeadsUpManager;
Lucas Dupin00be88f2019-01-03 17:50:52 -0800341 private final NotificationRoundnessManager mRoundnessManager;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700342 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700343 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200344 private boolean mForceNoOverlappingRendering;
Selim Cineke0890e52015-06-17 11:17:08 -0700345 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700346 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800347 private boolean mAnimationRunning;
Selim Cinekc383fd02016-10-21 15:31:26 -0700348 private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
Selim Cinek6811d722016-01-19 17:53:12 -0800349 = new ViewTreeObserver.OnPreDrawListener() {
350 @Override
351 public boolean onPreDraw() {
Selim Cinekc383fd02016-10-21 15:31:26 -0700352 onPreDrawDuringAnimation();
Selim Cinek6811d722016-01-19 17:53:12 -0800353 return true;
354 }
355 };
Gus Prevase2d6f042018-10-17 15:25:30 -0400356 private NotificationSection[] mSections = new NotificationSection[NUM_SECTIONS];
Selim Cinek614576e2016-01-20 10:54:09 -0800357 private boolean mAnimateNextBackgroundTop;
Gus Prevase2d6f042018-10-17 15:25:30 -0400358 private boolean mAnimateNextBackgroundBottom;
359 private boolean mAnimateNextSectionBoundsChange;
Selim Cinekd35c2792016-01-21 13:20:57 -0800360 private int mBgColor;
361 private float mDimAmount;
362 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800363 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400364 private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
Selim Cinekd35c2792016-01-21 13:20:57 -0800365 @Override
366 public void onAnimationEnd(Animator animation) {
367 mDimAnimator = null;
368 }
369 };
370 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
371 = new ValueAnimator.AnimatorUpdateListener() {
372
373 @Override
374 public void onAnimationUpdate(ValueAnimator animation) {
375 setDimAmount((Float) animation.getAnimatedValue());
376 }
377 };
Muyuan Li4fe4a402016-03-30 16:50:11 -0700378 protected ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800379 private boolean mContinuousShadowUpdate;
Selim Cinekae55d832019-02-22 17:43:43 -0800380 private boolean mContinuousBackgroundUpdate;
Selim Cinek33223572016-02-19 19:32:22 -0800381 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
382 = new ViewTreeObserver.OnPreDrawListener() {
383
384 @Override
385 public boolean onPreDraw() {
386 updateViewShadows();
387 return true;
388 }
389 };
Selim Cinekae55d832019-02-22 17:43:43 -0800390 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
391 updateBackground();
392 return true;
393 };
Selim Cinek33223572016-02-19 19:32:22 -0800394 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
395 @Override
396 public int compare(ExpandableView view, ExpandableView otherView) {
397 float endY = view.getTranslationY() + view.getActualHeight();
398 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
399 if (endY < otherEndY) {
400 return -1;
401 } else if (endY > otherEndY) {
402 return 1;
403 } else {
404 // The two notifications end at the same location
405 return 0;
406 }
407 }
408 };
Lucas Dupin64e2f572019-03-21 14:21:14 -0700409 private final ViewOutlineProvider mOutlineProvider = new ViewOutlineProvider() {
410 @Override
411 public void getOutline(View view, Outline outline) {
Selim Cinek1e059272019-05-21 18:15:00 -0700412 if (mAmbientState.isDarkAtAll()) {
Selim Cinek9ad240c2019-04-08 19:07:15 -0700413 float xProgress = mDarkXInterpolator.getInterpolation(
414 (1 - mLinearDarkAmount) * mBackgroundXFactor);
415 outline.setRoundRect(mBackgroundAnimationRect,
416 MathUtils.lerp(mCornerRadius / 2.0f, mCornerRadius,
417 xProgress));
Lucas Dupin64e2f572019-03-21 14:21:14 -0700418 } else {
419 ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
420 }
421 }
422 };
Selim Cinek25503252016-03-03 15:31:43 -0800423 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
yoshiki iguchi4e30e762018-02-06 12:09:23 +0900424 private boolean mPulsing;
Selim Cinek1b2a05e2016-04-28 14:20:39 -0700425 private boolean mGroupExpandedForMeasure;
Selim Cinekc22fff62016-05-20 12:44:30 -0700426 private boolean mScrollable;
Adrian Roos181385c2016-05-05 17:45:44 -0400427 private View mForcedScroll;
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200428
429 /**
430 * @see #setDarkAmount(float, float)
431 */
432 private float mInterpolatedDarkAmount = 0f;
433
434 /**
435 * @see #setDarkAmount(float, float)
436 */
437 private float mLinearDarkAmount = 0f;
Lucas Dupin439bd442018-06-12 15:05:28 -0700438
439 /**
440 * How fast the background scales in the X direction as a factor of the Y expansion.
441 */
442 private float mBackgroundXFactor = 1f;
Selim Cinek972123d2016-05-03 14:25:58 -0700443
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -0500444 private boolean mSwipingInProgress;
445
Lucas Dupine17ce522017-07-17 15:45:06 -0700446 private boolean mUsingLightTheme;
Selim Cinekbc243a92016-09-27 16:35:13 -0700447 private boolean mQsExpanded;
Selim Cinekef406062016-09-29 17:33:13 -0700448 private boolean mForwardScrollable;
449 private boolean mBackwardScrollable;
Selim Cinek281c2022016-10-13 19:14:43 -0700450 private NotificationShelf mShelf;
Selim Cinekad7fac02016-10-18 17:09:15 -0700451 private int mMaxDisplayedNotifications = -1;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800452 private int mStatusBarHeight;
Selim Cinek51d21972017-07-19 17:39:20 -0700453 private int mMinInteractionHeight;
Selim Cinek48ff9b42016-11-09 19:31:51 -0800454 private boolean mNoAmbient;
455 private final Rect mClipRect = new Rect();
456 private boolean mIsClipped;
Selim Cinekcafa87f2016-10-26 17:00:17 -0700457 private Rect mRequestedClipBounds;
458 private boolean mInHeadsUpPinnedMode;
459 private boolean mHeadsUpAnimatingAway;
Selim Cinek355652a2016-12-07 13:32:12 -0800460 private int mStatusBarState;
Selim Cinekfb6ee6d2016-12-29 16:49:26 +0100461 private int mCachedBackgroundColor;
Selim Cinek5cf1d052017-06-01 17:36:46 -0700462 private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
Gus Prevascdc98342019-01-14 14:29:44 -0500463 private Runnable mReflingAndAnimateScroll = () -> {
464 if (ANCHOR_SCROLLING) {
465 maybeReflingScroller();
466 }
467 animateScroll();
468 };
Selim Cinek0fe07392017-11-09 13:26:34 -0800469 private int mCornerRadius;
Selim Cinek515b2032017-11-15 10:20:19 -0800470 private int mSidePaddings;
Lucas Dupin16cfe452018-02-08 13:14:50 -0800471 private final Rect mBackgroundAnimationRect = new Rect();
Lucas Dupin0cd882f2018-01-30 12:19:49 -0800472 private int mAntiBurnInOffsetX;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800473 private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>();
474 private int mHeadsUpInset;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700475 private HeadsUpAppearanceController mHeadsUpAppearanceController;
Selim Cinek9bfc7a52018-06-11 16:09:00 -0700476 private NotificationIconAreaController mIconAreaController;
Lucas Dupinb46d0a22019-01-11 16:57:16 -0800477 private float mHorizontalPanelTranslation;
Jason Monk1fd3fc32018-08-14 17:20:09 -0400478 private final NotificationLockscreenUserManager mLockscreenUserManager =
479 Dependency.get(NotificationLockscreenUserManager.class);
Jason Monke59dc402018-08-16 12:05:01 -0400480 private final Rect mTmpRect = new Rect();
481 private final NotificationEntryManager mEntryManager =
482 Dependency.get(NotificationEntryManager.class);
483 private final IStatusBarService mBarService = IStatusBarService.Stub.asInterface(
484 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
Will Brockmane718d582019-01-17 16:38:38 -0500485 @VisibleForTesting
486 protected final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
Jason Monke59dc402018-08-16 12:05:01 -0400487 private final NotificationRemoteInputManager mRemoteInputManager =
488 Dependency.get(NotificationRemoteInputManager.class);
489 private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
490
491 private final DisplayMetrics mDisplayMetrics = Dependency.get(DisplayMetrics.class);
492 private final LockscreenGestureLogger mLockscreenGestureLogger =
493 Dependency.get(LockscreenGestureLogger.class);
494 private final VisualStabilityManager mVisualStabilityManager =
495 Dependency.get(VisualStabilityManager.class);
496 protected boolean mClearAllEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100497
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200498 private Interpolator mDarkXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
Jason Monke59dc402018-08-16 12:05:01 -0400499 private NotificationPanelView mNotificationPanel;
Jason Monk297c04e2018-08-23 17:16:59 -0400500 private final ShadeController mShadeController = Dependency.get(ShadeController.class);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400501
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400502 private final NotificationGutsManager
503 mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
Ned Burns9eb06332019-04-23 16:02:12 -0400504 private final NotificationSectionsManager mSectionsManager;
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800505 /**
506 * If the {@link NotificationShelf} should be visible when dark.
507 */
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700508 private boolean mAnimateBottomOnLayout;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -0400509
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500510 @Inject
511 public NotificationStackScrollLayout(
512 @Named(VIEW_CONTEXT) Context context,
513 AttributeSet attrs,
Lucas Dupin00be88f2019-01-03 17:50:52 -0800514 @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
Selim Cinek3d6ae232019-01-04 14:14:33 -0800515 NotificationRoundnessManager notificationRoundnessManager,
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700516 AmbientPulseManager ambientPulseManager,
Ned Burns9eb06332019-04-23 16:02:12 -0400517 DynamicPrivacyController dynamicPrivacyController,
Ned Burns2c74c2a2019-06-13 19:06:47 -0400518 ConfigurationController configurationController,
Ned Burns7eeccdd2019-05-15 14:50:11 -0400519 ActivityStarter activityStarter,
520 StatusBarStateController statusBarStateController) {
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500521 super(context, attrs, 0, 0);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800522 Resources res = getResources();
523
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500524 mAllowLongPress = allowLongPress;
525
Gus Prevase2d6f042018-10-17 15:25:30 -0400526 for (int i = 0; i < NUM_SECTIONS; i++) {
527 mSections[i] = new NotificationSection(this);
528 }
529
Selim Cinek3d6ae232019-01-04 14:14:33 -0800530 mAmbientPulseManager = ambientPulseManager;
Ned Burns61269442019-05-02 18:27:23 -0400531
Ned Burns9eb06332019-04-23 16:02:12 -0400532 mSectionsManager =
533 new NotificationSectionsManager(
534 this,
535 activityStarter,
Ned Burns7eeccdd2019-05-15 14:50:11 -0400536 statusBarStateController,
Ned Burns2c74c2a2019-06-13 19:06:47 -0400537 configurationController,
Ned Burns9eb06332019-04-23 16:02:12 -0400538 NotificationUtils.useNewInterruptionModel(context));
Ned Burns2c74c2a2019-06-13 19:06:47 -0400539 mSectionsManager.initialize(LayoutInflater.from(context));
Ned Burns61269442019-05-02 18:27:23 -0400540 mSectionsManager.setOnClearGentleNotifsClickListener(v -> {
541 // Leave the shade open if there will be other notifs left over to clear
542 final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
543 clearNotifications(ROWS_GENTLE, closeShade);
544 });
545
Ned Burns9eb06332019-04-23 16:02:12 -0400546 mAmbientState = new AmbientState(context, mSectionsManager);
Lucas Dupin00be88f2019-01-03 17:50:52 -0800547 mRoundnessManager = notificationRoundnessManager;
Selim Cinekd35c2792016-01-21 13:20:57 -0800548 mBgColor = context.getColor(R.color.notification_shade_background_color);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800549 int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
550 int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400551 mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback,
Selim Cinek1cf41c12014-08-12 20:06:19 +0200552 minHeight, maxHeight);
553 mExpandHelper.setEventSource(this);
554 mExpandHelper.setScrollAdapter(this);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400555 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback,
556 getContext(), mMenuEventListener);
Muyuan Li333a4fc2016-04-16 17:13:46 -0700557 mStackScrollAlgorithm = createStackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100558 initView(context);
Dave Mankoffdde5ee62019-05-02 17:36:11 -0400559 mFalsingManager = FalsingManagerFactory.getInstance(context);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800560 mShouldDrawNotificationBackground =
561 res.getBoolean(R.bool.config_drawNotificationBackground);
Anthony Chen7acbb772017-04-07 16:45:25 -0700562 mFadeNotificationsOnDismiss =
563 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
Selim Cinek29aab962018-02-27 17:05:45 -0800564 mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated);
565 mRoundnessManager.setOnRoundingChangedCallback(this::invalidate);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800566 addOnExpandedHeightListener(mRoundnessManager::setExpanded);
Lucas Dupin64e2f572019-03-21 14:21:14 -0700567 setOutlineProvider(mOutlineProvider);
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800568
Rohan Shah524cf7b2018-03-15 14:40:02 -0700569 // Blocking helper manager wants to know the expanded state, update as well.
570 NotificationBlockingHelperManager blockingHelperManager =
571 Dependency.get(NotificationBlockingHelperManager.class);
572 addOnExpandedHeightListener((height, unused) -> {
573 blockingHelperManager.setNotificationShadeExpanded(height);
574 });
575
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800576 updateWillNotDraw();
Lucas Dupind285cf02018-01-18 09:18:23 -0800577 mBackgroundPaint.setAntiAlias(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100578 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100579 mDebugPaint = new Paint();
580 mDebugPaint.setColor(0xffff0000);
581 mDebugPaint.setStrokeWidth(2);
582 mDebugPaint.setStyle(Paint.Style.STROKE);
Gus Prevas0fa58d62019-01-11 13:58:40 -0500583 mDebugPaint.setTextSize(25f);
Selim Cinek67b22602014-03-10 15:40:16 +0100584 }
Jason Monke59dc402018-08-16 12:05:01 -0400585 mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
Gus Prevas33619af2018-10-26 15:40:27 -0400586
587 TunerService tunerService = Dependency.get(TunerService.class);
588 tunerService.addTunable((key, newValue) -> {
Selim Cinek3c9b0542019-06-11 15:41:28 -0700589 if (key.equals(HIGH_PRIORITY)) {
590 mHighPriorityBeforeSpeedBump = "1".equals(newValue);
Gus Prevasa18dc572019-01-14 16:11:22 -0500591 } else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
592 updateDismissRtlSetting("1".equals(newValue));
Gus Prevas33619af2018-10-26 15:40:27 -0400593 }
Selim Cinek3c9b0542019-06-11 15:41:28 -0700594 }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500595
596 mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
597 @Override
Mady Mellor0ad5b9d2019-01-08 14:59:55 -0800598 public void onPostEntryUpdated(NotificationEntry entry) {
Gus Prevas5ecd2b82019-01-04 17:19:26 -0500599 if (!entry.notification.isClearable()) {
600 // The user may have performed a dismiss action on the notification, since it's
601 // not clearable we should snap it back.
602 snapViewIfNeeded(entry);
603 }
604 }
605 });
Selim Cinek6f0a62a2019-04-09 18:40:12 -0700606 dynamicPrivacyController.addListener(this);
Jason Monke59dc402018-08-16 12:05:01 -0400607 }
608
Gus Prevasa18dc572019-01-14 16:11:22 -0500609 private void updateDismissRtlSetting(boolean dismissRtl) {
610 mDismissRtl = dismissRtl;
611 for (int i = 0; i < getChildCount(); i++) {
612 View child = getChildAt(i);
613 if (child instanceof ExpandableNotificationRow) {
614 ((ExpandableNotificationRow) child).setDismissRtl(dismissRtl);
615 }
616 }
617 }
618
Jason Monke59dc402018-08-16 12:05:01 -0400619 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400620 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400621 protected void onFinishInflate() {
622 super.onFinishInflate();
623
624 inflateEmptyShadeView();
625 inflateFooterView();
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400626 mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation);
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500627 if (mAllowLongPress) {
Steve Elliottff6c25e2019-05-30 17:38:52 -0400628 setLongPressListener(mNotificationGutsManager::openGuts);
Gus Prevas59ec2ff2018-12-28 16:20:28 -0500629 }
Jason Monke59dc402018-08-16 12:05:01 -0400630 }
631
Selim Cinek34518f62019-02-28 19:41:18 -0800632 /**
633 * @return the height at which we will wake up when pulsing
634 */
635 public float getPulseHeight() {
Selim Cinek3d6ae232019-01-04 14:14:33 -0800636 ActivatableNotificationView firstChild = getFirstChildWithBackground();
637 if (firstChild != null) {
638 return firstChild.getCollapsedHeight();
639 }
Selim Cinek34518f62019-02-28 19:41:18 -0800640 return 0f;
Selim Cinek3d6ae232019-01-04 14:14:33 -0800641 }
642
Jason Monke59dc402018-08-16 12:05:01 -0400643 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400644 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400645 public void onDensityOrFontScaleChanged() {
Selim Cinekdd26a7e2019-02-11 18:42:55 -0800646 reinflateViews();
647 }
648
649 private void reinflateViews() {
Jason Monke59dc402018-08-16 12:05:01 -0400650 inflateFooterView();
651 inflateEmptyShadeView();
652 updateFooter();
Ned Burns2c74c2a2019-06-13 19:06:47 -0400653 mSectionsManager.reinflateViews(LayoutInflater.from(mContext));
Jason Monke59dc402018-08-16 12:05:01 -0400654 }
655
656 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400657 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400658 public void onThemeChanged() {
Lucas Dupina476c792019-06-10 18:00:22 -0700659 final boolean useDarkText = mColorExtractor.getNeutralColors().supportsDarkText();
Jason Monke59dc402018-08-16 12:05:01 -0400660 updateDecorViews(useDarkText);
661
662 updateFooter();
663 }
664
Fabian Kozynskid254b192019-02-05 13:42:58 -0500665 @Override
666 public void onOverlayChanged() {
667 int newRadius = mContext.getResources().getDimensionPixelSize(
668 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
669 if (mCornerRadius != newRadius) {
670 mCornerRadius = newRadius;
671 invalidate();
672 }
Selim Cinekdd26a7e2019-02-11 18:42:55 -0800673 reinflateViews();
Fabian Kozynskid254b192019-02-05 13:42:58 -0500674 }
675
Jason Monke59dc402018-08-16 12:05:01 -0400676 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400677 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -0400678 public void updateFooter() {
Ned Burns61269442019-05-02 18:27:23 -0400679 boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
Jason Monke59dc402018-08-16 12:05:01 -0400680 boolean showFooterView = (showDismissView ||
681 mEntryManager.getNotificationData().getActiveNotifications().size() != 0)
682 && mStatusBarState != StatusBarState.KEYGUARD
683 && !mRemoteInputManager.getController().isRemoteInputActive();
684
685 updateFooterView(showFooterView, showDismissView);
686 }
687
688 /**
689 * Return whether there are any clearable notifications
690 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400691 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -0400692 public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
Jason Monke59dc402018-08-16 12:05:01 -0400693 int childCount = getChildCount();
694 for (int i = 0; i < childCount; i++) {
695 View child = getChildAt(i);
696 if (!(child instanceof ExpandableNotificationRow)) {
697 continue;
698 }
Ned Burns61269442019-05-02 18:27:23 -0400699 final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
700 if (row.canViewBeDismissed() && matchesSelection(row, selection)) {
Jason Monke59dc402018-08-16 12:05:01 -0400701 return true;
702 }
703 }
704 return false;
705 }
706
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400707 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400708 public RemoteInputController.Delegate createDelegate() {
Jason Monke59dc402018-08-16 12:05:01 -0400709 return new RemoteInputController.Delegate() {
Ned Burnsf81c4c42019-01-07 14:10:43 -0500710 public void setRemoteInputActive(NotificationEntry entry,
Jason Monke59dc402018-08-16 12:05:01 -0400711 boolean remoteInputActive) {
712 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
Evan Laird94492852018-10-25 13:43:01 -0400713 entry.notifyHeightChanged(true /* needsAnimation */);
Jason Monke59dc402018-08-16 12:05:01 -0400714 updateFooter();
715 }
716
Ned Burnsf81c4c42019-01-07 14:10:43 -0500717 public void lockScrollTo(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -0400718 NotificationStackScrollLayout.this.lockScrollTo(entry.getRow());
Jason Monke59dc402018-08-16 12:05:01 -0400719 }
720
721 public void requestDisallowLongPressAndDismiss() {
722 requestDisallowLongPress();
723 requestDisallowDismiss();
724 }
725 };
Selim Cinek67b22602014-03-10 15:40:16 +0100726 }
727
Eliot Courtney2b4c3a02017-11-27 13:27:46 +0900728 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400729 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk1fd3fc32018-08-14 17:20:09 -0400730 protected void onAttachedToWindow() {
731 super.onAttachedToWindow();
Beverly8fdb5332019-02-04 14:29:49 -0500732 ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
733 .addCallback(mStateListener, SysuiStatusBarStateController.RANK_STACK_SCROLLER);
Jason Monke59dc402018-08-16 12:05:01 -0400734 Dependency.get(ConfigurationController.class).addCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400735 }
736
737 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400738 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk1fd3fc32018-08-14 17:20:09 -0400739 protected void onDetachedFromWindow() {
740 super.onDetachedFromWindow();
Jason Monkaf08c152018-12-04 11:12:39 -0500741 Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
Jason Monke59dc402018-08-16 12:05:01 -0400742 Dependency.get(ConfigurationController.class).removeCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400743 }
744
745 @Override
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -0400746 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellor95d743c2017-01-10 12:05:27 -0800747 public NotificationSwipeActionHelper getSwipeActionHelper() {
748 return mSwipeHelper;
749 }
750
Selim Cinek67b22602014-03-10 15:40:16 +0100751 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400752 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupinf03e7522018-06-25 16:21:13 -0700753 public void onUiModeChanged() {
754 mBgColor = mContext.getColor(R.color.notification_shade_background_color);
755 updateBackgroundDimming();
Selim Cinekab9c7b22018-12-11 18:15:47 -0800756 mShelf.onUiModeChanged();
Ned Burns9eb06332019-04-23 16:02:12 -0400757 mSectionsManager.onUiModeChanged();
Lucas Dupinf03e7522018-06-25 16:21:13 -0700758 }
759
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400760 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Selim Cinek67b22602014-03-10 15:40:16 +0100761 protected void onDraw(Canvas canvas) {
Lucas Dupind285cf02018-01-18 09:18:23 -0800762 if (mShouldDrawNotificationBackground
Gus Prevase2d6f042018-10-17 15:25:30 -0400763 && (mSections[0].getCurrentBounds().top
764 < mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom
765 || mAmbientState.isDark())) {
Lucas Dupind285cf02018-01-18 09:18:23 -0800766 drawBackground(canvas);
Gus Prevas211181532018-12-13 14:49:33 -0500767 } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
768 drawHeadsUpBackground(canvas);
Selim Cinekd381bc32016-08-15 12:40:57 -0700769 }
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800770
Selim Cinek67b22602014-03-10 15:40:16 +0100771 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800772 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100773 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Mady Mellor43c2cd12016-12-12 21:05:13 -0800774 y = getLayoutHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100775 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200776 y = getHeight() - getEmptyBottomMargin();
777 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100778 }
779 }
780
Gus Prevas0fa58d62019-01-11 13:58:40 -0500781 @Override
782 public void draw(Canvas canvas) {
783 super.draw(canvas);
784
785 if (DEBUG && ANCHOR_SCROLLING) {
786 if (mScrollAnchorView instanceof ExpandableNotificationRow) {
787 canvas.drawRect(0,
788 mScrollAnchorView.getTranslationY(),
789 getWidth(),
790 mScrollAnchorView.getTranslationY()
791 + ((ExpandableNotificationRow) mScrollAnchorView).getActualHeight(),
792 mDebugPaint);
793 canvas.drawText(Integer.toString(mScrollAnchorViewY), getWidth() - 200,
794 mScrollAnchorView.getTranslationY() + 30, mDebugPaint);
795 int y = (int) mShelf.getTranslationY();
796 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
797 }
Gus Prevascdc98342019-01-14 14:29:44 -0500798 canvas.drawText(Integer.toString(getMaxNegativeScrollAmount()), getWidth() - 100,
799 getIntrinsicPadding() + 30, mDebugPaint);
800 canvas.drawText(Integer.toString(getMaxPositiveScrollAmount()), getWidth() - 100,
801 getHeight() - 30, mDebugPaint);
Gus Prevas0fa58d62019-01-11 13:58:40 -0500802 }
803 }
804
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400805 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Lucas Dupind285cf02018-01-18 09:18:23 -0800806 private void drawBackground(Canvas canvas) {
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700807 int lockScreenLeft = mSidePaddings;
808 int lockScreenRight = getWidth() - mSidePaddings;
Gus Prevase2d6f042018-10-17 15:25:30 -0400809 int lockScreenTop = mSections[0].getCurrentBounds().top;
810 int lockScreenBottom = mSections[NUM_SECTIONS - 1].getCurrentBounds().bottom;
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700811 int darkLeft = getWidth() / 2;
Lucas Dupin00be88f2019-01-03 17:50:52 -0800812 int darkTop = mTopPadding;
Lucas Dupind285cf02018-01-18 09:18:23 -0800813
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700814 float yProgress = 1 - mInterpolatedDarkAmount;
815 float xProgress = mDarkXInterpolator.getInterpolation(
816 (1 - mLinearDarkAmount) * mBackgroundXFactor);
Lucas Dupin60661a62018-04-12 10:50:13 -0700817
Gus Prevase2d6f042018-10-17 15:25:30 -0400818 int left = (int) MathUtils.lerp(darkLeft, lockScreenLeft, xProgress);
819 int right = (int) MathUtils.lerp(darkLeft, lockScreenRight, xProgress);
820 int top = (int) MathUtils.lerp(darkTop, lockScreenTop, yProgress);
821 int bottom = (int) MathUtils.lerp(darkTop, lockScreenBottom, yProgress);
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700822 mBackgroundAnimationRect.set(
Gus Prevase2d6f042018-10-17 15:25:30 -0400823 left,
824 top,
825 right,
826 bottom);
Jorim Jaggic4cf07a2018-07-05 18:28:12 +0200827
Gus Prevase2d6f042018-10-17 15:25:30 -0400828 int backgroundTopAnimationOffset = top - lockScreenTop;
829 // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
830 boolean anySectionHasVisibleChild = false;
831 for (NotificationSection section : mSections) {
832 if (section.getFirstVisibleChild() != null) {
833 anySectionHasVisibleChild = true;
834 break;
835 }
836 }
837 if (!mAmbientState.isDark() || anySectionHasVisibleChild) {
838 drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
Lucas Dupind285cf02018-01-18 09:18:23 -0800839 }
Lucas Dupin23a8d3b2018-10-08 20:57:35 -0700840
Lucas Dupin16cfe452018-02-08 13:14:50 -0800841 updateClipping();
Lucas Dupind285cf02018-01-18 09:18:23 -0800842 }
843
Gus Prevase2d6f042018-10-17 15:25:30 -0400844 /**
845 * Draws round rects for each background section.
846 *
847 * We want to draw a round rect for each background section as defined by {@link #mSections}.
848 * However, if two sections are directly adjacent with no gap between them (e.g. on the
849 * lockscreen where the shelf can appear directly below the high priority section, or while
850 * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
851 * section), we don't want to round the adjacent corners.
852 *
853 * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
854 * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
855 * This method tracks the top of each rect we need to draw, then iterates through the visible
856 * sections. If a section is not adjacent to the previous section, we draw the previous rect
857 * behind the sections we've accumulated up to that point, then start a new rect at the top of
858 * the current section. When we're done iterating we will always have one rect left to draw.
859 */
860 private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
861 int animationYOffset) {
862 int backgroundRectTop = top;
863 int lastSectionBottom =
864 mSections[0].getCurrentBounds().bottom + animationYOffset;
Selim Cinek0acf4302019-06-11 12:43:03 -0700865 int currentLeft = left;
866 int currentRight = right;
Selim Cinekae55d832019-02-22 17:43:43 -0800867 boolean first = true;
Gus Prevase2d6f042018-10-17 15:25:30 -0400868 for (NotificationSection section : mSections) {
869 if (section.getFirstVisibleChild() == null) {
870 continue;
871 }
872 int sectionTop = section.getCurrentBounds().top + animationYOffset;
Selim Cinekae55d832019-02-22 17:43:43 -0800873 int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
Selim Cinekd0b48e32019-05-24 20:49:23 -0700874 int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
Gus Prevase2d6f042018-10-17 15:25:30 -0400875 // If sections are directly adjacent to each other, we don't want to draw them
876 // as separate roundrects, as the rounded corners right next to each other look
877 // bad.
Selim Cinekae55d832019-02-22 17:43:43 -0800878 if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
Selim Cinek0acf4302019-06-11 12:43:03 -0700879 || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
880 canvas.drawRoundRect(currentLeft,
Gus Prevase2d6f042018-10-17 15:25:30 -0400881 backgroundRectTop,
Selim Cinek0acf4302019-06-11 12:43:03 -0700882 currentRight,
Gus Prevase2d6f042018-10-17 15:25:30 -0400883 lastSectionBottom,
884 mCornerRadius, mCornerRadius, mBackgroundPaint);
885 backgroundRectTop = sectionTop;
886 }
Selim Cinek0acf4302019-06-11 12:43:03 -0700887 currentLeft = ownLeft;
888 currentRight = ownRight;
Gus Prevase2d6f042018-10-17 15:25:30 -0400889 lastSectionBottom =
890 section.getCurrentBounds().bottom + animationYOffset;
Selim Cinekae55d832019-02-22 17:43:43 -0800891 first = false;
Gus Prevase2d6f042018-10-17 15:25:30 -0400892 }
Selim Cinek0acf4302019-06-11 12:43:03 -0700893 canvas.drawRoundRect(currentLeft,
Gus Prevase2d6f042018-10-17 15:25:30 -0400894 backgroundRectTop,
Selim Cinek0acf4302019-06-11 12:43:03 -0700895 currentRight,
Gus Prevase2d6f042018-10-17 15:25:30 -0400896 lastSectionBottom,
897 mCornerRadius, mCornerRadius, mBackgroundPaint);
898 }
899
Gus Prevas211181532018-12-13 14:49:33 -0500900 private void drawHeadsUpBackground(Canvas canvas) {
901 int left = mSidePaddings;
902 int right = getWidth() - mSidePaddings;
903
904 float top = getHeight();
905 float bottom = 0;
906 int childCount = getChildCount();
907 for (int i = 0; i < childCount; i++) {
908 View child = getChildAt(i);
909 if (child.getVisibility() != View.GONE
910 && child instanceof ExpandableNotificationRow) {
911 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
shawnlin3a950c32019-05-15 20:06:10 +0800912 if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
913 && row.getProvider().shouldShowGutsOnSnapOpen()) {
Gus Prevas211181532018-12-13 14:49:33 -0500914 top = Math.min(top, row.getTranslationY());
915 bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
916 }
917 }
918 }
919
920 if (top < bottom) {
921 canvas.drawRoundRect(
922 left, top, right, bottom,
923 mCornerRadius, mCornerRadius, mBackgroundPaint);
924 }
925 }
926
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400927 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd35c2792016-01-21 13:20:57 -0800928 private void updateBackgroundDimming() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -0800929 // No need to update the background color if it's not being drawn.
930 if (!mShouldDrawNotificationBackground) {
931 return;
932 }
933
Lucas Dupinb561eda2018-04-09 17:25:04 -0700934 // Interpolate between semi-transparent notification panel background color
935 // and white AOD separator.
Lucas Dupinf03e7522018-06-25 16:21:13 -0700936 float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
937 mLinearDarkAmount);
Felka Chang97bc8d52019-05-29 02:51:15 +0800938 int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
Lucas Dupind285cf02018-01-18 09:18:23 -0800939
Selim Cinekfb6ee6d2016-12-29 16:49:26 +0100940 if (mCachedBackgroundColor != color) {
941 mCachedBackgroundColor = color;
942 mBackgroundPaint.setColor(color);
943 invalidate();
944 }
Selim Cinekd35c2792016-01-21 13:20:57 -0800945 }
946
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400947 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +0100948 private void initView(Context context) {
949 mScroller = new OverScroller(getContext());
Selim Cinek67b22602014-03-10 15:40:16 +0100950 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200951 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100952 final ViewConfiguration configuration = ViewConfiguration.get(context);
953 mTouchSlop = configuration.getScaledTouchSlop();
954 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
955 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100956 mOverflingDistance = configuration.getScaledOverflingDistance();
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700957
958 Resources res = context.getResources();
959 mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800960 mStackScrollAlgorithm.initView(context);
Selim Cinek281c2022016-10-13 19:14:43 -0700961 mAmbientState.reload(context);
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700962 mPaddingBetweenElements = Math.max(1,
963 res.getDimensionPixelSize(R.dimen.notification_divider_height));
964 mIncreasedPaddingBetweenElements =
965 res.getDimensionPixelSize(R.dimen.notification_divider_height_increased);
966 mMinTopOverScrollToEscape = res.getDimensionPixelSize(
Selim Cinek1408eb52014-06-02 14:45:38 +0200967 R.dimen.min_top_overscroll_to_qs);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800968 mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height);
Anthony Chen9fe1ee72017-04-07 13:53:37 -0700969 mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
Selim Cinekb95fd182017-12-21 13:03:32 -0800970 mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
Selim Cinek51d21972017-07-19 17:39:20 -0700971 mMinInteractionHeight = res.getDimensionPixelSize(
972 R.dimen.notification_min_interaction_height);
Selim Cinek0fe07392017-11-09 13:26:34 -0800973 mCornerRadius = res.getDimensionPixelSize(
974 Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800975 mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize(
976 R.dimen.heads_up_status_bar_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200977 }
978
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400979 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekaef92ef2014-06-06 18:06:04 +0200980 private void notifyHeightChangeListener(ExpandableView view) {
Lucas Dupin60661a62018-04-12 10:50:13 -0700981 notifyHeightChangeListener(view, false /* needsAnimation */);
982 }
983
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400984 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Lucas Dupin60661a62018-04-12 10:50:13 -0700985 private void notifyHeightChangeListener(ExpandableView view, boolean needsAnimation) {
Selim Cinekaef92ef2014-06-06 18:06:04 +0200986 if (mOnHeightChangedListener != null) {
Lucas Dupin60661a62018-04-12 10:50:13 -0700987 mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200988 }
Selim Cinek67b22602014-03-10 15:40:16 +0100989 }
990
991 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -0400992 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +0100993 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
994 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinekb95fd182017-12-21 13:03:32 -0800995
996 int width = MeasureSpec.getSize(widthMeasureSpec);
997 int childWidthSpec = MeasureSpec.makeMeasureSpec(width - mSidePaddings * 2,
998 MeasureSpec.getMode(widthMeasureSpec));
Evan Lairdeb7dbd52018-06-28 13:17:25 -0400999 // Don't constrain the height of the children so we know how big they'd like to be
1000 int childHeightSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec),
1001 MeasureSpec.UNSPECIFIED);
1002
Selim Cinekfa760d42016-05-10 15:50:53 -04001003 // We need to measure all children even the GONE ones, such that the heights are calculated
1004 // correctly as they are used to calculate how many we can fit on the screen.
1005 final int size = getChildCount();
1006 for (int i = 0; i < size; i++) {
Evan Lairdeb7dbd52018-06-28 13:17:25 -04001007 measureChild(getChildAt(i), childWidthSpec, childHeightSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -04001008 }
Selim Cinek67b22602014-03-10 15:40:16 +01001009 }
1010
1011 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001012 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001013 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinek67b22602014-03-10 15:40:16 +01001014 // we layout all our children centered on the top
1015 float centerX = getWidth() / 2.0f;
1016 for (int i = 0; i < getChildCount(); i++) {
1017 View child = getChildAt(i);
Selim Cinekfa760d42016-05-10 15:50:53 -04001018 // We need to layout all children even the GONE ones, such that the heights are
1019 // calculated correctly as they are used to calculate how many we can fit on the screen
Selim Cinek67b22602014-03-10 15:40:16 +01001020 float width = child.getMeasuredWidth();
1021 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001022 child.layout((int) (centerX - width / 2.0f),
1023 0,
1024 (int) (centerX + width / 2.0f),
1025 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +01001026 }
Jorim Jaggi1d480692014-05-20 19:41:58 +02001027 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +01001028 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001029 clampScrollPosition();
Selim Cinek319bdc42014-05-01 23:01:58 +02001030 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -08001031 updateFirstAndLastBackgroundViews();
Selim Cinekbc243a92016-09-27 16:35:13 -07001032 updateAlgorithmLayoutMinHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001033 }
1034
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001035 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5bc852a2015-12-21 12:19:09 -08001036 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
1037 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +02001038 mNeedViewResizeAnimation = true;
1039 mNeedsAnimation = true;
1040 }
Selim Cineka5e211b2014-08-11 17:35:48 +02001041 }
1042
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001043 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekdb167372016-11-17 15:41:17 -08001044 public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) {
1045 mAmbientState.setSpeedBumpIndex(newIndex);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001046 mNoAmbient = noAmbient;
Selim Cinekc27437b2014-05-14 10:23:33 +02001047 }
1048
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001049 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001050 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001051 public void setChildLocationsChangedListener(
1052 NotificationLogger.OnChildLocationsChangedListener listener) {
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001053 mListener = listener;
1054 }
1055
Selim Cineka7d4f822016-12-06 14:34:47 -08001056 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001057 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001058 public boolean isInVisibleLocation(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04001059 ExpandableNotificationRow row = entry.getRow();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05001060 ExpandableViewState childViewState = row.getViewState();
1061
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001062 if (childViewState == null) {
Selim Cineka7d4f822016-12-06 14:34:47 -08001063 return false;
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001064 }
Selim Cinek9b9d6e12017-11-30 12:29:47 +01001065 if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) {
Selim Cineka7d4f822016-12-06 14:34:47 -08001066 return false;
Christoph Studer12cf9e52014-10-29 17:35:30 +01001067 }
Selim Cineka7d4f822016-12-06 14:34:47 -08001068 if (row.getVisibility() != View.VISIBLE) {
1069 return false;
1070 }
1071 return true;
Christoph Studer6e3eceb2014-04-01 18:40:27 +02001072 }
1073
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001074 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Selim Cinek67b22602014-03-10 15:40:16 +01001075 private void setMaxLayoutHeight(int maxLayoutHeight) {
1076 mMaxLayoutHeight = maxLayoutHeight;
Selim Cinek9458b192016-10-25 19:02:42 -07001077 mShelf.setMaxLayoutHeight(maxLayoutHeight);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001078 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +01001079 }
1080
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001081 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001082 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001083 mAmbientState.setLayoutHeight(getLayoutHeight());
Selim Cinekbc243a92016-09-27 16:35:13 -07001084 updateAlgorithmLayoutMinHeight();
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001085 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +01001086 }
1087
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001088 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
Selim Cinekbc243a92016-09-27 16:35:13 -07001089 private void updateAlgorithmLayoutMinHeight() {
shawnlinc3457912018-05-15 16:39:56 +08001090 mAmbientState.setLayoutMinHeight(mQsExpanded || isHeadsUpTransition()
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001091 ? getLayoutMinHeight() : 0);
Selim Cinekbc243a92016-09-27 16:35:13 -07001092 }
1093
Selim Cinek67b22602014-03-10 15:40:16 +01001094 /**
1095 * Updates the children views according to the stack scroll algorithm. Call this whenever
1096 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
1097 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001098 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001099 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -08001100 updateScrollStateForAddedChildren();
Selim Cinek727903c2016-12-06 17:28:10 -08001101 mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
1102 ? 0
1103 : mScroller.getCurrVelocity());
Gus Prevas0fa58d62019-01-11 13:58:40 -05001104 if (ANCHOR_SCROLLING) {
1105 mAmbientState.setAnchorViewIndex(indexOfChild(mScrollAnchorView));
1106 mAmbientState.setAnchorViewY(mScrollAnchorViewY);
1107 } else {
1108 mAmbientState.setScrollY(mOwnScrollY);
1109 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05001110 mStackScrollAlgorithm.resetViewStates(mAmbientState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001111 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001112 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +01001113 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +02001114 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +01001115 }
1116 }
1117
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001118 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc383fd02016-10-21 15:31:26 -07001119 private void onPreDrawDuringAnimation() {
Selim Cineka686b2c2016-10-26 13:58:27 -07001120 mShelf.updateAppearance();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001121 updateClippingToTopRoundedCorner();
Selim Cinekc383fd02016-10-21 15:31:26 -07001122 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
1123 updateBackground();
1124 }
Selim Cinekc383fd02016-10-21 15:31:26 -07001125 }
1126
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001127 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001128 private void updateClippingToTopRoundedCorner() {
Arthur Hungc0ef5652018-05-22 14:00:42 +08001129 Float clipStart = (float) mTopPadding
Jason Monke59dc402018-08-16 12:05:01 -04001130 + mStackTranslation
1131 + mAmbientState.getExpandAnimationTopChange();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001132 Float clipEnd = clipStart + mCornerRadius;
1133 boolean first = true;
1134 for (int i = 0; i < getChildCount(); i++) {
1135 ExpandableView child = (ExpandableView) getChildAt(i);
1136 if (child.getVisibility() == GONE) {
1137 continue;
1138 }
1139 float start = child.getTranslationY();
Arthur Hungc0ef5652018-05-22 14:00:42 +08001140 float end = start + child.getActualHeight();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001141 boolean clip = clipStart > start && clipStart < end
1142 || clipEnd >= start && clipEnd <= end;
Gus Prevas0fa58d62019-01-11 13:58:40 -05001143 clip &= !(first && isScrolledToTop());
Selim Cinekeccf4942018-05-30 09:55:36 -07001144 child.setDistanceToTopRoundness(clip ? Math.max(start - clipStart, 0)
1145 : ExpandableView.NO_ROUNDNESS);
Selim Cinekb0ee18f2017-12-21 16:15:53 -08001146 first = false;
1147 }
1148 }
1149
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001150 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek3776fe02016-02-04 13:32:43 -08001151 private void updateScrollStateForAddedChildren() {
1152 if (mChildrenToAddAnimated.isEmpty()) {
1153 return;
1154 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001155 if (!ANCHOR_SCROLLING) {
1156 for (int i = 0; i < getChildCount(); i++) {
1157 ExpandableView child = (ExpandableView) getChildAt(i);
1158 if (mChildrenToAddAnimated.contains(child)) {
1159 int startingPosition = getPositionInLinearLayout(child);
1160 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
1161 int padding = increasedPaddingAmount == 1.0f ? mIncreasedPaddingBetweenElements
1162 : increasedPaddingAmount == -1.0f ? 0 : mPaddingBetweenElements;
1163 int childHeight = getIntrinsicHeight(child) + padding;
1164 if (startingPosition < mOwnScrollY) {
1165 // This child starts off screen, so let's keep it offscreen to keep the
1166 // others visible
Selim Cinek3776fe02016-02-04 13:32:43 -08001167
Gus Prevas0fa58d62019-01-11 13:58:40 -05001168 setOwnScrollY(mOwnScrollY + childHeight);
1169 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001170 }
1171 }
1172 }
1173 clampScrollPosition();
1174 }
1175
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001176 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04001177 private void updateForcedScroll() {
1178 if (mForcedScroll != null && (!mForcedScroll.hasFocus()
1179 || !mForcedScroll.isAttachedToWindow())) {
1180 mForcedScroll = null;
1181 }
1182 if (mForcedScroll != null) {
1183 ExpandableView expandableView = (ExpandableView) mForcedScroll;
1184 int positionInLinearLayout = getPositionInLinearLayout(expandableView);
1185 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
Adrian Roos4a579672016-05-24 16:54:37 -07001186 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Adrian Roos181385c2016-05-05 17:45:44 -04001187
Gus Prevas0fa58d62019-01-11 13:58:40 -05001188 if (ANCHOR_SCROLLING) {
1189 // TODO
1190 } else {
1191 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
Adrian Roos4a579672016-05-24 16:54:37 -07001192
Gus Prevas0fa58d62019-01-11 13:58:40 -05001193 // Only apply the scroll if we're scrolling the view upwards, or the view is so
1194 // far up that it is not visible anymore.
1195 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1196 setOwnScrollY(targetScroll);
1197 }
Adrian Roos181385c2016-05-05 17:45:44 -04001198 }
1199 }
1200 }
1201
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001202 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek319bdc42014-05-01 23:01:58 +02001203 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +02001204 if (!mChildrenUpdateRequested) {
1205 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
1206 mChildrenUpdateRequested = true;
1207 invalidate();
1208 }
Selim Cinek319bdc42014-05-01 23:01:58 +02001209 }
1210
Robert Snoeberger168949a2019-04-18 09:39:42 -04001211 /**
1212 * Returns best effort count of visible notifications.
1213 */
1214 public int getVisibleNotificationCount() {
1215 int count = 0;
1216 for (int i = 0; i < getChildCount(); i++) {
1217 final View child = getChildAt(i);
1218 if (child.getVisibility() != View.GONE && child instanceof ExpandableNotificationRow) {
1219 count++;
1220 }
1221 }
1222 return count;
1223 }
1224
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001225 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001226 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +02001227 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +01001228 }
1229
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001230 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekf7a14c02014-07-07 14:01:46 +02001231 private void clampScrollPosition() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001232 if (ANCHOR_SCROLLING) {
1233 // TODO
1234 } else {
1235 int scrollRange = getScrollRange();
1236 if (scrollRange < mOwnScrollY) {
1237 setOwnScrollY(scrollRange);
1238 }
Selim Cinek67b22602014-03-10 15:40:16 +01001239 }
1240 }
1241
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001242 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001243 public int getTopPadding() {
1244 return mTopPadding;
1245 }
1246
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001247 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001248 private void setTopPadding(int topPadding, boolean animate) {
Lucas Dupin00be88f2019-01-03 17:50:52 -08001249 if (mTopPadding != topPadding) {
1250 mTopPadding = topPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001251 updateAlgorithmHeightAndPadding();
1252 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +02001253 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001254 mTopPaddingNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04001255 mNeedsAnimation = true;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001256 }
Selim Cinek319bdc42014-05-01 23:01:58 +02001257 requestChildrenUpdate();
Lucas Dupin60661a62018-04-12 10:50:13 -07001258 notifyHeightChangeListener(null, animate);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001259 }
1260 }
1261
1262 /**
Selim Cinekbc243a92016-09-27 16:35:13 -07001263 * Update the height of the panel.
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001264 *
Selim Cinekbc243a92016-09-27 16:35:13 -07001265 * @param height the expanded height of the panel
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001266 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001267 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekbc243a92016-09-27 16:35:13 -07001268 public void setExpandedHeight(float height) {
1269 mExpandedHeight = height;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001270 setIsExpanded(height > 0);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001271 int minExpansionHeight = getMinExpansionHeight();
1272 if (height < minExpansionHeight) {
1273 mClipRect.left = 0;
1274 mClipRect.right = getWidth();
1275 mClipRect.top = 0;
1276 mClipRect.bottom = (int) height;
1277 height = minExpansionHeight;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001278 setRequestedClipBounds(mClipRect);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001279 } else {
Selim Cinekcafa87f2016-10-26 17:00:17 -07001280 setRequestedClipBounds(null);
Selim Cinek48ff9b42016-11-09 19:31:51 -08001281 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001282 int stackHeight;
Selim Cinek94c2d822016-07-13 18:50:04 -07001283 float translationY;
1284 float appearEndPosition = getAppearEndPosition();
1285 float appearStartPosition = getAppearStartPosition();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001286 float appearFraction = 1.0f;
shawnlin5be1f7c2018-05-21 20:50:54 +08001287 boolean appearing = height < appearEndPosition;
1288 mAmbientState.setAppearing(appearing);
1289 if (!appearing) {
Selim Cinekbc243a92016-09-27 16:35:13 -07001290 translationY = 0;
shawnlin8e4e92c2018-04-12 18:47:24 +08001291 if (mShouldShowShelfOnly) {
1292 stackHeight = mTopPadding + mShelf.getIntrinsicHeight();
1293 } else if (mQsExpanded) {
1294 int stackStartPosition = mContentHeight - mTopPadding + mIntrinsicPadding;
1295 int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
1296 if (stackStartPosition <= stackEndPosition) {
1297 stackHeight = stackEndPosition;
1298 } else {
1299 stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
1300 stackEndPosition, mQsExpansionFraction);
1301 }
1302 } else {
1303 stackHeight = (int) height;
1304 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001305 } else {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001306 appearFraction = getAppearFraction(height);
Selim Cinek94c2d822016-07-13 18:50:04 -07001307 if (appearFraction >= 0) {
1308 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
1309 appearFraction);
1310 } else {
1311 // This may happen when pushing up a heads up. We linearly push it up from the
1312 // start
1313 translationY = height - appearStartPosition + getExpandTranslationStart();
1314 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001315 if (isHeadsUpTransition()) {
Gus Prevase2d6f042018-10-17 15:25:30 -04001316 stackHeight =
1317 getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001318 translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction);
1319 } else {
1320 stackHeight = (int) (height - translationY);
1321 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001322 }
1323 if (stackHeight != mCurrentStackHeight) {
1324 mCurrentStackHeight = stackHeight;
1325 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +02001326 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001327 }
Selim Cinek94c2d822016-07-13 18:50:04 -07001328 setStackTranslation(translationY);
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001329 for (int i = 0; i < mExpandedHeightListeners.size(); i++) {
1330 BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i);
1331 listener.accept(mExpandedHeight, appearFraction);
1332 }
Selim Cinekcafa87f2016-10-26 17:00:17 -07001333 }
1334
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001335 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekcafa87f2016-10-26 17:00:17 -07001336 private void setRequestedClipBounds(Rect clipRect) {
1337 mRequestedClipBounds = clipRect;
1338 updateClipping();
1339 }
1340
Lucas Dupin60661a62018-04-12 10:50:13 -07001341 /**
1342 * Return the height of the content ignoring the footer.
1343 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001344 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Lucas Dupin60661a62018-04-12 10:50:13 -07001345 public int getIntrinsicContentHeight() {
1346 return mIntrinsicContentHeight;
1347 }
1348
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001349 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcafa87f2016-10-26 17:00:17 -07001350 public void updateClipping() {
1351 boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
1352 && !mHeadsUpAnimatingAway;
Lucas Dupin64e2f572019-03-21 14:21:14 -07001353 boolean clipToOutline = false;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001354 if (mIsClipped != clipped) {
1355 mIsClipped = clipped;
Selim Cinekcafa87f2016-10-26 17:00:17 -07001356 }
Lucas Dupin16cfe452018-02-08 13:14:50 -08001357
Selim Cinek1e059272019-05-21 18:15:00 -07001358 if (!mAmbientPulseManager.hasNotifications() && mAmbientState.isFullyDark()) {
Lucas Dupin4798ea12018-11-05 19:26:24 -08001359 setClipBounds(null);
1360 } else if (mAmbientState.isDarkAtAll()) {
Lucas Dupin64e2f572019-03-21 14:21:14 -07001361 clipToOutline = true;
1362 invalidateOutline();
Lucas Dupin16cfe452018-02-08 13:14:50 -08001363 } else if (clipped) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07001364 setClipBounds(mRequestedClipBounds);
1365 } else {
1366 setClipBounds(null);
1367 }
Lucas Dupin64e2f572019-03-21 14:21:14 -07001368
1369 setClipToOutline(clipToOutline);
Selim Cinek94c2d822016-07-13 18:50:04 -07001370 }
1371
1372 /**
1373 * @return The translation at the beginning when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001374 * Measured relative to the resting position.
Selim Cinek94c2d822016-07-13 18:50:04 -07001375 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001376 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001377 private float getExpandTranslationStart() {
Selim Cinek083f2142018-11-06 16:32:23 -08001378 return -mTopPadding + getMinExpansionHeight() - mShelf.getIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07001379 }
1380
1381 /**
1382 * @return the position from where the appear transition starts when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001383 * Measured in absolute height.
Selim Cinek94c2d822016-07-13 18:50:04 -07001384 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001385 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001386 private float getAppearStartPosition() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001387 if (isHeadsUpTransition()) {
Gus Prevase2d6f042018-10-17 15:25:30 -04001388 return mHeadsUpInset
1389 + getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
Selim Cinekd127d792016-11-01 19:11:41 -07001390 }
Selim Cinek48ff9b42016-11-09 19:31:51 -08001391 return getMinExpansionHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07001392 }
1393
1394 /**
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001395 * @return the height of the top heads up notification when pinned. This is different from the
Jason Monke59dc402018-08-16 12:05:01 -04001396 * intrinsic height, which also includes whether the notification is system expanded and
1397 * is mainly used when dragging down from a heads up notification.
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001398 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001399 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001400 private int getTopHeadsUpPinnedHeight() {
Ned Burnsf81c4c42019-01-07 14:10:43 -05001401 NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001402 if (topEntry == null) {
1403 return 0;
1404 }
Evan Laird94492852018-10-25 13:43:01 -04001405 ExpandableNotificationRow row = topEntry.getRow();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001406 if (row.isChildInGroup()) {
Ned Burnsf81c4c42019-01-07 14:10:43 -05001407 final NotificationEntry groupSummary
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001408 = mGroupManager.getGroupSummary(row.getStatusBarNotification());
1409 if (groupSummary != null) {
Evan Laird94492852018-10-25 13:43:01 -04001410 row = groupSummary.getRow();
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001411 }
1412 }
1413 return row.getPinnedHeadsUpHeight();
1414 }
1415
1416 /**
Selim Cinek94c2d822016-07-13 18:50:04 -07001417 * @return the position from where the appear transition ends when expanding.
Jason Monke59dc402018-08-16 12:05:01 -04001418 * Measured in absolute height.
Selim Cinek94c2d822016-07-13 18:50:04 -07001419 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001420 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001421 private float getAppearEndPosition() {
Selim Cinekaa417da2016-10-27 18:17:08 -07001422 int appearPosition;
Selim Cinek66440cf2017-05-26 13:48:47 -07001423 int notGoneChildCount = getNotGoneChildCount();
Julia Reynolds34f14962018-05-03 12:40:20 +00001424 if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001425 if (isHeadsUpTransition()
Selim Cinekebf42342017-07-13 15:46:10 +02001426 || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09001427 appearPosition = getTopHeadsUpPinnedHeight();
Selim Cinekcde90e52016-12-22 21:01:49 +01001428 } else {
1429 appearPosition = 0;
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001430 if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) {
1431 appearPosition += mShelf.getIntrinsicHeight();
1432 }
Selim Cinekcde90e52016-12-22 21:01:49 +01001433 }
Selim Cinekaa417da2016-10-27 18:17:08 -07001434 } else {
Selim Cinekcde90e52016-12-22 21:01:49 +01001435 appearPosition = mEmptyShadeView.getHeight();
Selim Cinekaa417da2016-10-27 18:17:08 -07001436 }
1437 return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
Selim Cinek94c2d822016-07-13 18:50:04 -07001438 }
1439
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001440 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001441 private boolean isHeadsUpTransition() {
Gus Prevase2d6f042018-10-17 15:25:30 -04001442 NotificationSection firstVisibleSection = getFirstVisibleSection();
1443 return mTrackingHeadsUp && firstVisibleSection != null
Selim Cinek459aee32019-02-20 11:18:56 -08001444 && firstVisibleSection.getFirstVisibleChild().isAboveShelf();
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001445 }
1446
Selim Cinek94c2d822016-07-13 18:50:04 -07001447 /**
1448 * @param height the height of the panel
1449 * @return the fraction of the appear animation that has been performed
1450 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001451 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek94c2d822016-07-13 18:50:04 -07001452 public float getAppearFraction(float height) {
1453 float appearEndPosition = getAppearEndPosition();
1454 float appearStartPosition = getAppearStartPosition();
1455 return (height - appearStartPosition)
1456 / (appearEndPosition - appearStartPosition);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001457 }
1458
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001459 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd2281152015-04-10 14:37:46 -07001460 public float getStackTranslation() {
1461 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001462 }
1463
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001464 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd2281152015-04-10 14:37:46 -07001465 private void setStackTranslation(float stackTranslation) {
1466 if (stackTranslation != mStackTranslation) {
1467 mStackTranslation = stackTranslation;
1468 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001469 requestChildrenUpdate();
1470 }
Selim Cinek67b22602014-03-10 15:40:16 +01001471 }
1472
1473 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001474 * 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 +01001475 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
1476 *
1477 * @return either the layout height or the externally defined height, whichever is smaller
1478 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001479 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek343e6e22014-04-11 21:23:30 +02001480 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001481 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
1482 }
1483
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001484 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek816c8e42015-11-19 12:00:45 -08001485 public int getFirstItemMinHeight() {
1486 final ExpandableView firstChild = getFirstChildNotGone();
1487 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001488 }
1489
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001490 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Jason Monk16ac3772016-02-10 15:39:21 -05001491 public void setQsContainer(ViewGroup qsContainer) {
1492 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +02001493 }
1494
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001495 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek684a4422015-04-15 16:18:39 -07001496 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001497 if (v instanceof ExpandableNotificationRow) {
1498 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -07001499 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -07001500 }
1501 return false;
1502 }
1503
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001504 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cineka59ecc32015-04-07 10:51:49 -07001505 private boolean isHeadsUp(View v) {
1506 if (v instanceof ExpandableNotificationRow) {
1507 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
1508 return row.isHeadsUp();
1509 }
1510 return false;
1511 }
1512
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001513 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001514 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
1515 getLocationOnScreen(mTempInt2);
1516 float localTouchY = touchY - mTempInt2[1];
1517
1518 ExpandableView closestChild = null;
1519 float minDist = Float.MAX_VALUE;
1520
1521 // find the view closest to the location, accounting for GONE views
1522 final int count = getChildCount();
1523 for (int childIdx = 0; childIdx < count; childIdx++) {
1524 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
1525 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -08001526 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001527 continue;
1528 }
1529 float childTop = slidingChild.getTranslationY();
1530 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001531 float bottom = childTop + slidingChild.getActualHeight()
1532 - slidingChild.getClipBottomAmount();
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01001533
1534 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
1535 if (dist < minDist) {
1536 closestChild = slidingChild;
1537 minDist = dist;
1538 }
1539 }
1540 return closestChild;
1541 }
1542
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001543 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
1544 private ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek34ed7c02017-09-08 15:03:12 -07001545 return getChildAtPosition(touchX, touchY, true /* requireMinHeight */);
1546
1547 }
1548
1549 /**
1550 * Get the child at a certain screen location.
1551 *
Jason Monke59dc402018-08-16 12:05:01 -04001552 * @param touchX the x coordinate
1553 * @param touchY the y coordinate
Selim Cinek34ed7c02017-09-08 15:03:12 -07001554 * @param requireMinHeight Whether a minimum height is required for a child to be returned.
1555 * @return the child at the given location.
1556 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001557 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek34ed7c02017-09-08 15:03:12 -07001558 private ExpandableView getChildAtPosition(float touchX, float touchY,
1559 boolean requireMinHeight) {
Selim Cinek67b22602014-03-10 15:40:16 +01001560 // find the view under the pointer, accounting for GONE views
1561 final int count = getChildCount();
1562 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001563 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek51d21972017-07-19 17:39:20 -07001564 if (slidingChild.getVisibility() != VISIBLE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -08001565 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +01001566 continue;
1567 }
Selim Cinek89faff12014-06-19 16:29:04 -07001568 float childTop = slidingChild.getTranslationY();
1569 float top = childTop + slidingChild.getClipTopAmount();
Selim Cineka686b2c2016-10-26 13:58:27 -07001570 float bottom = childTop + slidingChild.getActualHeight()
1571 - slidingChild.getClipBottomAmount();
Jorim Jaggi28f0e592014-08-05 22:03:07 +02001572
1573 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
1574 // camera affordance).
1575 int left = 0;
1576 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +01001577
Selim Cinek34ed7c02017-09-08 15:03:12 -07001578 if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
Selim Cinek51d21972017-07-19 17:39:20 -07001579 && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001580 if (slidingChild instanceof ExpandableNotificationRow) {
1581 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Ned Burnsf81c4c42019-01-07 14:10:43 -05001582 NotificationEntry entry = row.getEntry();
Selim Cinek131c1e22015-05-11 19:04:49 -07001583 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Evan Laird94492852018-10-25 13:43:01 -04001584 && mHeadsUpManager.getTopEntry().getRow() != row
Selim Cinek5bc852a2015-12-21 12:19:09 -08001585 && mGroupManager.getGroupSummary(
Evan Laird94492852018-10-25 13:43:01 -04001586 mHeadsUpManager.getTopEntry().notification)
1587 != entry) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001588 continue;
1589 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001590 return row.getViewAtPosition(touchY - childTop);
1591 }
Selim Cinek67b22602014-03-10 15:40:16 +01001592 return slidingChild;
1593 }
1594 }
1595 return null;
1596 }
1597
Selim Cinek3d6ae232019-01-04 14:14:33 -08001598 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001599 getLocationOnScreen(mTempInt2);
1600 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001601 }
1602
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001603 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001604 public void setScrollingEnabled(boolean enable) {
1605 mScrollingEnabled = enable;
1606 }
1607
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001608 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04001609 public void lockScrollTo(View v) {
1610 if (mForcedScroll == v) {
1611 return;
1612 }
1613 mForcedScroll = v;
1614 scrollTo(v);
1615 }
1616
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001617 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ricky Waicd35def2016-05-03 11:07:07 +01001618 public boolean scrollTo(View v) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001619 ExpandableView expandableView = (ExpandableView) v;
Gus Prevas0fa58d62019-01-11 13:58:40 -05001620 if (ANCHOR_SCROLLING) {
1621 // TODO
1622 } else {
1623 int positionInLinearLayout = getPositionInLinearLayout(v);
1624 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
1625 int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
Ricky Waicd35def2016-05-03 11:07:07 +01001626
Gus Prevas0fa58d62019-01-11 13:58:40 -05001627 // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
1628 // that it is not visible anymore.
1629 if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
1630 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1631 mDontReportNextOverScroll = true;
1632 animateScroll();
1633 return true;
1634 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001635 }
Ricky Waicd35def2016-05-03 11:07:07 +01001636 return false;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001637 }
1638
Adrian Roos181385c2016-05-05 17:45:44 -04001639 /**
1640 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
Jason Monke59dc402018-08-16 12:05:01 -04001641 * the IME.
Adrian Roos181385c2016-05-05 17:45:44 -04001642 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001643 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Adrian Roos181385c2016-05-05 17:45:44 -04001644 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1645 return positionInLinearLayout + v.getIntrinsicHeight() +
felkachang529bfe62018-07-04 12:51:44 +08001646 getImeInset() - getHeight()
1647 + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
Adrian Roos181385c2016-05-05 17:45:44 -04001648 }
1649
Adrian Roos5153d4a2016-03-22 10:01:56 -07001650 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001651 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Adrian Roos5153d4a2016-03-22 10:01:56 -07001652 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Selim Cineka424c502016-04-05 13:07:54 -07001653 mBottomInset = insets.getSystemWindowInsetBottom();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001654
Gus Prevas0fa58d62019-01-11 13:58:40 -05001655 if (ANCHOR_SCROLLING) {
1656 // TODO
1657 } else {
1658 int range = getScrollRange();
1659 if (mOwnScrollY > range) {
1660 // HACK: We're repeatedly getting staggered insets here while the IME is
1661 // animating away. To work around that we'll wait until things have settled.
1662 removeCallbacks(mReclamp);
1663 postDelayed(mReclamp, 50);
1664 } else if (mForcedScroll != null) {
1665 // The scroll was requested before we got the actual inset - in case we need
1666 // to scroll up some more do so now.
1667 scrollTo(mForcedScroll);
1668 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001669 }
1670 return insets;
1671 }
1672
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001673 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Adrian Roos5153d4a2016-03-22 10:01:56 -07001674 private Runnable mReclamp = new Runnable() {
1675 @Override
1676 public void run() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001677 if (ANCHOR_SCROLLING) {
1678 // TODO
1679 } else {
1680 int range = getScrollRange();
1681 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1682 }
Adrian Roos5153d4a2016-03-22 10:01:56 -07001683 mDontReportNextOverScroll = true;
1684 mDontClampNextScroll = true;
Selim Cinek9212de82017-02-06 16:04:28 -08001685 animateScroll();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001686 }
1687 };
1688
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001689 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
1690 public void setExpandingEnabled(boolean enable) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001691 mExpandHelper.setEnabled(enable);
1692 }
1693
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001694 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02001695 private boolean isScrollingEnabled() {
1696 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +01001697 }
1698
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001699 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbc243a92016-09-27 16:35:13 -07001700 private boolean onKeyguard() {
Selim Cinek355652a2016-12-07 13:32:12 -08001701 return mStatusBarState == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +02001702 }
1703
Selim Cinek67b22602014-03-10 15:40:16 +01001704 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001705 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01001706 protected void onConfigurationChanged(Configuration newConfig) {
1707 super.onConfigurationChanged(newConfig);
Adrian Roos22af6502018-02-22 16:57:08 +01001708 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
Selim Cinek67b22602014-03-10 15:40:16 +01001709 float densityScale = getResources().getDisplayMetrics().density;
1710 mSwipeHelper.setDensityScale(densityScale);
1711 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1712 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1713 initView(getContext());
1714 }
1715
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001716 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dan Sandlereceda3d2014-07-21 15:35:01 -04001717 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07001718 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1719 true /* isDismissAll */);
Selim Cinek67b22602014-03-10 15:40:16 +01001720 }
1721
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001722 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001723 private void snapViewIfNeeded(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04001724 ExpandableNotificationRow child = entry.getRow();
dongwan0605.kim30637e42016-03-02 17:16:47 +09001725 boolean animate = mIsExpanded || isPinnedHeadsUp(child);
Mady Mellor95d743c2017-01-10 12:05:27 -08001726 // If the child is showing the notification menu snap to that
Evan Lairde55c6012019-03-13 12:54:37 -04001727 if (child.getProvider() != null) {
1728 float targetLeft = child.getProvider().isMenuVisible() ? child.getTranslation() : 0;
1729 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
1730 }
dongwan0605.kim30637e42016-03-02 17:16:47 +09001731 }
1732
Selim Cinek67b22602014-03-10 15:40:16 +01001733 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001734 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Ned Burnsf81c4c42019-01-07 14:10:43 -05001735 public ViewGroup getViewParentForNotification(NotificationEntry entry) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09001736 return this;
1737 }
1738
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001739 /**
1740 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1741 *
1742 * @param deltaY The amount to scroll upwards, has to be positive.
1743 * @return The amount of scrolling to be performed by the scroller,
Jason Monke59dc402018-08-16 12:05:01 -04001744 * not handled by the overScroll amount.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001745 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04001746 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001747 private float overScrollUp(int deltaY, int range) {
1748 deltaY = Math.max(deltaY, 0);
1749 float currentTopAmount = getCurrentOverScrollAmount(true);
1750 float newTopAmount = currentTopAmount - deltaY;
1751 if (currentTopAmount > 0) {
1752 setOverScrollAmount(newTopAmount, true /* onTop */,
1753 false /* animate */);
1754 }
1755 // Top overScroll might not grab all scrolling motion,
1756 // we have to scroll as well.
Gus Prevas0fa58d62019-01-11 13:58:40 -05001757 if (ANCHOR_SCROLLING) {
1758 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1759 // TODO: once we're recycling this will need to check the adapter position of the child
1760 ExpandableView lastRow = getLastRowNotGone();
Gus Prevascdc98342019-01-14 14:29:44 -05001761 if (lastRow != null && !lastRow.isInShelf()) {
1762 float distanceToMax = Math.max(0, getMaxPositiveScrollAmount());
1763 if (scrollAmount > distanceToMax) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001764 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1765 // We overScroll on the bottom
1766 setOverScrolledPixels(currentBottomPixels + (scrollAmount - distanceToMax),
1767 false /* onTop */,
1768 false /* animate */);
1769 mScrollAnchorViewY -= distanceToMax;
1770 scrollAmount = 0f;
1771 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001772 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001773 return scrollAmount;
1774 } else {
1775 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1776 float newScrollY = mOwnScrollY + scrollAmount;
1777 if (newScrollY > range) {
1778 if (!mExpandedInThisMotion) {
1779 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1780 // We overScroll on the bottom
1781 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1782 false /* onTop */,
1783 false /* animate */);
1784 }
1785 setOwnScrollY(range);
1786 scrollAmount = 0.0f;
1787 }
1788 return scrollAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001789 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001790 }
1791
1792 /**
1793 * Perform a scroll downward and adapt the overscroll amounts accordingly
1794 *
1795 * @param deltaY The amount to scroll downwards, has to be negative.
1796 * @return The amount of scrolling to be performed by the scroller,
Jason Monke59dc402018-08-16 12:05:01 -04001797 * not handled by the overScroll amount.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001798 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001799 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001800 private float overScrollDown(int deltaY) {
1801 deltaY = Math.min(deltaY, 0);
1802 float currentBottomAmount = getCurrentOverScrollAmount(false);
1803 float newBottomAmount = currentBottomAmount + deltaY;
1804 if (currentBottomAmount > 0) {
1805 setOverScrollAmount(newBottomAmount, false /* onTop */,
1806 false /* animate */);
1807 }
1808 // Bottom overScroll might not grab all scrolling motion,
1809 // we have to scroll as well.
Gus Prevas0fa58d62019-01-11 13:58:40 -05001810 if (ANCHOR_SCROLLING) {
1811 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1812 // TODO: once we're recycling this will need to check the adapter position of the child
1813 ExpandableView firstChild = getFirstChildNotGone();
1814 float top = firstChild.getTranslationY();
1815 float distanceToTop = mScrollAnchorView.getTranslationY() - top - mScrollAnchorViewY;
1816 if (distanceToTop < -scrollAmount) {
1817 float currentTopPixels = getCurrentOverScrolledPixels(true);
1818 // We overScroll on the top
1819 setOverScrolledPixels(currentTopPixels + (-scrollAmount - distanceToTop),
1820 true /* onTop */,
1821 false /* animate */);
1822 mScrollAnchorView = firstChild;
1823 mScrollAnchorViewY = 0;
1824 scrollAmount = 0f;
1825 }
1826 return scrollAmount;
1827 } else {
1828 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1829 float newScrollY = mOwnScrollY + scrollAmount;
1830 if (newScrollY < 0) {
1831 float currentTopPixels = getCurrentOverScrolledPixels(true);
1832 // We overScroll on the top
1833 setOverScrolledPixels(currentTopPixels - newScrollY,
1834 true /* onTop */,
1835 false /* animate */);
1836 setOwnScrollY(0);
1837 scrollAmount = 0.0f;
1838 }
1839 return scrollAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001840 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001841 }
1842
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001843 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001844 private void initVelocityTrackerIfNotExists() {
1845 if (mVelocityTracker == null) {
1846 mVelocityTracker = VelocityTracker.obtain();
1847 }
1848 }
1849
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001850 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001851 private void recycleVelocityTracker() {
1852 if (mVelocityTracker != null) {
1853 mVelocityTracker.recycle();
1854 mVelocityTracker = null;
1855 }
1856 }
1857
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001858 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01001859 private void initOrResetVelocityTracker() {
1860 if (mVelocityTracker == null) {
1861 mVelocityTracker = VelocityTracker.obtain();
1862 } else {
1863 mVelocityTracker.clear();
1864 }
1865 }
1866
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001867 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ricky Waicd35def2016-05-03 11:07:07 +01001868 public void setFinishScrollingCallback(Runnable runnable) {
1869 mFinishScrollingCallback = runnable;
1870 }
1871
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001872 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek9212de82017-02-06 16:04:28 -08001873 private void animateScroll() {
Selim Cinek67b22602014-03-10 15:40:16 +01001874 if (mScroller.computeScrollOffset()) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001875 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05001876 int oldY = mLastScrollerY;
1877 int y = mScroller.getCurrY();
1878 int deltaY = y - oldY;
1879 if (deltaY != 0) {
1880 int maxNegativeScrollAmount = getMaxNegativeScrollAmount();
1881 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
1882 if ((maxNegativeScrollAmount < 0 && deltaY < maxNegativeScrollAmount)
1883 || (maxPositiveScrollAmount > 0 && deltaY > maxPositiveScrollAmount)) {
1884 // This frame takes us into overscroll, so set the max overscroll based on
1885 // the current velocity
1886 setMaxOverScrollFromCurrentVelocity();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001887 }
Gus Prevascdc98342019-01-14 14:29:44 -05001888 customOverScrollBy(deltaY, oldY, 0, (int) mMaxOverScroll);
1889 mLastScrollerY = y;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001890 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05001891 } else {
1892 int oldY = mOwnScrollY;
1893 int y = mScroller.getCurrY();
Selim Cinek67b22602014-03-10 15:40:16 +01001894
Gus Prevas0fa58d62019-01-11 13:58:40 -05001895 if (oldY != y) {
1896 int range = getScrollRange();
1897 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
Gus Prevascdc98342019-01-14 14:29:44 -05001898 // This frame takes us into overscroll, so set the max overscroll based on
1899 // the current velocity
1900 setMaxOverScrollFromCurrentVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +01001901 }
Selim Cinek67b22602014-03-10 15:40:16 +01001902
Gus Prevas0fa58d62019-01-11 13:58:40 -05001903 if (mDontClampNextScroll) {
1904 range = Math.max(range, oldY);
1905 }
1906 customOverScrollBy(y - oldY, oldY, range,
1907 (int) (mMaxOverScroll));
Adrian Roos5153d4a2016-03-22 10:01:56 -07001908 }
Selim Cinek67b22602014-03-10 15:40:16 +01001909 }
1910
Gus Prevascdc98342019-01-14 14:29:44 -05001911 postOnAnimation(mReflingAndAnimateScroll);
Adrian Roos5153d4a2016-03-22 10:01:56 -07001912 } else {
1913 mDontClampNextScroll = false;
Ricky Waicd35def2016-05-03 11:07:07 +01001914 if (mFinishScrollingCallback != null) {
1915 mFinishScrollingCallback.run();
1916 }
Selim Cinek67b22602014-03-10 15:40:16 +01001917 }
1918 }
1919
Gus Prevascdc98342019-01-14 14:29:44 -05001920 private void setMaxOverScrollFromCurrentVelocity() {
1921 float currVelocity = mScroller.getCurrVelocity();
1922 if (currVelocity >= mMinimumVelocity) {
1923 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
Selim Cinek4195dd02014-05-19 18:16:14 +02001924 }
Gus Prevascdc98342019-01-14 14:29:44 -05001925 }
Selim Cinek4195dd02014-05-19 18:16:14 +02001926
Gus Prevascdc98342019-01-14 14:29:44 -05001927 /**
1928 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
1929 * would cause us to exceed the provided maximum overscroll, springs back instead.
1930 *
1931 * This method performs the determination of whether we're exceeding the overscroll and clamps
1932 * the scroll amount if so. The actual scrolling/overscrolling happens in
1933 * {@link #onCustomOverScrolled(int, boolean)} (absolute scrolling) or
1934 * {@link #onCustomOverScrolledBy(int, boolean)} (anchor scrolling).
1935 *
1936 * @param deltaY The (signed) number of pixels to scroll.
1937 * @param scrollY The current scroll position (absolute scrolling only).
1938 * @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
1939 * @param maxOverScrollY The current (unsigned) limit on number of pixels to overscroll by.
1940 */
Selim Cinek4195dd02014-05-19 18:16:14 +02001941 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Gus Prevascdc98342019-01-14 14:29:44 -05001942 private void customOverScrollBy(int deltaY, int scrollY, int scrollRangeY, int maxOverScrollY) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05001943 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05001944 boolean clampedY = false;
1945 if (deltaY < 0) {
1946 int maxScrollAmount = getMaxNegativeScrollAmount();
1947 if (maxScrollAmount > Integer.MIN_VALUE) {
1948 maxScrollAmount -= maxOverScrollY;
1949 if (deltaY < maxScrollAmount) {
1950 deltaY = maxScrollAmount;
1951 clampedY = true;
1952 }
1953 }
1954 } else {
1955 int maxScrollAmount = getMaxPositiveScrollAmount();
1956 if (maxScrollAmount < Integer.MAX_VALUE) {
1957 maxScrollAmount += maxOverScrollY;
1958 if (deltaY > maxScrollAmount) {
1959 deltaY = maxScrollAmount;
1960 clampedY = true;
1961 }
1962 }
1963 }
1964 onCustomOverScrolledBy(deltaY, clampedY);
Gus Prevas0fa58d62019-01-11 13:58:40 -05001965 } else {
1966 int newScrollY = scrollY + deltaY;
1967 final int top = -maxOverScrollY;
1968 final int bottom = maxOverScrollY + scrollRangeY;
Selim Cinek4195dd02014-05-19 18:16:14 +02001969
Gus Prevas0fa58d62019-01-11 13:58:40 -05001970 boolean clampedY = false;
1971 if (newScrollY > bottom) {
1972 newScrollY = bottom;
1973 clampedY = true;
1974 } else if (newScrollY < top) {
1975 newScrollY = top;
1976 clampedY = true;
1977 }
Selim Cinek4195dd02014-05-19 18:16:14 +02001978
Gus Prevas0fa58d62019-01-11 13:58:40 -05001979 onCustomOverScrolled(newScrollY, clampedY);
Selim Cinek4195dd02014-05-19 18:16:14 +02001980 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001981 }
1982
1983 /**
1984 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1985 * overscroll effect based on numPixels. By default this will also cancel animations on the
1986 * same overScroll edge.
1987 *
1988 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1989 * the rubber-banding logic.
Jason Monke59dc402018-08-16 12:05:01 -04001990 * @param onTop Should the effect be applied on top of the scroller.
1991 * @param animate Should an animation be performed.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001992 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04001993 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001994 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001995 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001996 }
1997
1998 /**
1999 * Set the effective overScroll amount which will be directly reflected in the layout.
2000 * By default this will also cancel animations on the same overScroll edge.
2001 *
Jason Monke59dc402018-08-16 12:05:01 -04002002 * @param amount The amount to overScroll by.
2003 * @param onTop Should the effect be applied on top of the scroller.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002004 * @param animate Should an animation be performed.
2005 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002006
2007 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002008 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
2009 setOverScrollAmount(amount, onTop, animate, true);
2010 }
2011
2012 /**
2013 * Set the effective overScroll amount which will be directly reflected in the layout.
2014 *
Jason Monke59dc402018-08-16 12:05:01 -04002015 * @param amount The amount to overScroll by.
2016 * @param onTop Should the effect be applied on top of the scroller.
2017 * @param animate Should an animation be performed.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002018 * @param cancelAnimators Should running animations be cancelled.
2019 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002020 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002021 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2022 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002023 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
2024 }
2025
2026 /**
2027 * Set the effective overScroll amount which will be directly reflected in the layout.
2028 *
Jason Monke59dc402018-08-16 12:05:01 -04002029 * @param amount The amount to overScroll by.
2030 * @param onTop Should the effect be applied on top of the scroller.
2031 * @param animate Should an animation be performed.
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002032 * @param cancelAnimators Should running animations be cancelled.
Jason Monke59dc402018-08-16 12:05:01 -04002033 * @param isRubberbanded The value which will be passed to
2034 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002035 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002036 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002037 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
2038 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002039 if (cancelAnimators) {
2040 mStateAnimator.cancelOverScrollAnimators(onTop);
2041 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002042 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002043 }
2044
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002045 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002046 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
2047 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002048 amount = Math.max(0, amount);
2049 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002050 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002051 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07002052 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002053 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002054 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002055 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002056 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002057 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002058 }
2059 }
2060
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002061 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002062 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002063 mExpandHelper.onlyObserveMovements(amount > 1.0f);
2064 if (mDontReportNextOverScroll) {
2065 mDontReportNextOverScroll = false;
2066 return;
2067 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02002068 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002069 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002070 }
2071 }
2072
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002073 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi290600a2014-05-30 17:02:20 +02002074 public void setOverscrollTopChangedListener(
2075 OnOverscrollTopChangedListener overscrollTopChangedListener) {
2076 mOverscrollTopChangedListener = overscrollTopChangedListener;
2077 }
2078
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002079 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002080 public float getCurrentOverScrollAmount(boolean top) {
2081 return mAmbientState.getOverScrollAmount(top);
2082 }
2083
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002084 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002085 public float getCurrentOverScrolledPixels(boolean top) {
Jason Monke59dc402018-08-16 12:05:01 -04002086 return top ? mOverScrolledTopPixels : mOverScrolledBottomPixels;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002087 }
2088
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002089 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002090 private void setOverScrolledPixels(float amount, boolean onTop) {
2091 if (onTop) {
2092 mOverScrolledTopPixels = amount;
2093 } else {
2094 mOverScrolledBottomPixels = amount;
2095 }
2096 }
2097
Gus Prevascdc98342019-01-14 14:29:44 -05002098 /**
2099 * Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
2100 * would cause us to exceed the provided maximum overscroll, springs back instead.
2101 *
2102 * @param deltaY The (signed) number of pixels to scroll.
2103 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2104 * the overscroll limit.
2105 */
2106 private void onCustomOverScrolledBy(int deltaY, boolean clampedY) {
2107 assert ANCHOR_SCROLLING;
Gus Prevas0fa58d62019-01-11 13:58:40 -05002108 mScrollAnchorViewY -= deltaY;
2109 // Treat animating scrolls differently; see #computeScroll() for why.
2110 if (!mScroller.isFinished()) {
Gus Prevascdc98342019-01-14 14:29:44 -05002111 if (clampedY) {
2112 springBack();
2113 } else {
2114 float overScrollTop = getCurrentOverScrollAmount(true /* top */);
2115 if (isScrolledToTop() && mScrollAnchorViewY > 0) {
2116 notifyOverscrollTopListener(mScrollAnchorViewY,
2117 isRubberbanded(true /* onTop */));
2118 } else {
2119 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true /* onTop */));
2120 }
2121 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002122 }
2123 updateScrollAnchor();
2124 updateOnScrollChange();
2125 }
2126
Gus Prevascdc98342019-01-14 14:29:44 -05002127 /**
2128 * Scrolls to the given position, overscrolling if needed. If called during a fling and the
2129 * position exceeds the provided maximum overscroll, springs back instead.
2130 *
2131 * @param scrollY The target scroll position.
2132 * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
2133 * the overscroll limit.
2134 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002135 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek9212de82017-02-06 16:04:28 -08002136 private void onCustomOverScrolled(int scrollY, boolean clampedY) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002137 assert !ANCHOR_SCROLLING;
Selim Cinek67b22602014-03-10 15:40:16 +01002138 // Treat animating scrolls differently; see #computeScroll() for why.
2139 if (!mScroller.isFinished()) {
Selim Cinekef406062016-09-29 17:33:13 -07002140 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01002141 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002142 springBack();
2143 } else {
Jorim Jaggi290600a2014-05-30 17:02:20 +02002144 float overScrollTop = getCurrentOverScrollAmount(true);
2145 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002146 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02002147 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002148 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02002149 }
Selim Cinek67b22602014-03-10 15:40:16 +01002150 }
Selim Cinek67b22602014-03-10 15:40:16 +01002151 } else {
Selim Cinek9212de82017-02-06 16:04:28 -08002152 setOwnScrollY(scrollY);
Selim Cinek67b22602014-03-10 15:40:16 +01002153 }
2154 }
2155
Gus Prevascdc98342019-01-14 14:29:44 -05002156 /**
2157 * Springs back from an overscroll by stopping the {@link #mScroller} and animating the
2158 * overscroll amount back to zero.
2159 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002160 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002161 private void springBack() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002162 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05002163 boolean overScrolledTop = isScrolledToTop() && mScrollAnchorViewY > 0;
2164 int maxPositiveScrollAmount = getMaxPositiveScrollAmount();
2165 boolean overscrolledBottom = maxPositiveScrollAmount < 0;
2166 if (overScrolledTop || overscrolledBottom) {
2167 float newAmount;
2168 if (overScrolledTop) {
2169 newAmount = mScrollAnchorViewY;
2170 mScrollAnchorViewY = 0;
2171 mDontReportNextOverScroll = true;
2172 } else {
2173 newAmount = -maxPositiveScrollAmount;
2174 mScrollAnchorViewY -= maxPositiveScrollAmount;
2175 }
2176 setOverScrollAmount(newAmount, overScrolledTop, false);
2177 setOverScrollAmount(0.0f, overScrolledTop, true);
2178 mScroller.forceFinished(true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002179 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002180 } else {
2181 int scrollRange = getScrollRange();
2182 boolean overScrolledTop = mOwnScrollY <= 0;
2183 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
2184 if (overScrolledTop || overScrolledBottom) {
2185 boolean onTop;
2186 float newAmount;
2187 if (overScrolledTop) {
2188 onTop = true;
2189 newAmount = -mOwnScrollY;
2190 setOwnScrollY(0);
2191 mDontReportNextOverScroll = true;
2192 } else {
2193 onTop = false;
2194 newAmount = mOwnScrollY - scrollRange;
2195 setOwnScrollY(scrollRange);
2196 }
2197 setOverScrollAmount(newAmount, onTop, false);
2198 setOverScrollAmount(0.0f, onTop, true);
2199 mScroller.forceFinished(true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002200 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002201 }
2202 }
2203
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002204 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek67b22602014-03-10 15:40:16 +01002205 private int getScrollRange() {
felkachang529bfe62018-07-04 12:51:44 +08002206 // In current design, it only use the top HUN to treat all of HUNs
2207 // although there are more than one HUNs
2208 int contentHeight = mContentHeight;
2209 if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
2210 contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
2211 }
2212 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
Selim Cineka424c502016-04-05 13:07:54 -07002213 int imeInset = getImeInset();
felkachang529bfe62018-07-04 12:51:44 +08002214 scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
Selim Cineka424c502016-04-05 13:07:54 -07002215 return scrollRange;
2216 }
2217
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002218 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cineka424c502016-04-05 13:07:54 -07002219 private int getImeInset() {
2220 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
Selim Cinek67b22602014-03-10 15:40:16 +01002221 }
2222
Selim Cinek343e6e22014-04-11 21:23:30 +02002223 /**
2224 * @return the first child which has visibility unequal to GONE
2225 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002226 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb55386d2015-12-16 17:26:49 -08002227 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02002228 int childCount = getChildCount();
2229 for (int i = 0; i < childCount; i++) {
2230 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002231 if (child.getVisibility() != View.GONE && child != mShelf) {
Selim Cinek816c8e42015-11-19 12:00:45 -08002232 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02002233 }
2234 }
2235 return null;
2236 }
2237
Selim Cinek4a1ac842014-05-01 15:51:58 +02002238 /**
Selim Cinek1b2a05e2016-04-28 14:20:39 -07002239 * @return the child before the given view which has visibility unequal to GONE
2240 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002241 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1b2a05e2016-04-28 14:20:39 -07002242 public ExpandableView getViewBeforeView(ExpandableView view) {
2243 ExpandableView previousView = null;
2244 int childCount = getChildCount();
2245 for (int i = 0; i < childCount; i++) {
2246 View child = getChildAt(i);
2247 if (child == view) {
2248 return previousView;
2249 }
2250 if (child.getVisibility() != View.GONE) {
2251 previousView = (ExpandableView) child;
2252 }
2253 }
2254 return null;
2255 }
2256
2257 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002258 * @return The first child which has visibility unequal to GONE which is currently below the
Jason Monke59dc402018-08-16 12:05:01 -04002259 * given translationY or equal to it.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002260 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002261 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekef8c2252017-02-10 14:52:18 -08002262 private View getFirstChildBelowTranlsationY(float translationY, boolean ignoreChildren) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002263 int childCount = getChildCount();
2264 for (int i = 0; i < childCount; i++) {
2265 View child = getChildAt(i);
Selim Cinekef8c2252017-02-10 14:52:18 -08002266 if (child.getVisibility() == View.GONE) {
2267 continue;
2268 }
2269 float rowTranslation = child.getTranslationY();
2270 if (rowTranslation >= translationY) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002271 return child;
Selim Cinekef8c2252017-02-10 14:52:18 -08002272 } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
2273 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2274 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
2275 List<ExpandableNotificationRow> notificationChildren =
2276 row.getNotificationChildren();
2277 for (int childIndex = 0; childIndex < notificationChildren.size();
2278 childIndex++) {
2279 ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
2280 if (rowChild.getTranslationY() + rowTranslation >= translationY) {
2281 return rowChild;
2282 }
2283 }
2284 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002285 }
2286 }
2287 return null;
2288 }
2289
2290 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02002291 * @return the last child which has visibility unequal to GONE
2292 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002293 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002294 public ExpandableView getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02002295 int childCount = getChildCount();
2296 for (int i = childCount - 1; i >= 0; i--) {
2297 View child = getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002298 if (child.getVisibility() != View.GONE && child != mShelf) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002299 return (ExpandableView) child;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002300 }
2301 }
2302 return null;
2303 }
2304
Gus Prevas0fa58d62019-01-11 13:58:40 -05002305 private ExpandableNotificationRow getLastRowNotGone() {
2306 int childCount = getChildCount();
2307 for (int i = childCount - 1; i >= 0; i--) {
2308 View child = getChildAt(i);
2309 if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
2310 return (ExpandableNotificationRow) child;
2311 }
2312 }
2313 return null;
2314 }
2315
Jorim Jaggi069cd032014-05-15 03:09:01 +02002316 /**
2317 * @return the number of children which have visibility unequal to GONE
2318 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002319 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi069cd032014-05-15 03:09:01 +02002320 public int getNotGoneChildCount() {
2321 int childCount = getChildCount();
2322 int count = 0;
2323 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07002324 ExpandableView child = (ExpandableView) getChildAt(i);
Selim Cinekdb167372016-11-17 15:41:17 -08002325 if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02002326 count++;
2327 }
2328 }
2329 return count;
2330 }
2331
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002332 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek67b22602014-03-10 15:40:16 +01002333 private void updateContentHeight() {
2334 int height = 0;
Selim Cineka7ed2c12017-01-23 20:47:24 -08002335 float previousPaddingRequest = mPaddingBetweenElements;
2336 float previousPaddingAmount = 0.0f;
Selim Cinekad7fac02016-10-18 17:09:15 -07002337 int numShownItems = 0;
2338 boolean finish = false;
Selim Cinek5040f2e2019-02-14 18:22:42 -08002339 int maxDisplayedNotifications = mMaxDisplayedNotifications;
Adrian Roos7a9551a2017-01-11 12:27:49 -08002340
Selim Cinek67b22602014-03-10 15:40:16 +01002341 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002342 ExpandableView expandableView = (ExpandableView) getChildAt(i);
Lucas Dupin60661a62018-04-12 10:50:13 -07002343 boolean footerViewOnLockScreen = expandableView == mFooterView && onKeyguard();
Selim Cinek281c2022016-10-13 19:14:43 -07002344 if (expandableView.getVisibility() != View.GONE
Lucas Dupin60661a62018-04-12 10:50:13 -07002345 && !expandableView.hasNoContentHeight() && !footerViewOnLockScreen) {
Adrian Roos7d062c42017-03-30 15:11:43 -07002346 boolean limitReached = maxDisplayedNotifications != -1
2347 && numShownItems >= maxDisplayedNotifications;
Selim Cinek5040f2e2019-02-14 18:22:42 -08002348 if (limitReached) {
Selim Cinekad7fac02016-10-18 17:09:15 -07002349 expandableView = mShelf;
2350 finish = true;
2351 }
Selim Cinek42357e02016-02-24 18:48:01 -08002352 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Selim Cineka7ed2c12017-01-23 20:47:24 -08002353 float padding;
2354 if (increasedPaddingAmount >= 0.0f) {
2355 padding = (int) NotificationUtils.interpolate(
2356 previousPaddingRequest,
2357 mIncreasedPaddingBetweenElements,
2358 increasedPaddingAmount);
2359 previousPaddingRequest = (int) NotificationUtils.interpolate(
Selim Cinek42357e02016-02-24 18:48:01 -08002360 mPaddingBetweenElements,
2361 mIncreasedPaddingBetweenElements,
Selim Cineka7ed2c12017-01-23 20:47:24 -08002362 increasedPaddingAmount);
2363 } else {
2364 int ownPadding = (int) NotificationUtils.interpolate(
2365 0,
2366 mPaddingBetweenElements,
2367 1.0f + increasedPaddingAmount);
2368 if (previousPaddingAmount > 0.0f) {
2369 padding = (int) NotificationUtils.interpolate(
2370 ownPadding,
2371 mIncreasedPaddingBetweenElements,
2372 previousPaddingAmount);
2373 } else {
2374 padding = ownPadding;
2375 }
2376 previousPaddingRequest = ownPadding;
Jorim Jaggid4a57442014-04-10 02:45:55 +02002377 }
Selim Cineka7ed2c12017-01-23 20:47:24 -08002378 if (height != 0) {
2379 height += padding;
2380 }
2381 previousPaddingAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002382 height += expandableView.getIntrinsicHeight();
Selim Cinekad7fac02016-10-18 17:09:15 -07002383 numShownItems++;
2384 if (finish) {
2385 break;
2386 }
Selim Cinek67b22602014-03-10 15:40:16 +01002387 }
2388 }
Lucas Dupin60661a62018-04-12 10:50:13 -07002389 mIntrinsicContentHeight = height;
Selim Cinekf4b04ae2018-06-13 18:23:45 -07002390
Lucas Dupin00be88f2019-01-03 17:50:52 -08002391 mContentHeight = height + mTopPadding + mBottomMargin;
Selim Cinekc22fff62016-05-20 12:44:30 -07002392 updateScrollability();
Selim Cinek51d21972017-07-19 17:39:20 -07002393 clampScrollPosition();
Selim Cinek91d4cba2016-11-10 19:59:48 -08002394 mAmbientState.setLayoutMaxHeight(mContentHeight);
Selim Cinekc22fff62016-05-20 12:44:30 -07002395 }
2396
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09002397 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002398 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbe2c4432017-05-30 12:11:09 -07002399 public boolean hasPulsingNotifications() {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09002400 return mPulsing;
Adrian Roos7d062c42017-03-30 15:11:43 -07002401 }
2402
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002403 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07002404 private void updateScrollability() {
Riddle Hsu065c01c2018-05-10 23:14:19 +08002405 boolean scrollable = !mQsExpanded && getScrollRange() > 0;
Selim Cinekc22fff62016-05-20 12:44:30 -07002406 if (scrollable != mScrollable) {
2407 mScrollable = scrollable;
2408 setFocusable(scrollable);
Selim Cinekef406062016-09-29 17:33:13 -07002409 updateForwardAndBackwardScrollability();
2410 }
2411 }
2412
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002413 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekef406062016-09-29 17:33:13 -07002414 private void updateForwardAndBackwardScrollability() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002415 boolean forwardScrollable = mScrollable && !isScrolledToBottom();
2416 boolean backwardsScrollable = mScrollable && !isScrolledToTop();
Selim Cinekef406062016-09-29 17:33:13 -07002417 boolean changed = forwardScrollable != mForwardScrollable
2418 || backwardsScrollable != mBackwardScrollable;
2419 mForwardScrollable = forwardScrollable;
2420 mBackwardScrollable = backwardsScrollable;
2421 if (changed) {
2422 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
Selim Cinekc22fff62016-05-20 12:44:30 -07002423 }
Selim Cinek67b22602014-03-10 15:40:16 +01002424 }
2425
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002426 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek6811d722016-01-19 17:53:12 -08002427 private void updateBackground() {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08002428 // No need to update the background color if it's not being drawn.
Lucas Dupin16cfe452018-02-08 13:14:50 -08002429 if (!mShouldDrawNotificationBackground || mAmbientState.isFullyDark()) {
Selim Cinek6811d722016-01-19 17:53:12 -08002430 return;
2431 }
Anthony Chen3cb3ad92016-12-01 10:58:47 -08002432
Selim Cinek6811d722016-01-19 17:53:12 -08002433 updateBackgroundBounds();
Gus Prevase2d6f042018-10-17 15:25:30 -04002434 if (didSectionBoundsChange()) {
2435 boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
2436 || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
Selim Cinek54680902016-10-19 16:49:44 -07002437 if (!isExpanded()) {
2438 abortBackgroundAnimators();
2439 animate = false;
2440 }
2441 if (animate) {
Selim Cinek614576e2016-01-20 10:54:09 -08002442 startBackgroundAnimation();
2443 } else {
Gus Prevase2d6f042018-10-17 15:25:30 -04002444 for (NotificationSection section : mSections) {
2445 section.resetCurrentBounds();
2446 }
Lucas Dupin90a38dd2018-09-05 09:37:37 -07002447 invalidate();
Selim Cinek614576e2016-01-20 10:54:09 -08002448 }
2449 } else {
Selim Cinek54680902016-10-19 16:49:44 -07002450 abortBackgroundAnimators();
Selim Cinek6811d722016-01-19 17:53:12 -08002451 }
Selim Cinek614576e2016-01-20 10:54:09 -08002452 mAnimateNextBackgroundTop = false;
Gus Prevase2d6f042018-10-17 15:25:30 -04002453 mAnimateNextBackgroundBottom = false;
2454 mAnimateNextSectionBoundsChange = false;
Selim Cinek614576e2016-01-20 10:54:09 -08002455 }
2456
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002457 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek54680902016-10-19 16:49:44 -07002458 private void abortBackgroundAnimators() {
Gus Prevase2d6f042018-10-17 15:25:30 -04002459 for (NotificationSection section : mSections) {
2460 section.cancelAnimators();
Selim Cinek54680902016-10-19 16:49:44 -07002461 }
2462 }
2463
Gus Prevase2d6f042018-10-17 15:25:30 -04002464 private boolean didSectionBoundsChange() {
2465 for (NotificationSection section : mSections) {
2466 if (section.didBoundsChange()) {
2467 return true;
2468 }
2469 }
2470 return false;
2471 }
2472
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002473 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Gus Prevase2d6f042018-10-17 15:25:30 -04002474 private boolean areSectionBoundsAnimating() {
2475 for (NotificationSection section : mSections) {
2476 if (section.areBoundsAnimating()) {
2477 return true;
2478 }
2479 }
2480 return false;
Selim Cinek614576e2016-01-20 10:54:09 -08002481 }
2482
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002483 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek614576e2016-01-20 10:54:09 -08002484 private void startBackgroundAnimation() {
Gus Prevase2d6f042018-10-17 15:25:30 -04002485 // TODO(kprevas): do we still need separate fields for top/bottom?
2486 // or can each section manage its own animation state?
2487 NotificationSection firstVisibleSection = getFirstVisibleSection();
2488 NotificationSection lastVisibleSection = getLastVisibleSection();
2489 for (NotificationSection section : mSections) {
2490 section.startBackgroundAnimation(
2491 section == firstVisibleSection
2492 ? mAnimateNextBackgroundTop
2493 : mAnimateNextSectionBoundsChange,
2494 section == lastVisibleSection
2495 ? mAnimateNextBackgroundBottom
2496 : mAnimateNextSectionBoundsChange);
Selim Cinek614576e2016-01-20 10:54:09 -08002497 }
Selim Cinek6811d722016-01-19 17:53:12 -08002498 }
2499
2500 /**
2501 * Update the background bounds to the new desired bounds
2502 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002503 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek6811d722016-01-19 17:53:12 -08002504 private void updateBackgroundBounds() {
shawnlin27716722019-04-17 20:09:45 +08002505 int left = mSidePaddings;
2506 int right = getWidth() - mSidePaddings;
Gus Prevase2d6f042018-10-17 15:25:30 -04002507 for (NotificationSection section : mSections) {
2508 section.getBounds().left = left;
2509 section.getBounds().right = right;
2510 }
shawnlin3a2a2e22018-05-04 17:09:50 +08002511
Selim Cinek614576e2016-01-20 10:54:09 -08002512 if (!mIsExpanded) {
Gus Prevase2d6f042018-10-17 15:25:30 -04002513 for (NotificationSection section : mSections) {
2514 section.getBounds().top = 0;
2515 section.getBounds().bottom = 0;
2516 }
Selim Cinek1791f502016-10-07 17:38:03 -04002517 return;
Selim Cinek614576e2016-01-20 10:54:09 -08002518 }
Selim Cinek3fe7e7e2019-02-15 18:40:53 -08002519 int minTopPosition = 0;
Gus Prevase2d6f042018-10-17 15:25:30 -04002520 NotificationSection lastSection = getLastVisibleSection();
Selim Cinek355652a2016-12-07 13:32:12 -08002521 if (mStatusBarState != StatusBarState.KEYGUARD) {
Selim Cinek3fe7e7e2019-02-15 18:40:53 -08002522 minTopPosition = (int) (mTopPadding + mStackTranslation);
2523 } else if (lastSection == null) {
2524 minTopPosition = mTopPadding;
Selim Cinek3776fe02016-02-04 13:32:43 -08002525 }
Selim Cinekae55d832019-02-22 17:43:43 -08002526 boolean shiftPulsingWithFirst = mAmbientPulseManager.getAllEntries().count() <= 1;
Selim Cinek3fe7e7e2019-02-15 18:40:53 -08002527 for (NotificationSection section : mSections) {
2528 int minBottomPosition = minTopPosition;
2529 if (section == lastSection) {
2530 // We need to make sure the section goes all the way to the shelf
Selim Cinek3a1d2742019-03-11 18:38:29 -07002531 minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
2532 + mShelf.getIntrinsicHeight());
Gus Prevase83700cb2018-12-14 11:42:51 -05002533 }
Selim Cinekae55d832019-02-22 17:43:43 -08002534 minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
2535 shiftPulsingWithFirst);
2536 shiftPulsingWithFirst = false;
Gus Prevase83700cb2018-12-14 11:42:51 -05002537 }
Selim Cinek614576e2016-01-20 10:54:09 -08002538 }
2539
Gus Prevase2d6f042018-10-17 15:25:30 -04002540 private NotificationSection getFirstVisibleSection() {
2541 for (NotificationSection section : mSections) {
2542 if (section.getFirstVisibleChild() != null) {
2543 return section;
2544 }
2545 }
2546 return null;
2547 }
2548
2549 private NotificationSection getLastVisibleSection() {
2550 for (int i = mSections.length - 1; i >= 0; i--) {
2551 NotificationSection section = mSections[i];
2552 if (section.getLastVisibleChild() != null) {
2553 return section;
2554 }
2555 }
2556 return null;
2557 }
2558
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002559 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek614576e2016-01-20 10:54:09 -08002560 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002561 int childCount = getChildCount();
2562 for (int i = childCount - 1; i >= 0; i--) {
2563 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002564 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2565 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002566 return (ActivatableNotificationView) child;
2567 }
2568 }
2569 return null;
2570 }
2571
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002572 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek614576e2016-01-20 10:54:09 -08002573 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08002574 int childCount = getChildCount();
2575 for (int i = 0; i < childCount; i++) {
2576 View child = getChildAt(i);
Selim Cinek48ff9b42016-11-09 19:31:51 -08002577 if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
2578 && child != mShelf) {
Selim Cinek6811d722016-01-19 17:53:12 -08002579 return (ActivatableNotificationView) child;
2580 }
2581 }
2582 return null;
2583 }
2584
Selim Cinek67b22602014-03-10 15:40:16 +01002585 /**
2586 * Fling the scroll view
2587 *
2588 * @param velocityY The initial velocity in the Y direction. Positive
2589 * numbers mean that the finger/cursor is moving down the screen,
2590 * which means we want to scroll towards the top.
2591 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002592 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Muyuan Li26e30ae2016-04-11 17:31:42 -07002593 protected void fling(int velocityY) {
Selim Cinek67b22602014-03-10 15:40:16 +01002594 if (getChildCount() > 0) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002595 float topAmount = getCurrentOverScrollAmount(true);
2596 float bottomAmount = getCurrentOverScrollAmount(false);
2597 if (velocityY < 0 && topAmount > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002598 if (ANCHOR_SCROLLING) {
2599 mScrollAnchorViewY += topAmount;
2600 } else {
2601 setOwnScrollY(mOwnScrollY - (int) topAmount);
2602 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002603 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002604 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002605 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002606 * mOverflingDistance + topAmount;
2607 } else if (velocityY > 0 && bottomAmount > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05002608 if (ANCHOR_SCROLLING) {
2609 mScrollAnchorViewY -= bottomAmount;
2610 } else {
2611 setOwnScrollY((int) (mOwnScrollY + bottomAmount));
2612 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002613 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07002614 mMaxOverScroll = Math.abs(velocityY) / 1000f
2615 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
Jason Monke59dc402018-08-16 12:05:01 -04002616 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002617 } else {
2618 // it will be set once we reach the boundary
2619 mMaxOverScroll = 0.0f;
2620 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05002621 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05002622 flingScroller(velocityY);
Gus Prevas0fa58d62019-01-11 13:58:40 -05002623 } else {
2624 int scrollRange = getScrollRange();
2625 int minScrollY = Math.max(0, scrollRange);
2626 if (mExpandedInThisMotion) {
2627 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2628 }
2629 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0, minScrollY, 0,
2630 mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek94ab18c2016-02-25 12:35:51 -08002631 }
Selim Cinek67b22602014-03-10 15:40:16 +01002632
Selim Cinek9212de82017-02-06 16:04:28 -08002633 animateScroll();
Selim Cinek67b22602014-03-10 15:40:16 +01002634 }
2635 }
2636
Selim Cinek1408eb52014-06-02 14:45:38 +02002637 /**
Gus Prevascdc98342019-01-14 14:29:44 -05002638 * Flings the overscroller with the given velocity (anchor-based scrolling).
2639 *
2640 * Because anchor-based scrolling can't track the current scroll position, the overscroller is
2641 * always started at startY = 0, and we interpret the positions it computes as relative to the
2642 * start of the scroll.
2643 */
2644 private void flingScroller(int velocityY) {
2645 assert ANCHOR_SCROLLING;
2646 mIsScrollerBoundSet = false;
2647 maybeFlingScroller(velocityY, true /* always fling */);
2648 }
2649
2650 private void maybeFlingScroller(int velocityY, boolean alwaysFling) {
2651 assert ANCHOR_SCROLLING;
2652 // Attempt to determine the maximum amount to scroll before we reach the end.
2653 // If the first view is not materialized (for an upwards scroll) or the last view is either
2654 // not materialized or is pinned to the shade (for a downwards scroll), we don't know this
2655 // amount, so we do an unbounded fling and rely on {@link #maybeReflingScroller()} to update
2656 // the scroller once we approach the start/end of the list.
2657 int minY = Integer.MIN_VALUE;
2658 int maxY = Integer.MAX_VALUE;
2659 if (velocityY < 0) {
2660 minY = getMaxNegativeScrollAmount();
2661 if (minY > Integer.MIN_VALUE) {
2662 mIsScrollerBoundSet = true;
2663 }
2664 } else {
2665 maxY = getMaxPositiveScrollAmount();
2666 if (maxY < Integer.MAX_VALUE) {
2667 mIsScrollerBoundSet = true;
2668 }
2669 }
2670 if (mIsScrollerBoundSet || alwaysFling) {
2671 mLastScrollerY = 0;
2672 // x velocity is set to 1 to avoid overscroller bug
2673 mScroller.fling(0, 0, 1, velocityY, 0, 0, minY, maxY, 0,
2674 mExpandedInThisMotion && !isScrolledToTop() ? 0 : Integer.MAX_VALUE / 2);
2675 }
2676 }
2677
2678 /**
2679 * Returns the maximum number of pixels we can scroll in the positive direction (downwards)
2680 * before reaching the bottom of the list (discounting overscroll).
2681 *
2682 * If the return value is negative then we have overscrolled; this is a transient state which
2683 * should immediately be handled by adjusting the anchor position and adding the extra space to
2684 * the bottom overscroll amount.
2685 *
2686 * If we don't know how many pixels we have left to scroll (because the last row has not been
2687 * materialized, or it's in the shelf so it doesn't have its "natural" position), we return
2688 * {@link Integer#MAX_VALUE}.
2689 */
2690 private int getMaxPositiveScrollAmount() {
2691 assert ANCHOR_SCROLLING;
2692 // TODO: once we're recycling we need to check the adapter position of the last child.
2693 ExpandableNotificationRow lastRow = getLastRowNotGone();
2694 if (mScrollAnchorView != null && lastRow != null && !lastRow.isInShelf()) {
2695 // distance from bottom of last child to bottom of notifications area is:
2696 // distance from bottom of last child
2697 return (int) (lastRow.getTranslationY() + lastRow.getActualHeight()
2698 // to top of anchor view
2699 - mScrollAnchorView.getTranslationY()
2700 // plus distance from anchor view to top of notifications area
2701 + mScrollAnchorViewY
2702 // minus height of notifications area.
2703 - (mMaxLayoutHeight - getIntrinsicPadding() - mFooterView.getActualHeight()));
2704 } else {
2705 return Integer.MAX_VALUE;
2706 }
2707 }
2708
2709 /**
2710 * Returns the maximum number of pixels (as a negative number) we can scroll in the negative
2711 * direction (upwards) before reaching the top of the list (discounting overscroll).
2712 *
2713 * If the return value is positive then we have overscrolled; this is a transient state which
2714 * should immediately be handled by adjusting the anchor position and adding the extra space to
2715 * the top overscroll amount.
2716 *
2717 * If we don't know how many pixels we have left to scroll (because the first row has not been
2718 * materialized), we return {@link Integer#MIN_VALUE}.
2719 */
2720 private int getMaxNegativeScrollAmount() {
2721 assert ANCHOR_SCROLLING;
2722 // TODO: once we're recycling we need to check the adapter position of the first child.
2723 ExpandableView firstChild = getFirstChildNotGone();
2724 if (mScrollAnchorView != null && firstChild != null) {
2725 // distance from top of first child to top of notifications area is:
2726 // distance from top of anchor view
2727 return (int) -(mScrollAnchorView.getTranslationY()
2728 // to top of first child
2729 - firstChild.getTranslationY()
2730 // minus distance from top of anchor view to top of notifications area.
2731 - mScrollAnchorViewY);
2732 } else {
2733 return Integer.MIN_VALUE;
2734 }
2735 }
2736
2737 /**
2738 * During a fling, if we were unable to set the bounds of the fling due to the top/bottom view
2739 * not being materialized or being pinned to the shelf, we need to check on every frame if we're
2740 * able to set the bounds. If we are, we fling the scroller again with the newly computed
2741 * bounds.
2742 */
2743 private void maybeReflingScroller() {
2744 if (!mIsScrollerBoundSet) {
2745 // Because mScroller is a flywheel scroller, we fling with the minimum possible
2746 // velocity to establish direction, so as not to perceptibly affect the velocity.
2747 maybeFlingScroller((int) Math.signum(mScroller.getCurrVelocity()),
2748 false /* alwaysFling */);
2749 }
2750 }
2751
2752 /**
Selim Cinek1408eb52014-06-02 14:45:38 +02002753 * @return Whether a fling performed on the top overscroll edge lead to the expanded
2754 * overScroll view (i.e QS).
2755 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002756 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02002757 private boolean shouldOverScrollFling(int initialVelocity) {
2758 float topOverScroll = getCurrentOverScrollAmount(true);
2759 return mScrolledToTopOnFirstDown
2760 && !mExpandedInThisMotion
2761 && topOverScroll > mMinTopOverScrollToEscape
2762 && initialVelocity > 0;
2763 }
2764
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002765 /**
2766 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2767 * account.
2768 *
Jason Monke59dc402018-08-16 12:05:01 -04002769 * @param qsHeight the top padding imposed by the quick settings panel
2770 * @param animate whether to animate the change
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002771 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
2772 * {@code qsHeight} is the final top padding
2773 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002774 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jason Monk16ac3772016-02-10 15:39:21 -05002775 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002776 boolean ignoreIntrinsicPadding) {
Selim Cinekbc243a92016-09-27 16:35:13 -07002777 int topPadding = (int) qsHeight;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002778 int minStackHeight = getLayoutMinHeight();
Selim Cinekbc243a92016-09-27 16:35:13 -07002779 if (topPadding + minStackHeight > getHeight()) {
2780 mTopPaddingOverflow = topPadding + minStackHeight - getHeight();
Selim Cinek1408eb52014-06-02 14:45:38 +02002781 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002782 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02002783 }
Selim Cinekbc243a92016-09-27 16:35:13 -07002784 setTopPadding(ignoreIntrinsicPadding ? topPadding : clampPadding(topPadding),
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002785 animate);
Selim Cinekbc243a92016-09-27 16:35:13 -07002786 setExpandedHeight(mExpandedHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02002787 }
2788
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002789 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
shawnlin8e4e92c2018-04-12 18:47:24 +08002790 public void setMaxTopPadding(int maxTopPadding) {
2791 mMaxTopPadding = maxTopPadding;
2792 }
2793
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002794 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002795 public int getLayoutMinHeight() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08002796 if (isHeadsUpTransition()) {
2797 return getTopHeadsUpPinnedHeight();
2798 }
Anthony Chen9e05d462017-04-07 10:10:21 -07002799 return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight();
Selim Cinek94c2d822016-07-13 18:50:04 -07002800 }
2801
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002802 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002803 public float getTopPaddingOverflow() {
2804 return mTopPaddingOverflow;
2805 }
2806
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002807 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002808 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002809 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek567e8452016-03-24 10:54:56 -07002810 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08002811 : mCollapsedSize;
Selim Cinekdb167372016-11-17 15:41:17 -08002812 int shelfHeight = 0;
Gus Prevase2d6f042018-10-17 15:25:30 -04002813 if (getLastVisibleSection() != null && mShelf.getVisibility() != GONE) {
Selim Cinekdb167372016-11-17 15:41:17 -08002814 shelfHeight = mShelf.getIntrinsicHeight();
2815 }
2816 return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002817 }
2818
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002819 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1408eb52014-06-02 14:45:38 +02002820 private int clampPadding(int desiredPadding) {
2821 return Math.max(desiredPadding, mIntrinsicPadding);
2822 }
2823
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002824 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfed1ab62014-06-17 14:10:33 -07002825 private float getRubberBandFactor(boolean onTop) {
2826 if (!onTop) {
2827 return RUBBER_BAND_FACTOR_NORMAL;
2828 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002829 if (mExpandedInThisMotion) {
2830 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002831 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002832 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2833 } else if (mScrolledToTopOnFirstDown) {
2834 return 1.0f;
2835 }
2836 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02002837 }
2838
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002839 /**
2840 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2841 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2842 * overscroll view (e.g. expand QS).
2843 */
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04002844 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002845 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002846 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002847 || !mScrolledToTopOnFirstDown;
2848 }
2849
Selim Cinek67b22602014-03-10 15:40:16 +01002850
Selim Cinek67b22602014-03-10 15:40:16 +01002851
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002852 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekef5127e2015-12-21 16:55:58 -08002853 public void setChildTransferInProgress(boolean childTransferInProgress) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04002854 Assert.isMainThread();
Selim Cinekef5127e2015-12-21 16:55:58 -08002855 mChildTransferInProgress = childTransferInProgress;
2856 }
2857
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002858 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Christoph Studer068f5922014-04-08 17:43:07 -04002859 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002860 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04002861 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002862 // we only call our internal methods if this is actually a removal and not just a
2863 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08002864 if (!mChildTransferInProgress) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002865 onViewRemovedInternal((ExpandableView) child, this);
Selim Cinekb5605e52015-02-20 18:21:41 +01002866 }
2867 }
2868
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002869 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09002870 @Override
Ned Burnsf81c4c42019-01-07 14:10:43 -05002871 public void cleanUpViewStateForEntry(NotificationEntry entry) {
Evan Laird94492852018-10-25 13:43:01 -04002872 View child = entry.getRow();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04002873 if (child == mSwipeHelper.getTranslatingParentView()) {
2874 mSwipeHelper.clearTranslatingParentView();
Mady Mellor4c97b0a2017-02-15 11:16:13 -08002875 }
Mady Mellor4c97b0a2017-02-15 11:16:13 -08002876 }
2877
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002878 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002879 private void onViewRemovedInternal(ExpandableView child, ViewGroup container) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002880 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002881 // This is only a position change, don't do anything special
2882 return;
2883 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002884 child.setOnHeightChangedListener(null);
2885 updateScrollStateForRemovedChild(child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002886 boolean animationGenerated = generateRemoveAnimation(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002887 if (animationGenerated) {
Rohan Shah524cf7b2018-03-15 14:40:02 -07002888 if (!mSwipedOutViews.contains(child)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002889 || Math.abs(child.getTranslation()) != child.getWidth()) {
Selim Cineka5703182016-05-11 21:23:16 -04002890 container.addTransientView(child, 0);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002891 child.setTransientContainer(container);
Selim Cinekd1395642016-04-28 12:22:42 -07002892 }
2893 } else {
2894 mSwipedOutViews.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002895 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002896 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002897
Selim Cineke9bad242016-06-15 11:46:37 -07002898 focusNextViewIfFocused(child);
2899 }
2900
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002901 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineke9bad242016-06-15 11:46:37 -07002902 private void focusNextViewIfFocused(View view) {
2903 if (view instanceof ExpandableNotificationRow) {
2904 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2905 if (row.shouldRefocusOnDismiss()) {
2906 View nextView = row.getChildAfterViewWhenDismissed();
2907 if (nextView == null) {
2908 View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
2909 nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
2910 ? groupParentWhenDismissed.getTranslationY()
Selim Cinekef8c2252017-02-10 14:52:18 -08002911 : view.getTranslationY(), true /* ignoreChildren */);
Selim Cineke9bad242016-06-15 11:46:37 -07002912 }
2913 if (nextView != null) {
2914 nextView.requestAccessibilityFocus();
2915 }
2916 }
2917 }
2918
Selim Cinekc27437b2014-05-14 10:23:33 +02002919 }
2920
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002921 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekb5605e52015-02-20 18:21:41 +01002922 private boolean isChildInGroup(View child) {
2923 return child instanceof ExpandableNotificationRow
2924 && mGroupManager.isChildInGroupWithSummary(
Jason Monke59dc402018-08-16 12:05:01 -04002925 ((ExpandableNotificationRow) child).getStatusBarNotification());
Selim Cinekb5605e52015-02-20 18:21:41 +01002926 }
2927
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002928 /**
2929 * Generate a remove animation for a child view.
2930 *
2931 * @param child The view to generate the remove animation for.
2932 * @return Whether an animation was generated.
2933 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002934 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002935 private boolean generateRemoveAnimation(ExpandableView child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002936 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002937 mAddedHeadsUpChildren.remove(child);
2938 return false;
2939 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002940 if (isClickedHeadsUp(child)) {
Selim Cinek9dd0d042018-05-14 18:12:42 -07002941 // An animation is already running, add it transiently
Dave Mankoffa4d195d2018-11-16 13:33:27 -05002942 mClearTransientViewsWhenFinished.add(child);
Selim Cinek0fccc722015-07-29 17:04:36 -07002943 return true;
2944 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002945 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002946 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002947 // Generate Animations
2948 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002949 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002950 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002951 } else {
2952 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002953 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002954 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002955 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002956 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002957 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002958 }
2959
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002960 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek0fccc722015-07-29 17:04:36 -07002961 private boolean isClickedHeadsUp(View child) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09002962 return HeadsUpUtil.isClickedHeadsUpNotification(child);
Selim Cinek0fccc722015-07-29 17:04:36 -07002963 }
2964
Selim Cineke0890e52015-06-17 11:17:08 -07002965 /**
2966 * Remove a removed child view from the heads up animations if it was just added there
2967 *
2968 * @return whether any child was removed from the list to animate
2969 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002970 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineke0890e52015-06-17 11:17:08 -07002971 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2972 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002973 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2974 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002975 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002976 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002977 mTmpList.add(eventPair);
2978 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002979 }
2980 }
Selim Cineke0890e52015-06-17 11:17:08 -07002981 if (hasAddEvent) {
2982 // This child was just added lets remove all events.
2983 mHeadsUpChangeAnimations.removeAll(mTmpList);
Jason Monke59dc402018-08-16 12:05:01 -04002984 ((ExpandableNotificationRow) child).setHeadsUpAnimatingAway(false);
Selim Cineke0890e52015-06-17 11:17:08 -07002985 }
2986 mTmpList.clear();
2987 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002988 }
2989
Selim Cinek572bbd42014-04-25 16:43:27 +02002990 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002991 * @param child the child to query
2992 * @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 -04002993 * not expanded
Selim Cinekb5605e52015-02-20 18:21:41 +01002994 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04002995 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinekb5605e52015-02-20 18:21:41 +01002996 private boolean isChildInInvisibleGroup(View child) {
2997 if (child instanceof ExpandableNotificationRow) {
2998 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Ned Burnsf81c4c42019-01-07 14:10:43 -05002999 NotificationEntry groupSummary =
Selim Cinekb5605e52015-02-20 18:21:41 +01003000 mGroupManager.getGroupSummary(row.getStatusBarNotification());
Evan Laird94492852018-10-25 13:43:01 -04003001 if (groupSummary != null && groupSummary.getRow() != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07003002 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01003003 }
3004 }
3005 return false;
3006 }
3007
3008 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02003009 * Updates the scroll position when a child was removed
3010 *
3011 * @param removedChild the removed child
3012 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003013 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek61633a82016-01-25 15:54:10 -08003014 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003015 if (ANCHOR_SCROLLING) {
3016 if (removedChild == mScrollAnchorView) {
3017 ExpandableView firstChild = getFirstChildNotGone();
3018 if (firstChild != null) {
3019 mScrollAnchorView = firstChild;
3020 } else {
3021 mScrollAnchorView = mShelf;
3022 }
3023 // Adjust anchor view Y by the distance between the old and new anchors
3024 // so that there's no visible change.
3025 mScrollAnchorViewY +=
3026 mScrollAnchorView.getTranslationY() - removedChild.getTranslationY();
3027 }
3028 updateScrollAnchor();
3029 // TODO: once we're recycling this will need to check the adapter position of the child
3030 if (mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY > 0) {
3031 mScrollAnchorViewY = 0;
3032 }
3033 updateOnScrollChange();
Selim Cineka7ed2c12017-01-23 20:47:24 -08003034 } else {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003035 int startingPosition = getPositionInLinearLayout(removedChild);
3036 float increasedPaddingAmount = removedChild.getIncreasedPaddingAmount();
3037 int padding;
3038 if (increasedPaddingAmount >= 0) {
3039 padding = (int) NotificationUtils.interpolate(
3040 mPaddingBetweenElements,
3041 mIncreasedPaddingBetweenElements,
3042 increasedPaddingAmount);
3043 } else {
3044 padding = (int) NotificationUtils.interpolate(
3045 0,
3046 mPaddingBetweenElements,
3047 1.0f + increasedPaddingAmount);
3048 }
3049 int childHeight = getIntrinsicHeight(removedChild) + padding;
3050 int endPosition = startingPosition + childHeight;
3051 if (endPosition <= mOwnScrollY) {
3052 // This child is fully scrolled of the top, so we have to deduct its height from the
3053 // scrollPosition
3054 setOwnScrollY(mOwnScrollY - childHeight);
3055 } else if (startingPosition < mOwnScrollY) {
3056 // This child is currently being scrolled into, set the scroll position to the
3057 // start of this child
3058 setOwnScrollY(startingPosition);
3059 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003060 }
3061 }
3062
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003063 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekd7c4e002014-07-04 18:36:42 +02003064 private int getIntrinsicHeight(View view) {
3065 if (view instanceof ExpandableView) {
3066 ExpandableView expandableView = (ExpandableView) view;
3067 return expandableView.getIntrinsicHeight();
3068 }
3069 return view.getHeight();
3070 }
3071
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003072 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1f624952017-06-08 19:11:50 -07003073 public int getPositionInLinearLayout(View requestedView) {
Adrian Roos4a579672016-05-24 16:54:37 -07003074 ExpandableNotificationRow childInGroup = null;
3075 ExpandableNotificationRow requestedRow = null;
3076 if (isChildInGroup(requestedView)) {
3077 // We're asking for a child in a group. Calculate the position of the parent first,
3078 // then within the parent.
3079 childInGroup = (ExpandableNotificationRow) requestedView;
3080 requestedView = requestedRow = childInGroup.getNotificationParent();
3081 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003082 int position = 0;
Selim Cineka7ed2c12017-01-23 20:47:24 -08003083 float previousPaddingRequest = mPaddingBetweenElements;
3084 float previousPaddingAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02003085 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08003086 ExpandableView child = (ExpandableView) getChildAt(i);
3087 boolean notGone = child.getVisibility() != View.GONE;
Selim Cinek281c2022016-10-13 19:14:43 -07003088 if (notGone && !child.hasNoContentHeight()) {
Selim Cinek42357e02016-02-24 18:48:01 -08003089 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cineka7ed2c12017-01-23 20:47:24 -08003090 float padding;
3091 if (increasedPaddingAmount >= 0.0f) {
3092 padding = (int) NotificationUtils.interpolate(
3093 previousPaddingRequest,
3094 mIncreasedPaddingBetweenElements,
3095 increasedPaddingAmount);
3096 previousPaddingRequest = (int) NotificationUtils.interpolate(
Selim Cinek42357e02016-02-24 18:48:01 -08003097 mPaddingBetweenElements,
3098 mIncreasedPaddingBetweenElements,
Selim Cineka7ed2c12017-01-23 20:47:24 -08003099 increasedPaddingAmount);
3100 } else {
3101 int ownPadding = (int) NotificationUtils.interpolate(
3102 0,
3103 mPaddingBetweenElements,
3104 1.0f + increasedPaddingAmount);
3105 if (previousPaddingAmount > 0.0f) {
3106 padding = (int) NotificationUtils.interpolate(
3107 ownPadding,
3108 mIncreasedPaddingBetweenElements,
3109 previousPaddingAmount);
3110 } else {
3111 padding = ownPadding;
3112 }
3113 previousPaddingRequest = ownPadding;
Selim Cinek61633a82016-01-25 15:54:10 -08003114 }
Selim Cineka7ed2c12017-01-23 20:47:24 -08003115 if (position != 0) {
3116 position += padding;
3117 }
3118 previousPaddingAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08003119 }
Adrian Roos4a579672016-05-24 16:54:37 -07003120 if (child == requestedView) {
3121 if (requestedRow != null) {
3122 position += requestedRow.getPositionOfChild(childInGroup);
3123 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003124 return position;
3125 }
Selim Cinek61633a82016-01-25 15:54:10 -08003126 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02003127 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02003128 }
3129 }
3130 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02003131 }
3132
3133 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003134 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adam Powell6690d012015-06-17 16:41:56 -07003135 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02003136 super.onViewAdded(child);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003137 onViewAddedInternal((ExpandableView) child);
Selim Cinekb5605e52015-02-20 18:21:41 +01003138 }
3139
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003140 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek614576e2016-01-20 10:54:09 -08003141 private void updateFirstAndLastBackgroundViews() {
Gus Prevase2d6f042018-10-17 15:25:30 -04003142 NotificationSection firstSection = getFirstVisibleSection();
3143 NotificationSection lastSection = getLastVisibleSection();
Gus Prevasda13cfa2018-11-20 14:58:48 -05003144 ActivatableNotificationView previousFirstChild =
3145 firstSection == null ? null : firstSection.getFirstVisibleChild();
3146 ActivatableNotificationView previousLastChild =
3147 lastSection == null ? null : lastSection.getLastVisibleChild();
Gus Prevase2d6f042018-10-17 15:25:30 -04003148
Selim Cinek614576e2016-01-20 10:54:09 -08003149 ActivatableNotificationView firstChild = getFirstChildWithBackground();
3150 ActivatableNotificationView lastChild = getLastChildWithBackground();
Ned Burns9eb06332019-04-23 16:02:12 -04003151 boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsInSections(
Gus Prevase2d6f042018-10-17 15:25:30 -04003152 mSections[0], mSections[1], firstChild, lastChild);
3153
Selim Cinek614576e2016-01-20 10:54:09 -08003154 if (mAnimationsEnabled && mIsExpanded) {
Gus Prevasda13cfa2018-11-20 14:58:48 -05003155 mAnimateNextBackgroundTop = firstChild != previousFirstChild;
Selim Cinek6f0a62a2019-04-09 18:40:12 -07003156 mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
Gus Prevase2d6f042018-10-17 15:25:30 -04003157 mAnimateNextSectionBoundsChange = sectionViewsChanged;
Selim Cinek614576e2016-01-20 10:54:09 -08003158 } else {
3159 mAnimateNextBackgroundTop = false;
3160 mAnimateNextBackgroundBottom = false;
Gus Prevase2d6f042018-10-17 15:25:30 -04003161 mAnimateNextSectionBoundsChange = false;
Selim Cinek614576e2016-01-20 10:54:09 -08003162 }
Selim Cinekdb167372016-11-17 15:41:17 -08003163 mAmbientState.setLastVisibleBackgroundChild(lastChild);
Gus Prevase2d6f042018-10-17 15:25:30 -04003164 mRoundnessManager.updateRoundedChildren(mSections);
Selim Cinek6f0a62a2019-04-09 18:40:12 -07003165 mAnimateBottomOnLayout = false;
Selim Cinek515b2032017-11-15 10:20:19 -08003166 invalidate();
Selim Cinek614576e2016-01-20 10:54:09 -08003167 }
3168
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003169 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003170 private void onViewAddedInternal(ExpandableView child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07003171 updateHideSensitiveForChild(child);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003172 child.setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00003173 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02003174 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02003175 updateChronometerForChild(child);
Gus Prevasa18dc572019-01-14 16:11:22 -05003176 if (child instanceof ExpandableNotificationRow) {
3177 ((ExpandableNotificationRow) child).setDismissRtl(mDismissRtl);
3178 }
Gus Prevas0fa58d62019-01-11 13:58:40 -05003179 if (ANCHOR_SCROLLING) {
3180 // TODO: once we're recycling this will need to check the adapter position of the child
3181 if (child == getFirstChildNotGone() && (isScrolledToTop() || !mIsExpanded)) {
3182 // New child was added at the top while we're scrolled to the top;
3183 // make it the new anchor view so that we stay at the top.
3184 mScrollAnchorView = child;
3185 }
3186 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003187 }
3188
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003189 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003190 private void updateHideSensitiveForChild(ExpandableView child) {
3191 child.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
Selim Cinekd06c41c2015-07-06 14:51:36 -07003192 }
3193
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003194 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003195 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003196 public void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer) {
Selim Cinekd1395642016-04-28 12:22:42 -07003197 onViewRemovedInternal(row, childrenContainer);
Selim Cinekb5605e52015-02-20 18:21:41 +01003198 }
3199
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003200 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003201 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003202 public void notifyGroupChildAdded(ExpandableView row) {
Selim Cinekb5605e52015-02-20 18:21:41 +01003203 onViewAddedInternal(row);
3204 }
3205
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003206 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi75c95042014-05-16 19:09:59 +02003207 public void setAnimationsEnabled(boolean animationsEnabled) {
3208 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02003209 updateNotificationAnimationStates();
Rohan Shah8ee53652018-04-05 11:13:50 -07003210 if (!animationsEnabled) {
3211 mSwipedOutViews.clear();
3212 mChildrenToRemoveAnimated.clear();
3213 clearTemporaryViewsInGroup(this);
3214 }
Selim Cinekcab4a602014-09-03 14:47:57 +02003215 }
3216
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003217 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcab4a602014-09-03 14:47:57 +02003218 private void updateNotificationAnimationStates() {
Selim Cinekbe2c4432017-05-30 12:11:09 -07003219 boolean running = mAnimationsEnabled || hasPulsingNotifications();
Selim Cinek09bd29d2017-02-03 15:30:28 -08003220 mShelf.setAnimationsEnabled(running);
Lucas Dupin9d6172b2019-06-04 15:14:25 -07003221 mIconAreaController.setAnimationsEnabled(running);
Selim Cinekcab4a602014-09-03 14:47:57 +02003222 int childCount = getChildCount();
3223 for (int i = 0; i < childCount; i++) {
3224 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07003225 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02003226 updateAnimationState(running, child);
3227 }
3228 }
3229
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003230 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek51ae05d2014-09-09 15:51:38 +02003231 private void updateAnimationState(View child) {
Selim Cinekbe2c4432017-05-30 12:11:09 -07003232 updateAnimationState((mAnimationsEnabled || hasPulsingNotifications())
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003233 && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02003234 }
3235
Selim Cinek2627d722018-01-19 12:16:49 -08003236 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003237 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek2627d722018-01-19 12:16:49 -08003238 public void setExpandingNotification(ExpandableNotificationRow row) {
3239 mAmbientState.setExpandingNotification(row);
3240 requestChildrenUpdate();
3241 }
3242
3243 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003244 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek8875de12018-03-22 10:14:32 -07003245 public void bindRow(ExpandableNotificationRow row) {
Selim Cinekf0c79e12018-05-14 17:17:31 -07003246 row.setHeadsUpAnimatingAwayListener(animatingAway -> {
3247 mRoundnessManager.onHeadsupAnimatingAwayChanged(row, animatingAway);
3248 mHeadsUpAppearanceController.updateHeader(row.getEntry());
3249 });
Selim Cinek8875de12018-03-22 10:14:32 -07003250 }
3251
3252 @Override
Selim Cinekfdf80332019-03-07 17:29:55 -08003253 public boolean containsView(View v) {
3254 return v.getParent() == this;
3255 }
3256
3257 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003258 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek2627d722018-01-19 12:16:49 -08003259 public void applyExpandAnimationParams(ExpandAnimationParameters params) {
3260 mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
3261 requestChildrenUpdate();
3262 }
Selim Cinek51ae05d2014-09-09 15:51:38 +02003263
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003264 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekcab4a602014-09-03 14:47:57 +02003265 private void updateAnimationState(boolean running, View child) {
3266 if (child instanceof ExpandableNotificationRow) {
3267 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3268 row.setIconAnimationRunning(running);
3269 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02003270 }
3271
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003272 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi75c95042014-05-16 19:09:59 +02003273 public boolean isAddOrRemoveAnimationPending() {
3274 return mNeedsAnimation
3275 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
3276 }
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003277
3278 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003279 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003280 public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003281 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003282 // Generate Animations
3283 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00003284 if (fromMoreCard) {
3285 mFromMoreCardAdditions.add(child);
3286 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003287 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02003288 }
Selim Cinekf306d9b2017-02-21 11:45:13 -08003289 if (isHeadsUp(child) && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003290 mAddedHeadsUpChildren.add(child);
3291 mChildrenToAddAnimated.remove(child);
3292 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003293 }
3294
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09003295 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003296 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003297 public void changeViewPosition(ExpandableView child, int newIndex) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04003298 Assert.isMainThread();
3299 if (mChangePositionInProgress) {
3300 throw new IllegalStateException("Reentrant call to changeViewPosition");
3301 }
3302
Dan Sandlereceda3d2014-07-21 15:35:01 -04003303 int currentIndex = indexOfChild(child);
Rohan Shah8ee53652018-04-05 11:13:50 -07003304
3305 if (currentIndex == -1) {
3306 boolean isTransient = false;
3307 if (child instanceof ExpandableNotificationRow
Jason Monke59dc402018-08-16 12:05:01 -04003308 && ((ExpandableNotificationRow) child).getTransientContainer() != null) {
Rohan Shah8ee53652018-04-05 11:13:50 -07003309 isTransient = true;
3310 }
3311 Log.e(TAG, "Attempting to re-position "
3312 + (isTransient ? "transient" : "")
3313 + " view {"
3314 + child
3315 + "}");
3316 return;
3317 }
3318
Dan Sandlereceda3d2014-07-21 15:35:01 -04003319 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003320 mChangePositionInProgress = true;
Jason Monke59dc402018-08-16 12:05:01 -04003321 ((ExpandableView) child).setChangingPosition(true);
Selim Cinekc27437b2014-05-14 10:23:33 +02003322 removeView(child);
3323 addView(child, newIndex);
Jason Monke59dc402018-08-16 12:05:01 -04003324 ((ExpandableView) child).setChangingPosition(false);
Selim Cinek159ffdb2014-06-04 22:24:18 +02003325 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04003326 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02003327 mChildrenChangingPositions.add(child);
3328 mNeedsAnimation = true;
3329 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003330 }
3331 }
3332
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003333 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekf4c19962014-05-01 21:55:31 +02003334 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003335 if (mNeedsAnimation) {
Rohan Shahb9d500a2018-06-25 16:27:16 -07003336 generateAllAnimationEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003337 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02003338 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003339 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Selim Cinekea66ca02016-05-24 13:33:47 -07003340 setAnimationRunning(true);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003341 mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003342 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08003343 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08003344 updateViewShadows();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08003345 updateClippingToTopRoundedCorner();
Selim Cinekf4c19962014-05-01 21:55:31 +02003346 } else {
3347 applyCurrentState();
3348 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003349 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02003350 }
3351
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003352 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Rohan Shahb9d500a2018-06-25 16:27:16 -07003353 private void generateAllAnimationEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07003354 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02003355 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003356 generateChildAdditionEvents();
3357 generatePositionChangeEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003358 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003359 generateActivateEvent();
3360 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02003361 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04003362 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003363 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02003364 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01003365 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02003366 generateAnimateEverythingEvent();
Selim Cinek572bbd42014-04-25 16:43:27 +02003367 }
3368
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003369 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003370 private void generateHeadsUpAnimationEvents() {
3371 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003372 ExpandableNotificationRow row = eventPair.first;
3373 boolean isHeadsUp = eventPair.second;
3374 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
3375 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07003376 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07003377 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003378 type = row.wasJustClicked()
3379 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3380 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinek76e813c2016-07-14 11:16:58 -07003381 if (row.isChildInGroup()) {
3382 // We can otherwise get stuck in there if it was just isolated
Selim Cinekcafa87f2016-10-26 17:00:17 -07003383 row.setHeadsUpAnimatingAway(false);
Selim Cinekf93bf3e2018-05-08 14:43:21 -07003384 continue;
Selim Cinek76e813c2016-07-14 11:16:58 -07003385 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04003386 } else {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003387 ExpandableViewState viewState = row.getViewState();
Selim Cinekeaee9c02015-06-25 11:04:20 -04003388 if (viewState == null) {
3389 // A view state was never generated for this view, so we don't need to animate
3390 // this. This may happen with notification children.
3391 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07003392 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04003393 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
3394 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
3395 // Our custom add animation
3396 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
3397 } else {
3398 // Normal add animation
3399 type = AnimationEvent.ANIMATION_TYPE_ADD;
3400 }
3401 onBottom = !pinnedAndClosed;
3402 }
Selim Cineka59ecc32015-04-07 10:51:49 -07003403 }
3404 AnimationEvent event = new AnimationEvent(row, type);
3405 event.headsUpFromBottom = onBottom;
3406 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003407 }
3408 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07003409 mAddedHeadsUpChildren.clear();
3410 }
3411
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003412 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekbbcebde2016-11-09 18:28:20 -08003413 private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07003414 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
3415 return false;
3416 }
3417 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003418 }
3419
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003420 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb5605e52015-02-20 18:21:41 +01003421 private void generateGroupExpansionEvent() {
3422 // Generate a group expansion/collapsing event if there is such a group at all
3423 if (mExpandedGroupView != null) {
3424 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
3425 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
3426 mExpandedGroupView = null;
3427 }
3428 }
3429
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003430 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cineka5e211b2014-08-11 17:35:48 +02003431 private void generateViewResizeEvent() {
3432 if (mNeedViewResizeAnimation) {
felkachangd7835b02018-07-17 18:18:13 +08003433 boolean hasDisappearAnimation = false;
3434 for (AnimationEvent animationEvent : mAnimationEvents) {
3435 final int type = animationEvent.animationType;
3436 if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
Jason Monke59dc402018-08-16 12:05:01 -04003437 || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
felkachangd7835b02018-07-17 18:18:13 +08003438 hasDisappearAnimation = true;
3439 break;
3440 }
3441 }
3442
3443 if (!hasDisappearAnimation) {
3444 mAnimationEvents.add(
3445 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
3446 }
Selim Cineka5e211b2014-08-11 17:35:48 +02003447 }
3448 mNeedViewResizeAnimation = false;
3449 }
3450
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003451 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02003452 private void generateChildRemovalEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003453 for (ExpandableView child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003454 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003455
3456 // we need to know the view after this one
Selim Cinekef8c2252017-02-10 14:52:18 -08003457 float removedTranslation = child.getTranslationY();
3458 boolean ignoreChildren = true;
3459 if (child instanceof ExpandableNotificationRow) {
3460 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
3461 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
3462 removedTranslation = row.getTranslationWhenRemoved();
3463 ignoreChildren = false;
3464 }
Selim Cinek51052042017-07-04 12:07:55 +02003465 childWasSwipedOut |= Math.abs(row.getTranslation()) == row.getWidth();
Selim Cinekef8c2252017-02-10 14:52:18 -08003466 }
Selim Cinek51052042017-07-04 12:07:55 +02003467 if (!childWasSwipedOut) {
3468 Rect clipBounds = child.getClipBounds();
Selim Cineke5832ee2017-11-06 14:46:48 -08003469 childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
Rohan Shaha7594962018-05-22 10:59:30 -07003470
3471 if (childWasSwipedOut && child instanceof ExpandableView) {
3472 // Clean up any potential transient views if the child has already been swiped
3473 // out, as we won't be animating it further (due to its height already being
3474 // clipped to 0.
3475 ViewGroup transientContainer = ((ExpandableView) child).getTransientContainer();
3476 if (transientContainer != null) {
3477 transientContainer.removeTransientView(child);
3478 }
3479 }
Selim Cinek51052042017-07-04 12:07:55 +02003480 }
3481 int animationType = childWasSwipedOut
3482 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
3483 : AnimationEvent.ANIMATION_TYPE_REMOVE;
3484 AnimationEvent event = new AnimationEvent(child, animationType);
Selim Cinekef8c2252017-02-10 14:52:18 -08003485 event.viewAfterChangingView = getFirstChildBelowTranlsationY(removedTranslation,
3486 ignoreChildren);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003487 mAnimationEvents.add(event);
Selim Cinekd1395642016-04-28 12:22:42 -07003488 mSwipedOutViews.remove(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02003489 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003490 mChildrenToRemoveAnimated.clear();
3491 }
3492
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003493 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003494 private void generatePositionChangeEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003495 for (ExpandableView child : mChildrenChangingPositions) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003496 mAnimationEvents.add(new AnimationEvent(child,
3497 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3498 }
3499 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01003500 if (mGenerateChildOrderChangedEvent) {
3501 mAnimationEvents.add(new AnimationEvent(null,
3502 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
3503 mGenerateChildOrderChangedEvent = false;
3504 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003505 }
3506
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003507 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02003508 private void generateChildAdditionEvents() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003509 for (ExpandableView child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003510 if (mFromMoreCardAdditions.contains(child)) {
3511 mAnimationEvents.add(new AnimationEvent(child,
3512 AnimationEvent.ANIMATION_TYPE_ADD,
3513 StackStateAnimator.ANIMATION_DURATION_STANDARD));
3514 } else {
3515 mAnimationEvents.add(new AnimationEvent(child,
3516 AnimationEvent.ANIMATION_TYPE_ADD));
3517 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003518 }
3519 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003520 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04003521 }
3522
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003523 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003524 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02003525 if (mTopPaddingNeedsAnimation) {
Lucas Dupinc445dcc2018-05-07 16:00:11 -07003526 AnimationEvent event;
3527 if (mAmbientState.isDark()) {
3528 event = new AnimationEvent(null /* view */,
3529 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED,
3530 KeyguardSliceView.DEFAULT_ANIM_DURATION);
3531 } else {
3532 event = new AnimationEvent(null /* view */,
3533 AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED);
3534 }
3535 mAnimationEvents.add(event);
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02003536 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003537 mTopPaddingNeedsAnimation = false;
3538 }
3539
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003540 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003541 private void generateActivateEvent() {
3542 if (mActivateNeedsAnimation) {
3543 mAnimationEvents.add(
3544 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
3545 }
3546 mActivateNeedsAnimation = false;
3547 }
3548
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003549 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekd9acca52014-09-01 22:33:25 +02003550 private void generateAnimateEverythingEvent() {
3551 if (mEverythingNeedsAnimation) {
3552 mAnimationEvents.add(
3553 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
3554 }
3555 mEverythingNeedsAnimation = false;
3556 }
3557
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003558 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003559 private void generateDimmedEvent() {
3560 if (mDimmedNeedsAnimation) {
3561 mAnimationEvents.add(
3562 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
3563 }
3564 mDimmedNeedsAnimation = false;
3565 }
3566
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003567 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggiae441282014-08-01 02:45:18 +02003568 private void generateHideSensitiveEvent() {
3569 if (mHideSensitiveNeedsAnimation) {
3570 mAnimationEvents.add(
3571 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
3572 }
3573 mHideSensitiveNeedsAnimation = false;
3574 }
3575
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003576 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
John Spurlockbf370992014-06-17 13:58:31 -04003577 private void generateDarkEvent() {
3578 if (mDarkNeedsAnimation) {
Adrian Roos28f90c72017-05-08 17:24:26 -07003579 AnimationEvent ev = new AnimationEvent(null,
3580 AnimationEvent.ANIMATION_TYPE_DARK,
3581 new AnimationFilter()
3582 .animateDark()
3583 .animateY(mShelf));
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003584 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
3585 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04003586 }
3587 mDarkNeedsAnimation = false;
3588 }
3589
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003590 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003591 private void generateGoToFullShadeEvent() {
3592 if (mGoToFullShadeNeedsAnimation) {
3593 mAnimationEvents.add(
3594 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
3595 }
3596 mGoToFullShadeNeedsAnimation = false;
3597 }
3598
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003599 @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
3600 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05003601 return new StackScrollAlgorithm(context, this);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003602 }
3603
3604 /**
3605 * @return Whether a y coordinate is inside the content.
3606 */
3607 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
3608 public boolean isInContentBounds(float y) {
3609 return y < getHeight() - getEmptyBottomMargin();
3610 }
3611
3612 @ShadeViewRefactor(RefactorComponent.INPUT)
3613 public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
3614 mLongPressListener = listener;
3615 }
3616
3617 @Override
3618 @ShadeViewRefactor(RefactorComponent.INPUT)
3619 public boolean onTouchEvent(MotionEvent ev) {
3620 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
3621 || ev.getActionMasked() == MotionEvent.ACTION_UP;
3622 handleEmptySpaceClick(ev);
3623 boolean expandWantsIt = false;
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003624 boolean swipingInProgress = mSwipingInProgress;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003625 if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) {
3626 if (isCancelOrUp) {
3627 mExpandHelper.onlyObserveMovements(false);
3628 }
3629 boolean wasExpandingBefore = mExpandingNotification;
3630 expandWantsIt = mExpandHelper.onTouchEvent(ev);
3631 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
3632 && !mDisallowScrollingInThisMotion) {
3633 dispatchDownEventToScroller(ev);
3634 }
3635 }
3636 boolean scrollerWantsIt = false;
3637 if (mIsExpanded && !swipingInProgress && !mExpandingNotification
3638 && !mDisallowScrollingInThisMotion) {
3639 scrollerWantsIt = onScrollTouch(ev);
3640 }
3641 boolean horizontalSwipeWantsIt = false;
3642 if (!mIsBeingDragged
3643 && !mExpandingNotification
3644 && !mExpandedInThisMotion
3645 && !mOnlyScrollingInThisMotion
3646 && !mDisallowDismissInThisMotion) {
3647 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
3648 }
3649
3650 // Check if we need to clear any snooze leavebehinds
3651 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
3652 if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
3653 && guts.getGutsContent() instanceof NotificationSnooze) {
3654 NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
3655 if ((ns.isExpanded() && isCancelOrUp)
3656 || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
3657 // If the leavebehind is expanded we clear it on the next up event, otherwise we
3658 // clear it on the next non-horizontal swipe or expand event.
3659 checkSnoozeLeavebehind();
3660 }
3661 }
3662 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
3663 mCheckForLeavebehind = true;
3664 }
3665 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
3666 }
3667
3668 @ShadeViewRefactor(RefactorComponent.INPUT)
3669 private void dispatchDownEventToScroller(MotionEvent ev) {
3670 MotionEvent downEvent = MotionEvent.obtain(ev);
3671 downEvent.setAction(MotionEvent.ACTION_DOWN);
3672 onScrollTouch(downEvent);
3673 downEvent.recycle();
3674 }
3675
3676 @Override
3677 @ShadeViewRefactor(RefactorComponent.INPUT)
3678 public boolean onGenericMotionEvent(MotionEvent event) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003679 if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003680 || mDisallowScrollingInThisMotion) {
3681 return false;
3682 }
3683 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
3684 switch (event.getAction()) {
3685 case MotionEvent.ACTION_SCROLL: {
3686 if (!mIsBeingDragged) {
3687 final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
3688 if (vscroll != 0) {
3689 final int delta = (int) (vscroll * getVerticalScrollFactor());
Gus Prevas0fa58d62019-01-11 13:58:40 -05003690 if (ANCHOR_SCROLLING) {
3691 mScrollAnchorViewY -= delta;
3692 updateScrollAnchor();
3693 clampScrollPosition();
3694 updateOnScrollChange();
3695 } else {
3696 final int range = getScrollRange();
3697 int oldScrollY = mOwnScrollY;
3698 int newScrollY = oldScrollY - delta;
3699 if (newScrollY < 0) {
3700 newScrollY = 0;
3701 } else if (newScrollY > range) {
3702 newScrollY = range;
3703 }
3704 if (newScrollY != oldScrollY) {
3705 setOwnScrollY(newScrollY);
3706 return true;
3707 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003708 }
3709 }
3710 }
3711 }
3712 }
3713 }
3714 return super.onGenericMotionEvent(event);
3715 }
3716
3717 @ShadeViewRefactor(RefactorComponent.INPUT)
3718 private boolean onScrollTouch(MotionEvent ev) {
3719 if (!isScrollingEnabled()) {
3720 return false;
3721 }
3722 if (isInsideQsContainer(ev) && !mIsBeingDragged) {
3723 return false;
3724 }
3725 mForcedScroll = null;
3726 initVelocityTrackerIfNotExists();
3727 mVelocityTracker.addMovement(ev);
3728
3729 final int action = ev.getAction();
3730
3731 switch (action & MotionEvent.ACTION_MASK) {
3732 case MotionEvent.ACTION_DOWN: {
3733 if (getChildCount() == 0 || !isInContentBounds(ev)) {
3734 return false;
3735 }
3736 boolean isBeingDragged = !mScroller.isFinished();
3737 setIsBeingDragged(isBeingDragged);
3738 /*
3739 * If being flinged and user touches, stop the fling. isFinished
3740 * will be false if being flinged.
3741 */
3742 if (!mScroller.isFinished()) {
3743 mScroller.forceFinished(true);
3744 }
3745
3746 // Remember where the motion event started
3747 mLastMotionY = (int) ev.getY();
3748 mDownX = (int) ev.getX();
3749 mActivePointerId = ev.getPointerId(0);
3750 break;
3751 }
3752 case MotionEvent.ACTION_MOVE:
3753 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
3754 if (activePointerIndex == -1) {
3755 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
3756 break;
3757 }
3758
3759 final int y = (int) ev.getY(activePointerIndex);
3760 final int x = (int) ev.getX(activePointerIndex);
3761 int deltaY = mLastMotionY - y;
3762 final int xDiff = Math.abs(x - mDownX);
3763 final int yDiff = Math.abs(deltaY);
3764 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
3765 setIsBeingDragged(true);
3766 if (deltaY > 0) {
3767 deltaY -= mTouchSlop;
3768 } else {
3769 deltaY += mTouchSlop;
3770 }
3771 }
3772 if (mIsBeingDragged) {
3773 // Scroll to follow the motion event
3774 mLastMotionY = y;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003775 float scrollAmount;
Gus Prevas0fa58d62019-01-11 13:58:40 -05003776 int range;
3777 if (ANCHOR_SCROLLING) {
3778 range = 0; // unused in the methods it's being passed to
3779 } else {
3780 range = getScrollRange();
3781 if (mExpandedInThisMotion) {
3782 range = Math.min(range, mMaxScrollAfterExpand);
3783 }
3784 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003785 if (deltaY < 0) {
3786 scrollAmount = overScrollDown(deltaY);
3787 } else {
3788 scrollAmount = overScrollUp(deltaY, range);
3789 }
3790
3791 // Calling customOverScrollBy will call onCustomOverScrolled, which
3792 // sets the scrolling if applicable.
3793 if (scrollAmount != 0.0f) {
3794 // The scrolling motion could not be compensated with the
3795 // existing overScroll, we have to scroll the view
3796 customOverScrollBy((int) scrollAmount, mOwnScrollY,
3797 range, getHeight() / 2);
3798 // If we're scrolling, leavebehinds should be dismissed
3799 checkSnoozeLeavebehind();
3800 }
3801 }
3802 break;
3803 case MotionEvent.ACTION_UP:
3804 if (mIsBeingDragged) {
3805 final VelocityTracker velocityTracker = mVelocityTracker;
3806 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
3807 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
3808
3809 if (shouldOverScrollFling(initialVelocity)) {
3810 onOverScrollFling(true, initialVelocity);
3811 } else {
3812 if (getChildCount() > 0) {
3813 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
3814 float currentOverScrollTop = getCurrentOverScrollAmount(true);
3815 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
3816 fling(-initialVelocity);
3817 } else {
3818 onOverScrollFling(false, initialVelocity);
3819 }
3820 } else {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003821 if (ANCHOR_SCROLLING) {
3822 // TODO
3823 } else {
3824 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
3825 getScrollRange())) {
3826 animateScroll();
3827 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003828 }
3829 }
3830 }
3831 }
3832 mActivePointerId = INVALID_POINTER;
3833 endDrag();
3834 }
3835
3836 break;
3837 case MotionEvent.ACTION_CANCEL:
3838 if (mIsBeingDragged && getChildCount() > 0) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05003839 if (ANCHOR_SCROLLING) {
3840 // TODO
3841 } else {
3842 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
3843 getScrollRange())) {
3844 animateScroll();
3845 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003846 }
3847 mActivePointerId = INVALID_POINTER;
3848 endDrag();
3849 }
3850 break;
3851 case MotionEvent.ACTION_POINTER_DOWN: {
3852 final int index = ev.getActionIndex();
3853 mLastMotionY = (int) ev.getY(index);
3854 mDownX = (int) ev.getX(index);
3855 mActivePointerId = ev.getPointerId(index);
3856 break;
3857 }
3858 case MotionEvent.ACTION_POINTER_UP:
3859 onSecondaryPointerUp(ev);
3860 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
3861 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
3862 break;
3863 }
3864 return true;
3865 }
3866
3867 @ShadeViewRefactor(RefactorComponent.INPUT)
3868 protected boolean isInsideQsContainer(MotionEvent ev) {
3869 return ev.getY() < mQsContainer.getBottom();
3870 }
3871
3872 @ShadeViewRefactor(RefactorComponent.INPUT)
3873 private void onOverScrollFling(boolean open, int initialVelocity) {
3874 if (mOverscrollTopChangedListener != null) {
3875 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
3876 }
3877 mDontReportNextOverScroll = true;
3878 setOverScrollAmount(0.0f, true, false);
3879 }
3880
3881
3882 @ShadeViewRefactor(RefactorComponent.INPUT)
3883 private void onSecondaryPointerUp(MotionEvent ev) {
3884 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
3885 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
3886 final int pointerId = ev.getPointerId(pointerIndex);
3887 if (pointerId == mActivePointerId) {
3888 // This was our active pointer going up. Choose a new
3889 // active pointer and adjust accordingly.
3890 // TODO: Make this decision more intelligent.
3891 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
3892 mLastMotionY = (int) ev.getY(newPointerIndex);
3893 mActivePointerId = ev.getPointerId(newPointerIndex);
3894 if (mVelocityTracker != null) {
3895 mVelocityTracker.clear();
3896 }
3897 }
3898 }
3899
3900 @ShadeViewRefactor(RefactorComponent.INPUT)
3901 private void endDrag() {
3902 setIsBeingDragged(false);
3903
3904 recycleVelocityTracker();
3905
3906 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
3907 setOverScrollAmount(0, true /* onTop */, true /* animate */);
3908 }
3909 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
3910 setOverScrollAmount(0, false /* onTop */, true /* animate */);
3911 }
3912 }
3913
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003914 @Override
3915 @ShadeViewRefactor(RefactorComponent.INPUT)
3916 public boolean onInterceptTouchEvent(MotionEvent ev) {
3917 initDownStates(ev);
3918 handleEmptySpaceClick(ev);
3919 boolean expandWantsIt = false;
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05003920 boolean swipingInProgress = mSwipingInProgress;
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04003921 if (!swipingInProgress && !mOnlyScrollingInThisMotion) {
3922 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
3923 }
3924 boolean scrollWantsIt = false;
3925 if (!swipingInProgress && !mExpandingNotification) {
3926 scrollWantsIt = onInterceptTouchEventScroll(ev);
3927 }
3928 boolean swipeWantsIt = false;
3929 if (!mIsBeingDragged
3930 && !mExpandingNotification
3931 && !mExpandedInThisMotion
3932 && !mOnlyScrollingInThisMotion
3933 && !mDisallowDismissInThisMotion) {
3934 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
3935 }
3936 // Check if we need to clear any snooze leavebehinds
3937 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
3938 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
3939 if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt &&
3940 !expandWantsIt && !scrollWantsIt) {
3941 mCheckForLeavebehind = false;
3942 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
3943 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
3944 false /* resetMenu */);
3945 }
3946 if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
3947 mCheckForLeavebehind = true;
3948 }
3949 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
3950 }
3951
3952 @ShadeViewRefactor(RefactorComponent.INPUT)
3953 private void handleEmptySpaceClick(MotionEvent ev) {
3954 switch (ev.getActionMasked()) {
3955 case MotionEvent.ACTION_MOVE:
3956 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
3957 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop)) {
3958 mTouchIsClick = false;
3959 }
3960 break;
3961 case MotionEvent.ACTION_UP:
3962 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
3963 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
3964 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
3965 }
3966 break;
3967 }
3968 }
3969
3970 @ShadeViewRefactor(RefactorComponent.INPUT)
3971 private void initDownStates(MotionEvent ev) {
3972 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
3973 mExpandedInThisMotion = false;
3974 mOnlyScrollingInThisMotion = !mScroller.isFinished();
3975 mDisallowScrollingInThisMotion = false;
3976 mDisallowDismissInThisMotion = false;
3977 mTouchIsClick = true;
3978 mInitialTouchX = ev.getX();
3979 mInitialTouchY = ev.getY();
3980 }
3981 }
3982
3983 @Override
3984 @ShadeViewRefactor(RefactorComponent.INPUT)
3985 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
3986 super.requestDisallowInterceptTouchEvent(disallowIntercept);
3987 if (disallowIntercept) {
3988 cancelLongPress();
3989 }
3990 }
3991
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04003992 @ShadeViewRefactor(RefactorComponent.INPUT)
Selim Cinek67b22602014-03-10 15:40:16 +01003993 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02003994 if (!isScrollingEnabled()) {
3995 return false;
3996 }
Selim Cinek67b22602014-03-10 15:40:16 +01003997 /*
3998 * This method JUST determines whether we want to intercept the motion.
3999 * If we return true, onMotionEvent will be called and we do the actual
4000 * scrolling there.
4001 */
4002
4003 /*
Jason Monke59dc402018-08-16 12:05:01 -04004004 * Shortcut the most recurring case: the user is in the dragging
4005 * state and is moving their finger. We want to intercept this
4006 * motion.
4007 */
Selim Cinek67b22602014-03-10 15:40:16 +01004008 final int action = ev.getAction();
4009 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
4010 return true;
4011 }
4012
Selim Cinek67b22602014-03-10 15:40:16 +01004013 switch (action & MotionEvent.ACTION_MASK) {
4014 case MotionEvent.ACTION_MOVE: {
4015 /*
4016 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04004017 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01004018 */
4019
4020 /*
Jason Monke59dc402018-08-16 12:05:01 -04004021 * Locally do absolute value. mLastMotionY is set to the y value
4022 * of the down event.
4023 */
Selim Cinek67b22602014-03-10 15:40:16 +01004024 final int activePointerId = mActivePointerId;
4025 if (activePointerId == INVALID_POINTER) {
4026 // If we don't have a valid id, the touch down wasn't on content.
4027 break;
4028 }
4029
4030 final int pointerIndex = ev.findPointerIndex(activePointerId);
4031 if (pointerIndex == -1) {
4032 Log.e(TAG, "Invalid pointerId=" + activePointerId
4033 + " in onInterceptTouchEvent");
4034 break;
4035 }
4036
4037 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02004038 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01004039 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02004040 final int xDiff = Math.abs(x - mDownX);
4041 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01004042 setIsBeingDragged(true);
4043 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02004044 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01004045 initVelocityTrackerIfNotExists();
4046 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01004047 }
4048 break;
4049 }
4050
4051 case MotionEvent.ACTION_DOWN: {
4052 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05304053 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek34ed7c02017-09-08 15:03:12 -07004054 if (getChildAtPosition(ev.getX(), y, false /* requireMinHeight */) == null) {
Selim Cinek67b22602014-03-10 15:40:16 +01004055 setIsBeingDragged(false);
4056 recycleVelocityTracker();
4057 break;
4058 }
4059
4060 /*
4061 * Remember location of down touch.
4062 * ACTION_DOWN always refers to pointer index 0.
4063 */
4064 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02004065 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01004066 mActivePointerId = ev.getPointerId(0);
4067
4068 initOrResetVelocityTracker();
4069 mVelocityTracker.addMovement(ev);
4070 /*
Jason Monke59dc402018-08-16 12:05:01 -04004071 * If being flinged and user touches the screen, initiate drag;
4072 * otherwise don't. mScroller.isFinished should be false when
4073 * being flinged.
4074 */
Selim Cinek67b22602014-03-10 15:40:16 +01004075 boolean isBeingDragged = !mScroller.isFinished();
4076 setIsBeingDragged(isBeingDragged);
4077 break;
4078 }
4079
4080 case MotionEvent.ACTION_CANCEL:
4081 case MotionEvent.ACTION_UP:
4082 /* Release the drag */
4083 setIsBeingDragged(false);
4084 mActivePointerId = INVALID_POINTER;
4085 recycleVelocityTracker();
Gus Prevas0fa58d62019-01-11 13:58:40 -05004086 if (ANCHOR_SCROLLING) {
4087 // TODO
4088 } else {
4089 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
4090 animateScroll();
4091 }
Selim Cinek67b22602014-03-10 15:40:16 +01004092 }
4093 break;
4094 case MotionEvent.ACTION_POINTER_UP:
4095 onSecondaryPointerUp(ev);
4096 break;
4097 }
4098
4099 /*
Jason Monke59dc402018-08-16 12:05:01 -04004100 * The only time we want to intercept motion events is if we are in the
4101 * drag mode.
4102 */
Selim Cinek67b22602014-03-10 15:40:16 +01004103 return mIsBeingDragged;
4104 }
4105
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004106 /**
4107 * @return Whether the specified motion event is actually happening over the content.
4108 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004109 @ShadeViewRefactor(RefactorComponent.INPUT)
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004110 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01004111 return isInContentBounds(event.getY());
4112 }
4113
Jorim Jaggife6bfa62014-05-07 23:23:18 +02004114
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004115 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004116 @ShadeViewRefactor(RefactorComponent.INPUT)
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004117 void setIsBeingDragged(boolean isDragged) {
Selim Cinek67b22602014-03-10 15:40:16 +01004118 mIsBeingDragged = isDragged;
4119 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004120 requestDisallowInterceptTouchEvent(true);
Geoffrey Pitsch409db272017-08-28 14:51:52 +00004121 cancelLongPress();
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004122 resetExposedMenuView(true /* animate */, true /* force */);
Selim Cinek67b22602014-03-10 15:40:16 +01004123 }
4124 }
4125
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004126 @ShadeViewRefactor(RefactorComponent.INPUT)
4127 public void requestDisallowLongPress() {
4128 cancelLongPress();
4129 }
4130
4131 @ShadeViewRefactor(RefactorComponent.INPUT)
4132 public void requestDisallowDismiss() {
4133 mDisallowDismissInThisMotion = true;
4134 }
4135
4136 @ShadeViewRefactor(RefactorComponent.INPUT)
4137 public void cancelLongPress() {
4138 mSwipeHelper.cancelLongPress();
4139 }
4140
4141 @ShadeViewRefactor(RefactorComponent.INPUT)
4142 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
4143 mOnEmptySpaceClickListener = listener;
4144 }
4145
4146 /** @hide */
4147 @Override
4148 @ShadeViewRefactor(RefactorComponent.INPUT)
4149 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
4150 if (super.performAccessibilityActionInternal(action, arguments)) {
4151 return true;
4152 }
4153 if (!isEnabled()) {
4154 return false;
4155 }
4156 int direction = -1;
4157 switch (action) {
4158 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
4159 // fall through
4160 case android.R.id.accessibilityActionScrollDown:
4161 direction = 1;
4162 // fall through
4163 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
4164 // fall through
4165 case android.R.id.accessibilityActionScrollUp:
Gus Prevas0fa58d62019-01-11 13:58:40 -05004166 if (ANCHOR_SCROLLING) {
4167 // TODO
4168 } else {
4169 final int viewportHeight =
4170 getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
4171 - mShelf.getIntrinsicHeight();
4172 final int targetScrollY = Math.max(0,
4173 Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
4174 if (targetScrollY != mOwnScrollY) {
4175 mScroller.startScroll(mScrollX, mOwnScrollY, 0,
4176 targetScrollY - mOwnScrollY);
4177 animateScroll();
4178 return true;
4179 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004180 }
4181 break;
4182 }
4183 return false;
4184 }
4185
4186 @ShadeViewRefactor(RefactorComponent.INPUT)
4187 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4188 NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
4189 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
4190 View translatingParentView = mSwipeHelper.getTranslatingParentView();
4191 View view = null;
4192 if (guts != null && !guts.getGutsContent().isLeavebehind()) {
4193 // Only close visible guts if they're not a leavebehind.
4194 view = guts;
4195 } else if (menuRow != null && menuRow.isMenuVisible()
4196 && translatingParentView != null) {
4197 // Checking menu
4198 view = translatingParentView;
4199 }
4200 if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) {
4201 // Touch was outside visible guts / menu notification, close what's visible
4202 mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */,
4203 false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
4204 false /* resetMenu */);
4205 resetExposedMenuView(true /* animate */, true /* force */);
4206 }
4207 }
4208
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05004209 @ShadeViewRefactor(RefactorComponent.INPUT)
4210 private void setSwipingInProgress(boolean swiping) {
4211 mSwipingInProgress = swiping;
4212 if (swiping) {
4213 requestDisallowInterceptTouchEvent(true);
4214 }
4215 }
4216
Selim Cinek67b22602014-03-10 15:40:16 +01004217 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004218 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek67b22602014-03-10 15:40:16 +01004219 public void onWindowFocusChanged(boolean hasWindowFocus) {
4220 super.onWindowFocusChanged(hasWindowFocus);
4221 if (!hasWindowFocus) {
Geoffrey Pitsch409db272017-08-28 14:51:52 +00004222 cancelLongPress();
Selim Cinek67b22602014-03-10 15:40:16 +01004223 }
4224 }
Selim Cinekfab078b2014-03-27 22:45:58 +01004225
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07004226 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004227 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Adrian Roos181385c2016-05-05 17:45:44 -04004228 public void clearChildFocus(View child) {
4229 super.clearChildFocus(child);
4230 if (mForcedScroll == child) {
4231 mForcedScroll = null;
4232 }
4233 }
4234
Selim Cinekfab078b2014-03-27 22:45:58 +01004235 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004236 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekfab078b2014-03-27 22:45:58 +01004237 public boolean isScrolledToTop() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004238 if (ANCHOR_SCROLLING) {
4239 updateScrollAnchor();
4240 // TODO: once we're recycling this will need to check the adapter position of the child
4241 return mScrollAnchorView == getFirstChildNotGone() && mScrollAnchorViewY >= 0;
4242 } else {
4243 return mOwnScrollY == 0;
4244 }
Selim Cinekfab078b2014-03-27 22:45:58 +01004245 }
4246
4247 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004248 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004249 public boolean isScrolledToBottom() {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004250 if (ANCHOR_SCROLLING) {
Gus Prevascdc98342019-01-14 14:29:44 -05004251 return getMaxPositiveScrollAmount() <= 0;
Gus Prevas0fa58d62019-01-11 13:58:40 -05004252 } else {
4253 return mOwnScrollY >= getScrollRange();
4254 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004255 }
4256
4257 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004258 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfab078b2014-03-27 22:45:58 +01004259 public View getHostView() {
4260 return this;
4261 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02004262
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004263 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004264 public int getEmptyBottomMargin() {
Selim Cinekdb167372016-11-17 15:41:17 -08004265 return Math.max(mMaxLayoutHeight - mContentHeight, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01004266 }
4267
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004268 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellorc2dbe492017-03-30 13:22:03 -07004269 public void checkSnoozeLeavebehind() {
4270 if (mCheckForLeavebehind) {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04004271 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
Eliot Courtney47098cb2017-10-18 17:30:30 +09004272 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
4273 false /* resetMenu */);
Mady Mellorc2dbe492017-03-30 13:22:03 -07004274 mCheckForLeavebehind = false;
4275 }
4276 }
4277
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004278 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellorc2dbe492017-03-30 13:22:03 -07004279 public void resetCheckSnoozeLeavebehind() {
4280 mCheckForLeavebehind = true;
4281 }
4282
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004283 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek1685e632014-04-08 02:27:49 +02004284 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02004285 mIsExpansionChanging = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004286 mAmbientState.setExpansionChanging(true);
Mady Mellorc2dbe492017-03-30 13:22:03 -07004287 checkSnoozeLeavebehind();
Selim Cinek1685e632014-04-08 02:27:49 +02004288 }
4289
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004290 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek1685e632014-04-08 02:27:49 +02004291 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02004292 mIsExpansionChanging = false;
Mady Mellorc2dbe492017-03-30 13:22:03 -07004293 resetCheckSnoozeLeavebehind();
Selim Cinekd5ab6452016-12-08 16:34:00 -08004294 mAmbientState.setExpansionChanging(false);
Selim Cinek4fe3e472014-07-03 16:32:54 +02004295 if (!mIsExpanded) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004296 resetScrollPosition();
Jason Monk2a6ea9c2017-01-26 11:14:51 -05004297 mStatusBar.resetUserExpandedStates();
Selim Cinek5b1591a2017-07-03 17:05:01 +02004298 clearTemporaryViews();
4299 clearUserLockedViews();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05004300 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
Selim Cinekd4c32302018-11-19 19:43:14 -08004301 if (draggedViews.size() > 0) {
4302 draggedViews.clear();
4303 updateContinuousShadowDrawing();
4304 }
Selim Cinek5b1591a2017-07-03 17:05:01 +02004305 }
4306 }
Selim Cinekf336f4c2014-11-12 16:58:16 +01004307
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004308 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5b1591a2017-07-03 17:05:01 +02004309 private void clearUserLockedViews() {
4310 for (int i = 0; i < getChildCount(); i++) {
4311 ExpandableView child = (ExpandableView) getChildAt(i);
4312 if (child instanceof ExpandableNotificationRow) {
4313 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4314 row.setUserLocked(false);
4315 }
4316 }
4317 }
4318
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004319 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek5b1591a2017-07-03 17:05:01 +02004320 private void clearTemporaryViews() {
Selim Cinek9dd0d042018-05-14 18:12:42 -07004321 // lets make sure nothing is transient anymore
Rohan Shah8ee53652018-04-05 11:13:50 -07004322 clearTemporaryViewsInGroup(this);
Selim Cinek5b1591a2017-07-03 17:05:01 +02004323 for (int i = 0; i < getChildCount(); i++) {
4324 ExpandableView child = (ExpandableView) getChildAt(i);
4325 if (child instanceof ExpandableNotificationRow) {
4326 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Rohan Shah8ee53652018-04-05 11:13:50 -07004327 clearTemporaryViewsInGroup(row.getChildrenContainer());
Selim Cinekd1395642016-04-28 12:22:42 -07004328 }
Selim Cinek4fe3e472014-07-03 16:32:54 +02004329 }
Selim Cinek1685e632014-04-08 02:27:49 +02004330 }
4331
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004332 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Rohan Shah8ee53652018-04-05 11:13:50 -07004333 private void clearTemporaryViewsInGroup(ViewGroup viewGroup) {
Selim Cinekd1395642016-04-28 12:22:42 -07004334 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
Selim Cinek81f26d32016-05-09 18:54:10 -04004335 viewGroup.removeTransientView(viewGroup.getTransientView(0));
Selim Cinekd1395642016-04-28 12:22:42 -07004336 }
4337 }
4338
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004339 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004340 public void onPanelTrackingStarted() {
4341 mPanelTracking = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004342 mAmbientState.setPanelTracking(true);
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004343 resetExposedMenuView(true /* animate */, true /* force */);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004344 }
Jason Monke59dc402018-08-16 12:05:01 -04004345
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004346 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004347 public void onPanelTrackingStopped() {
4348 mPanelTracking = false;
Selim Cinekd5ab6452016-12-08 16:34:00 -08004349 mAmbientState.setPanelTracking(false);
Jorim Jaggie4b840d2015-06-30 16:19:17 -07004350 }
4351
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004352 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb24e0a92015-06-09 20:17:30 -07004353 public void resetScrollPosition() {
4354 mScroller.abortAnimation();
Gus Prevas0fa58d62019-01-11 13:58:40 -05004355 if (ANCHOR_SCROLLING) {
4356 // TODO: once we're recycling this will need to modify the adapter position instead
4357 mScrollAnchorView = getFirstChildNotGone();
4358 mScrollAnchorViewY = 0;
4359 updateOnScrollChange();
4360 } else {
4361 setOwnScrollY(0);
4362 }
Selim Cinekb24e0a92015-06-09 20:17:30 -07004363 }
4364
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004365 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek3d6ae232019-01-04 14:14:33 -08004366 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02004367 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02004368 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02004369 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinek3d6ae232019-01-04 14:14:33 -08004370 mAmbientState.setShadeExpanded(isExpanded);
4371 mStateAnimator.setShadeExpanded(isExpanded);
shawnlin3a950c32019-05-15 20:06:10 +08004372 mSwipeHelper.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02004373 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08004374 if (!mIsExpanded) {
4375 mGroupManager.collapseAllGroups();
Selim Cinek5b1591a2017-07-03 17:05:01 +02004376 mExpandHelper.cancelImmediately();
Selim Cinek9184f9c2016-02-02 17:36:53 -08004377 }
Selim Cinekcab4a602014-09-03 14:47:57 +02004378 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02004379 updateChronometers();
Selim Cinekf3fa6852016-12-20 18:36:02 +01004380 requestChildrenUpdate();
Selim Cinek98713a42015-09-21 15:47:20 +02004381 }
4382 }
4383
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004384 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek98713a42015-09-21 15:47:20 +02004385 private void updateChronometers() {
4386 int childCount = getChildCount();
4387 for (int i = 0; i < childCount; i++) {
4388 updateChronometerForChild(getChildAt(i));
4389 }
4390 }
4391
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004392 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek98713a42015-09-21 15:47:20 +02004393 private void updateChronometerForChild(View child) {
4394 if (child instanceof ExpandableNotificationRow) {
4395 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
4396 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02004397 }
Selim Cinek1685e632014-04-08 02:27:49 +02004398 }
4399
Jorim Jaggibe565df2014-04-28 17:51:23 +02004400 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01004401 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004402 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02004403 updateScrollPositionOnExpandInBottom(view);
4404 clampScrollPosition();
Lucas Dupin60661a62018-04-12 10:50:13 -07004405 notifyHeightChangeListener(view, needsAnimation);
Selim Cinekbc243a92016-09-27 16:35:13 -07004406 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
4407 ? (ExpandableNotificationRow) view
4408 : null;
Gus Prevase2d6f042018-10-17 15:25:30 -04004409 NotificationSection firstSection = getFirstVisibleSection();
4410 ActivatableNotificationView firstVisibleChild =
4411 firstSection == null ? null : firstSection.getFirstVisibleChild();
4412 if (row != null) {
4413 if (row == firstVisibleChild
4414 || row.getNotificationParent() == firstVisibleChild) {
4415 updateAlgorithmLayoutMinHeight();
4416 }
Selim Cinekbc243a92016-09-27 16:35:13 -07004417 }
Selim Cinekb5605e52015-02-20 18:21:41 +01004418 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08004419 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01004420 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004421 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02004422 }
4423
Selim Cineka5e211b2014-08-11 17:35:48 +02004424 @Override
4425 public void onReset(ExpandableView view) {
Selim Cinek51ae05d2014-09-09 15:51:38 +02004426 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02004427 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02004428 }
4429
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004430 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekf7a14c02014-07-07 14:01:46 +02004431 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
Selim Cinek51d21972017-07-19 17:39:20 -07004432 if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02004433 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Gus Prevas0fa58d62019-01-11 13:58:40 -05004434 // TODO: once we're recycling this will need to check the adapter position of the child
Selim Cinekcb9400a2015-06-03 16:56:13 +02004435 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08004436 if (row.isSummaryWithChildren()) {
4437 return;
4438 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02004439 // We are actually expanding this view
Selim Cinek4e4cac32016-03-11 16:45:52 -08004440 float endPosition = row.getTranslationY() + row.getActualHeight();
Selim Cinek388df6d2015-10-22 13:25:11 -07004441 if (row.isChildInGroup()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08004442 endPosition += row.getNotificationParent().getTranslationY();
Selim Cinek388df6d2015-10-22 13:25:11 -07004443 }
Selim Cinekdb167372016-11-17 15:41:17 -08004444 int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
Gus Prevase2d6f042018-10-17 15:25:30 -04004445 NotificationSection lastSection = getLastVisibleSection();
4446 ActivatableNotificationView lastVisibleChild =
4447 lastSection == null ? null : lastSection.getLastVisibleChild();
4448 if (row != lastVisibleChild && mShelf.getVisibility() != GONE) {
Selim Cinekdb167372016-11-17 15:41:17 -08004449 layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
4450 }
4451 if (endPosition > layoutEnd) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05004452 if (ANCHOR_SCROLLING) {
4453 mScrollAnchorViewY -= (endPosition - layoutEnd);
4454 updateScrollAnchor();
4455 updateOnScrollChange();
4456 } else {
4457 setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
4458 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02004459 mDisallowScrollingInThisMotion = true;
4460 }
4461 }
4462 }
4463 }
4464
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004465 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggibe565df2014-04-28 17:51:23 +02004466 public void setOnHeightChangedListener(
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04004467 ExpandableView.OnHeightChangedListener onHeightChangedListener) {
4468 this.mOnHeightChangedListener = onHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01004469 }
4470
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004471 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02004472 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08004473 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02004474 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07004475 runAnimationFinishedRunnables();
Selim Cinek9dd0d042018-05-14 18:12:42 -07004476 clearTransient();
Selim Cinek8fc78752016-07-13 14:34:56 -07004477 clearHeadsUpDisappearRunning();
4478 }
4479
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004480 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek8fc78752016-07-13 14:34:56 -07004481 private void clearHeadsUpDisappearRunning() {
4482 for (int i = 0; i < getChildCount(); i++) {
4483 View view = getChildAt(i);
4484 if (view instanceof ExpandableNotificationRow) {
Selim Cinek76e813c2016-07-14 11:16:58 -07004485 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcafa87f2016-10-26 17:00:17 -07004486 row.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07004487 if (row.isSummaryWithChildren()) {
4488 for (ExpandableNotificationRow child : row.getNotificationChildren()) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07004489 child.setHeadsUpAnimatingAway(false);
Selim Cinek76e813c2016-07-14 11:16:58 -07004490 }
4491 }
Selim Cinek8fc78752016-07-13 14:34:56 -07004492 }
4493 }
Selim Cinek0fccc722015-07-29 17:04:36 -07004494 }
4495
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004496 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek9dd0d042018-05-14 18:12:42 -07004497 private void clearTransient() {
4498 for (ExpandableView view : mClearTransientViewsWhenFinished) {
4499 StackStateAnimator.removeTransientView(view);
Selim Cinek0fccc722015-07-29 17:04:36 -07004500 }
Selim Cinek9dd0d042018-05-14 18:12:42 -07004501 mClearTransientViewsWhenFinished.clear();
Selim Cinek32a59fd32015-06-10 13:54:42 -07004502 }
4503
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004504 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek32a59fd32015-06-10 13:54:42 -07004505 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004506 for (Runnable runnable : mAnimationFinishedRunnables) {
4507 runnable.run();
4508 }
4509 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02004510 }
4511
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004512 /**
4513 * See {@link AmbientState#setDimmed}.
4514 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004515 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004516 public void setDimmed(boolean dimmed, boolean animate) {
Selim Cinek8a9308d2017-08-24 09:31:08 -07004517 dimmed &= onKeyguard();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004518 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02004519 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004520 mDimmedNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004521 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08004522 animateDimmed(dimmed);
4523 } else {
4524 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004525 }
4526 requestChildrenUpdate();
4527 }
4528
Selim Cinek8a9308d2017-08-24 09:31:08 -07004529 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004530 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek8a9308d2017-08-24 09:31:08 -07004531 boolean isDimmed() {
4532 return mAmbientState.isDimmed();
4533 }
4534
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004535 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd35c2792016-01-21 13:20:57 -08004536 private void setDimAmount(float dimAmount) {
4537 mDimAmount = dimAmount;
4538 updateBackgroundDimming();
4539 }
4540
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004541 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekd35c2792016-01-21 13:20:57 -08004542 private void animateDimmed(boolean dimmed) {
4543 if (mDimAnimator != null) {
4544 mDimAnimator.cancel();
4545 }
4546 float target = dimmed ? 1.0f : 0.0f;
4547 if (target == mDimAmount) {
4548 return;
4549 }
4550 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
4551 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
4552 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
4553 mDimAnimator.addListener(mDimEndListener);
4554 mDimAnimator.addUpdateListener(mDimUpdateListener);
4555 mDimAnimator.start();
4556 }
Evan Laird91d0f102018-09-18 17:39:55 -04004557
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004558 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Evan Laird91d0f102018-09-18 17:39:55 -04004559 private void setHideSensitive(boolean hideSensitive, boolean animate) {
Jorim Jaggiae441282014-08-01 02:45:18 +02004560 if (hideSensitive != mAmbientState.isHideSensitive()) {
4561 int childCount = getChildCount();
4562 for (int i = 0; i < childCount; i++) {
4563 ExpandableView v = (ExpandableView) getChildAt(i);
4564 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
4565 }
4566 mAmbientState.setHideSensitive(hideSensitive);
4567 if (animate && mAnimationsEnabled) {
4568 mHideSensitiveNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004569 mNeedsAnimation = true;
Jorim Jaggiae441282014-08-01 02:45:18 +02004570 }
Selim Cinek0b9cf462017-12-07 16:31:03 -08004571 updateContentHeight();
Jorim Jaggiae441282014-08-01 02:45:18 +02004572 requestChildrenUpdate();
4573 }
4574 }
4575
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004576 /**
4577 * See {@link AmbientState#setActivatedChild}.
4578 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004579 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka32ab602014-06-11 15:06:01 +02004580 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004581 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02004582 if (mAnimationsEnabled) {
4583 mActivateNeedsAnimation = true;
Jason Monke59dc402018-08-16 12:05:01 -04004584 mNeedsAnimation = true;
Jorim Jaggi75c95042014-05-16 19:09:59 +02004585 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004586 requestChildrenUpdate();
4587 }
4588
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004589 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka32ab602014-06-11 15:06:01 +02004590 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004591 return mAmbientState.getActivatedChild();
4592 }
4593
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004594 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek572bbd42014-04-25 16:43:27 +02004595 private void applyCurrentState() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -05004596 int numChildren = getChildCount();
4597 for (int i = 0; i < numChildren; i++) {
4598 ExpandableView child = (ExpandableView) getChildAt(i);
4599 child.applyViewState();
4600 }
4601
Selim Cinekf4c19962014-05-01 21:55:31 +02004602 if (mListener != null) {
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09004603 mListener.onChildLocationsChanged();
Selim Cinekf4c19962014-05-01 21:55:31 +02004604 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07004605 runAnimationFinishedRunnables();
Selim Cinekea66ca02016-05-24 13:33:47 -07004606 setAnimationRunning(false);
Selim Cinek6811d722016-01-19 17:53:12 -08004607 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08004608 updateViewShadows();
Selim Cinekb0ee18f2017-12-21 16:15:53 -08004609 updateClippingToTopRoundedCorner();
Selim Cinek33223572016-02-19 19:32:22 -08004610 }
4611
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004612 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek33223572016-02-19 19:32:22 -08004613 private void updateViewShadows() {
4614 // we need to work around an issue where the shadow would not cast between siblings when
4615 // their z difference is between 0 and 0.1
4616
4617 // Lefts first sort by Z difference
4618 for (int i = 0; i < getChildCount(); i++) {
4619 ExpandableView child = (ExpandableView) getChildAt(i);
4620 if (child.getVisibility() != GONE) {
4621 mTmpSortedChildren.add(child);
4622 }
4623 }
4624 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
4625
4626 // Now lets update the shadow for the views
4627 ExpandableView previous = null;
4628 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
4629 ExpandableView expandableView = mTmpSortedChildren.get(i);
4630 float translationZ = expandableView.getTranslationZ();
4631 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
4632 float diff = otherZ - translationZ;
4633 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
4634 // There is no fake shadow to be drawn
4635 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
4636 } else {
4637 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
Mady Mellorb0a82462016-04-30 17:31:02 -07004638 expandableView.getTranslationY() - previous.getExtraBottomPadding();
4639 expandableView.setFakeShadowIntensity(
4640 diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
Selim Cinek33223572016-02-19 19:32:22 -08004641 previous.getOutlineAlpha(), (int) yLocation,
4642 previous.getOutlineTranslation());
4643 }
4644 previous = expandableView;
4645 }
4646
4647 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02004648 }
4649
Lucas Dupine17ce522017-07-17 15:45:06 -07004650 /**
4651 * Update colors of "dismiss" and "empty shade" views.
4652 *
4653 * @param lightTheme True if light theme should be used.
4654 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004655 @ShadeViewRefactor(RefactorComponent.DECORATOR)
Lucas Dupine17ce522017-07-17 15:45:06 -07004656 public void updateDecorViews(boolean lightTheme) {
4657 if (lightTheme == mUsingLightTheme) {
4658 return;
4659 }
4660 mUsingLightTheme = lightTheme;
4661 Context context = new ContextThemeWrapper(mContext,
4662 lightTheme ? R.style.Theme_SystemUI_Light : R.style.Theme_SystemUI);
Jason Changb4e879d2018-04-11 11:17:58 +08004663 final int textColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor);
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004664 mFooterView.setTextColor(textColor);
Lucas Dupine17ce522017-07-17 15:45:06 -07004665 mEmptyShadeView.setTextColor(textColor);
4666 }
4667
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004668 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004669 public void goToFullShade(long delay) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004670 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004671 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004672 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004673 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02004674 }
4675
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004676 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek1408eb52014-06-02 14:45:38 +02004677 public void cancelExpandHelper() {
4678 mExpandHelper.cancel();
4679 }
4680
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004681 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek1408eb52014-06-02 14:45:38 +02004682 public void setIntrinsicPadding(int intrinsicPadding) {
4683 mIntrinsicPadding = intrinsicPadding;
Selim Cinek1f624952017-06-08 19:11:50 -07004684 mAmbientState.setIntrinsicPadding(intrinsicPadding);
Selim Cinek1408eb52014-06-02 14:45:38 +02004685 }
4686
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004687 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi30c305c2014-07-01 23:34:41 +02004688 public int getIntrinsicPadding() {
4689 return mIntrinsicPadding;
4690 }
4691
Christoph Studer6e3eceb2014-04-01 18:40:27 +02004692 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02004693 * @return the y position of the first notification
4694 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004695 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggi457cc352014-06-02 22:47:42 +02004696 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07004697 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02004698 }
4699
Selim Cinekc0ce82d2014-06-10 13:21:15 +02004700 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004701 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc0ce82d2014-06-10 13:21:15 +02004702 public boolean shouldDelayChildPressedState() {
4703 return true;
4704 }
4705
Jorim Jaggi457cc352014-06-02 22:47:42 +02004706 /**
John Spurlockbf370992014-06-17 13:58:31 -04004707 * See {@link AmbientState#setDark}.
4708 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004709 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004710 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
Adrian Roos260c1f72017-08-07 15:52:26 +02004711 if (mAmbientState.isDark() == dark) {
4712 return;
4713 }
John Spurlockbf370992014-06-17 13:58:31 -04004714 mAmbientState.setDark(dark);
4715 if (animate && mAnimationsEnabled) {
4716 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004717 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004718 mNeedsAnimation = true;
Selim Cinek6811d722016-01-19 17:53:12 -08004719 } else {
Lucas Dupin8e9fa2d2018-01-29 15:36:35 -08004720 setDarkAmount(dark ? 1f : 0f);
Selim Cinek6811d722016-01-19 17:53:12 -08004721 updateBackground();
Selim Cinek6811d722016-01-19 17:53:12 -08004722 }
Lucas Dupin8e9fa2d2018-01-29 15:36:35 -08004723 requestChildrenUpdate();
Anthony Chen3cb3ad92016-12-01 10:58:47 -08004724 updateWillNotDraw();
Adrian Roos7a9551a2017-01-11 12:27:49 -08004725 notifyHeightChangeListener(mShelf);
John Spurlockbf370992014-06-17 13:58:31 -04004726 }
4727
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004728 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Bill Line60aa1e2018-06-13 18:07:15 +08004729 private void updatePanelTranslation() {
Lucas Dupinb46d0a22019-01-11 16:57:16 -08004730 setTranslationX(mHorizontalPanelTranslation + mAntiBurnInOffsetX * mInterpolatedDarkAmount);
Bill Line60aa1e2018-06-13 18:07:15 +08004731 }
4732
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004733 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupinb46d0a22019-01-11 16:57:16 -08004734 public void setHorizontalPanelTranslation(float verticalPanelTranslation) {
4735 mHorizontalPanelTranslation = verticalPanelTranslation;
Bill Line60aa1e2018-06-13 18:07:15 +08004736 updatePanelTranslation();
Lucas Dupin0cd882f2018-01-30 12:19:49 -08004737 }
4738
Anthony Chen3cb3ad92016-12-01 10:58:47 -08004739 /**
4740 * Updates whether or not this Layout will perform its own custom drawing (i.e. whether or
4741 * not {@link #onDraw(Canvas)} is called). This method should be called whenever the
4742 * {@link #mAmbientState}'s dark mode is toggled.
4743 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004744 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Anthony Chen3cb3ad92016-12-01 10:58:47 -08004745 private void updateWillNotDraw() {
Lucas Dupind285cf02018-01-18 09:18:23 -08004746 boolean willDraw = mShouldDrawNotificationBackground || DEBUG;
Adrian Roosf0b4f962017-05-25 11:53:11 -07004747 setWillNotDraw(!willDraw);
Anthony Chen3cb3ad92016-12-01 10:58:47 -08004748 }
4749
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004750 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupind285cf02018-01-18 09:18:23 -08004751 private void setDarkAmount(float darkAmount) {
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004752 setDarkAmount(darkAmount, darkAmount);
4753 }
4754
4755 /**
4756 * Sets the current dark amount.
4757 *
Jason Monke59dc402018-08-16 12:05:01 -04004758 * @param linearDarkAmount The dark amount that follows linear interpoloation in the
4759 * animation,
4760 * i.e. animates from 0 to 1 or vice-versa in a linear manner.
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004761 * @param interpolatedDarkAmount The dark amount that follows the actual interpolation of the
Jason Monke59dc402018-08-16 12:05:01 -04004762 * animation curve.
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004763 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004764 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004765 public void setDarkAmount(float linearDarkAmount, float interpolatedDarkAmount) {
4766 mLinearDarkAmount = linearDarkAmount;
4767 mInterpolatedDarkAmount = interpolatedDarkAmount;
Lucas Dupinb561eda2018-04-09 17:25:04 -07004768 boolean wasFullyDark = mAmbientState.isFullyDark();
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004769 boolean wasDarkAtAll = mAmbientState.isDarkAtAll();
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004770 mAmbientState.setDarkAmount(interpolatedDarkAmount);
Selim Cinek9bfc7a52018-06-11 16:09:00 -07004771 boolean nowFullyDark = mAmbientState.isFullyDark();
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004772 boolean nowDarkAtAll = mAmbientState.isDarkAtAll();
Selim Cinek9bfc7a52018-06-11 16:09:00 -07004773 if (nowFullyDark != wasFullyDark) {
Lucas Dupin16cfe452018-02-08 13:14:50 -08004774 updateContentHeight();
Selim Cinek1e059272019-05-21 18:15:00 -07004775 if (nowFullyDark) {
Lucas Dupin7fc9dc12019-01-03 09:19:43 -08004776 updateDarkShelfVisibility();
4777 }
Lucas Dupin16cfe452018-02-08 13:14:50 -08004778 }
Gus Prevas99ba4ba2018-10-01 16:40:23 -04004779 if (!wasDarkAtAll && nowDarkAtAll) {
4780 resetExposedMenuView(true /* animate */, true /* animate */);
4781 }
Lucas Dupin64e2f572019-03-21 14:21:14 -07004782 if (nowFullyDark != wasFullyDark || wasDarkAtAll != nowDarkAtAll) {
4783 invalidateOutline();
4784 }
Lucas Dupin60661a62018-04-12 10:50:13 -07004785 updateAlgorithmHeightAndPadding();
Selim Cinek972123d2016-05-03 14:25:58 -07004786 updateBackgroundDimming();
Bill Line60aa1e2018-06-13 18:07:15 +08004787 updatePanelTranslation();
Lucas Dupinb561eda2018-04-09 17:25:04 -07004788 requestChildrenUpdate();
Selim Cinek972123d2016-05-03 14:25:58 -07004789 }
4790
Lucas Dupin7fc9dc12019-01-03 09:19:43 -08004791 private void updateDarkShelfVisibility() {
4792 DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
4793 if (dozeParameters.shouldControlScreenOff()) {
4794 mShelf.fadeInTranslating();
4795 }
4796 updateClipping();
4797 }
4798
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004799 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggic4cf07a2018-07-05 18:28:12 +02004800 public void notifyDarkAnimationStart(boolean dark) {
4801 // We only swap the scaling factor if we're fully dark or fully awake to avoid
4802 // interpolation issues when playing with the power button.
4803 if (mInterpolatedDarkAmount == 0 || mInterpolatedDarkAmount == 1) {
4804 mBackgroundXFactor = dark ? 1.8f : 1.5f;
4805 mDarkXInterpolator = dark
4806 ? Interpolators.FAST_OUT_SLOW_IN_REVERSE
4807 : Interpolators.FAST_OUT_SLOW_IN;
4808 }
Selim Cinek972123d2016-05-03 14:25:58 -07004809 }
4810
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004811 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004812 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
Selim Cinekbc243a92016-09-27 16:35:13 -07004813 if (screenLocation == null || screenLocation.y < mTopPadding) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004814 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
4815 }
4816 if (screenLocation.y > getBottomMostNotificationBottom()) {
4817 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
4818 }
4819 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
4820 if (child != null) {
4821 return getNotGoneIndex(child);
4822 } else {
4823 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
4824 }
4825 }
4826
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004827 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004828 private int getNotGoneIndex(View child) {
4829 int count = getChildCount();
4830 int notGoneIndex = 0;
4831 for (int i = 0; i < count; i++) {
4832 View v = getChildAt(i);
4833 if (child == v) {
4834 return notGoneIndex;
4835 }
4836 if (v.getVisibility() != View.GONE) {
4837 notGoneIndex++;
4838 }
4839 }
4840 return -1;
4841 }
4842
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004843 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004844 public void setFooterView(@NonNull FooterView footerView) {
Selim Cinek01af3342016-02-09 19:25:31 -08004845 int index = -1;
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004846 if (mFooterView != null) {
4847 index = indexOfChild(mFooterView);
4848 removeView(mFooterView);
Selim Cinek01af3342016-02-09 19:25:31 -08004849 }
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004850 mFooterView = footerView;
4851 addView(mFooterView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04004852 }
4853
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004854 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004855 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08004856 int index = -1;
4857 if (mEmptyShadeView != null) {
4858 index = indexOfChild(mEmptyShadeView);
4859 removeView(mEmptyShadeView);
4860 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004861 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08004862 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004863 }
4864
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004865 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004866 public void updateEmptyShadeView(boolean visible) {
Selim Cinekd60ef9e2018-05-16 16:01:05 -07004867 mEmptyShadeView.setVisible(visible, mIsExpanded && mAnimationsEnabled);
Lucas Dupinc9274ff2018-05-09 17:40:20 -07004868
4869 int oldTextRes = mEmptyShadeView.getTextResource();
4870 int newTextRes = mStatusBar.areNotificationsHidden()
4871 ? R.string.dnd_suppressing_shade_text : R.string.empty_shade_text;
4872 if (oldTextRes != newTextRes) {
4873 mEmptyShadeView.setText(newTextRes);
4874 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02004875 }
4876
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004877 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004878 public void updateFooterView(boolean visible, boolean showDismissView) {
4879 if (mFooterView == null) {
Anthony Chen5e3742e2017-04-07 14:28:44 -07004880 return;
4881 }
Selim Cinekd60ef9e2018-05-16 16:01:05 -07004882 boolean animate = mIsExpanded && mAnimationsEnabled;
4883 mFooterView.setVisible(visible, animate);
4884 mFooterView.setSecondaryVisible(showDismissView, animate);
Dan Sandlereceda3d2014-07-21 15:35:01 -04004885 }
4886
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004887 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Dan Sandlereceda3d2014-07-21 15:35:01 -04004888 public void setDismissAllInProgress(boolean dismissAllInProgress) {
4889 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07004890 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07004891 handleDismissAllClipping();
4892 }
4893
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004894 @ShadeViewRefactor(RefactorComponent.ADAPTER)
Selim Cinek9c17b772015-07-07 20:37:09 -07004895 private void handleDismissAllClipping() {
4896 final int count = getChildCount();
4897 boolean previousChildWillBeDismissed = false;
4898 for (int i = 0; i < count; i++) {
4899 ExpandableView child = (ExpandableView) getChildAt(i);
4900 if (child.getVisibility() == GONE) {
4901 continue;
4902 }
4903 if (mDismissAllInProgress && previousChildWillBeDismissed) {
4904 child.setMinClipTopAmount(child.getClipTopAmount());
4905 } else {
4906 child.setMinClipTopAmount(0);
4907 }
Ned Burns61269442019-05-02 18:27:23 -04004908 previousChildWillBeDismissed = StackScrollAlgorithm.canChildBeDismissed(child);
Selim Cinek9c17b772015-07-07 20:37:09 -07004909 }
Selim Cineka272dfe2015-02-20 18:12:28 +01004910 }
4911
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004912 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004913 public boolean isFooterViewNotGone() {
4914 return mFooterView != null
4915 && mFooterView.getVisibility() != View.GONE
4916 && !mFooterView.willBeGone();
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02004917 }
4918
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004919 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek38475442018-05-18 11:11:46 -07004920 public boolean isFooterViewContentVisible() {
4921 return mFooterView != null && mFooterView.isContentVisible();
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02004922 }
4923
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004924 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004925 public int getFooterViewHeight() {
4926 return mFooterView == null ? 0 : mFooterView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02004927 }
4928
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004929 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01004930 public int getEmptyShadeViewHeight() {
4931 return mEmptyShadeView.getHeight();
4932 }
4933
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004934 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Jorim Jaggie0640dd2014-08-05 23:12:40 +02004935 public float getBottomMostNotificationBottom() {
4936 final int count = getChildCount();
4937 float max = 0;
4938 for (int childIdx = 0; childIdx < count; childIdx++) {
4939 ExpandableView child = (ExpandableView) getChildAt(childIdx);
4940 if (child.getVisibility() == GONE) {
4941 continue;
4942 }
Selim Cineka686b2c2016-10-26 13:58:27 -07004943 float bottom = child.getTranslationY() + child.getActualHeight()
4944 - child.getClipBottomAmount();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02004945 if (bottom > max) {
4946 max = bottom;
4947 }
4948 }
Selim Cinekd2281152015-04-10 14:37:46 -07004949 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02004950 }
4951
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004952 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk2a6ea9c2017-01-26 11:14:51 -05004953 public void setStatusBar(StatusBar statusBar) {
4954 this.mStatusBar = statusBar;
Selim Cinek19c8c702014-08-25 22:09:19 +02004955 }
4956
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004957 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekb5605e52015-02-20 18:21:41 +01004958 public void setGroupManager(NotificationGroupManager groupManager) {
4959 this.mGroupManager = groupManager;
Kevin01a53cb2018-11-09 18:19:54 -08004960 mGroupManager.addOnGroupChangeListener(mOnGroupChangeListener);
Selim Cinek379ff8f2015-02-20 17:03:16 +01004961 }
4962
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004963 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek379ff8f2015-02-20 17:03:16 +01004964 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02004965 if (mIsExpanded && mAnimationsEnabled) {
4966 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01004967 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02004968 requestChildrenUpdate();
4969 }
4970 }
4971
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04004972 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek04fb2582015-06-02 19:58:09 +02004973 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01004974 int childCount = getChildCount();
4975 for (int i = childCount - 1; i >= 0; i--) {
4976 ExpandableView child = (ExpandableView) getChildAt(i);
4977 if (child.getVisibility() != View.GONE) {
4978 float childTop = child.getY();
4979 if (childTop > touchY) {
4980 // we are above a notification entirely let's abort
4981 return false;
4982 }
Selim Cineka686b2c2016-10-26 13:58:27 -07004983 boolean belowChild = touchY > childTop + child.getActualHeight()
4984 - child.getClipBottomAmount();
Julia Reynoldsed1c9af2018-03-21 15:21:09 -04004985 if (child == mFooterView) {
Jason Monke59dc402018-08-16 12:05:01 -04004986 if (!belowChild && !mFooterView.isOnEmptySpace(touchX - mFooterView.getX(),
4987 touchY - childTop)) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01004988 // We clicked on the dismiss button
4989 return false;
4990 }
4991 } else if (child == mEmptyShadeView) {
4992 // We arrived at the empty shade view, for which we accept all clicks
4993 return true;
Jason Monke59dc402018-08-16 12:05:01 -04004994 } else if (!belowChild) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01004995 // We are on a child
4996 return false;
4997 }
4998 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01004999 }
Selim Cinek04fb2582015-06-02 19:58:09 +02005000 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005001 }
5002
Selim Cinekc22fff62016-05-20 12:44:30 -07005003 /** @hide */
5004 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005005 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07005006 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
5007 super.onInitializeAccessibilityEventInternal(event);
5008 event.setScrollable(mScrollable);
5009 event.setScrollX(mScrollX);
Selim Cinekc22fff62016-05-20 12:44:30 -07005010 event.setMaxScrollX(mScrollX);
Gus Prevas0fa58d62019-01-11 13:58:40 -05005011 if (ANCHOR_SCROLLING) {
5012 // TODO
5013 } else {
5014 event.setScrollY(mOwnScrollY);
5015 event.setMaxScrollY(getScrollRange());
5016 }
Selim Cinekc22fff62016-05-20 12:44:30 -07005017 }
5018
5019 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005020 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekc22fff62016-05-20 12:44:30 -07005021 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
5022 super.onInitializeAccessibilityNodeInfoInternal(info);
Selim Cinekef406062016-09-29 17:33:13 -07005023 if (mScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005024 info.setScrollable(true);
Selim Cinekef406062016-09-29 17:33:13 -07005025 if (mBackwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005026 info.addAction(
5027 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
5028 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
5029 }
Selim Cinekef406062016-09-29 17:33:13 -07005030 if (mForwardScrollable) {
Selim Cinekc22fff62016-05-20 12:44:30 -07005031 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
5032 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
5033 }
5034 }
Selim Cinek41fe89a2016-06-02 15:27:56 -07005035 // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
5036 info.setClassName(ScrollView.class.getName());
Selim Cinekc22fff62016-05-20 12:44:30 -07005037 }
5038
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005039 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekb5605e52015-02-20 18:21:41 +01005040 public void generateChildOrderChangedEvent() {
5041 if (mIsExpanded && mAnimationsEnabled) {
5042 mGenerateChildOrderChangedEvent = true;
5043 mNeedsAnimation = true;
5044 requestChildrenUpdate();
5045 }
5046 }
5047
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005048 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005049 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005050 public int getContainerChildCount() {
5051 return getChildCount();
5052 }
5053
5054 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005055 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005056 public View getContainerChildAt(int i) {
5057 return getChildAt(i);
5058 }
5059
5060 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005061 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005062 public void removeContainerView(View v) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04005063 Assert.isMainThread();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005064 removeView(v);
5065 }
5066
5067 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005068 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005069 public void addContainerView(View v) {
Ned Burnsd4a69f72019-06-19 19:49:19 -04005070 Assert.isMainThread();
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005071 addView(v);
5072 }
5073
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005074 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek684a4422015-04-15 16:18:39 -07005075 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005076 mAnimationFinishedRunnables.add(runnable);
5077 }
5078
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005079 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
yoshiki iguchi4e30e762018-02-06 12:09:23 +09005080 public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005081 mHeadsUpManager = headsUpManager;
Selim Cinek29aab962018-02-27 17:05:45 -08005082 mHeadsUpManager.addListener(mRoundnessManager);
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04005083 mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005084 }
5085
Ned Burnsf81c4c42019-01-07 14:10:43 -05005086 public void generateHeadsUpAnimation(NotificationEntry entry, boolean isHeadsUp) {
Evan Laird94492852018-10-25 13:43:01 -04005087 ExpandableNotificationRow row = entry.getHeadsUpAnimationView();
5088 generateHeadsUpAnimation(row, isHeadsUp);
5089 }
5090
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005091 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005092 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
Selim Cinek5cf1d052017-06-01 17:36:46 -07005093 if (mAnimationsEnabled && (isHeadsUp || mHeadsUpGoingAwayAnimationsAllowed)) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005094 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
5095 mNeedsAnimation = true;
Selim Cinek73cf02a2016-06-17 13:08:00 -07005096 if (!mIsExpanded && !isHeadsUp) {
Selim Cinekcafa87f2016-10-26 17:00:17 -07005097 row.setHeadsUpAnimatingAway(true);
Selim Cinek73cf02a2016-06-17 13:08:00 -07005098 }
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005099 requestChildrenUpdate();
5100 }
5101 }
5102
Selim Cineka59ecc32015-04-07 10:51:49 -07005103 /**
5104 * Set the boundary for the bottom heads up position. The heads up will always be above this
5105 * position.
5106 *
Jason Monke59dc402018-08-16 12:05:01 -04005107 * @param height the height of the screen
Selim Cineka59ecc32015-04-07 10:51:49 -07005108 * @param bottomBarHeight the height of the bar on the bottom
5109 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005110 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cineka59ecc32015-04-07 10:51:49 -07005111 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
5112 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
5113 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005114 requestChildrenUpdate();
5115 }
5116
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005117 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005118 public void setTrackingHeadsUp(ExpandableNotificationRow row) {
5119 mTrackingHeadsUp = row != null;
5120 mRoundnessManager.setTrackingHeadsUp(row);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005121 }
5122
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005123 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaac93252015-04-14 20:04:12 -07005124 public void setScrimController(ScrimController scrimController) {
5125 mScrimController = scrimController;
Lucas Dupin8da8f2e92017-04-21 14:02:16 -07005126 mScrimController.setScrimBehindChangeRunnable(this::updateBackgroundDimming);
Selim Cinekaac93252015-04-14 20:04:12 -07005127 }
5128
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005129 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbbc580b2015-06-03 14:11:03 +02005130 public void forceNoOverlappingRendering(boolean force) {
5131 mForceNoOverlappingRendering = force;
5132 }
5133
5134 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005135 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbbc580b2015-06-03 14:11:03 +02005136 public boolean hasOverlappingRendering() {
5137 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
5138 }
5139
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005140 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek6811d722016-01-19 17:53:12 -08005141 public void setAnimationRunning(boolean animationRunning) {
5142 if (animationRunning != mAnimationRunning) {
5143 if (animationRunning) {
Selim Cinekc383fd02016-10-21 15:31:26 -07005144 getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08005145 } else {
Selim Cinekc383fd02016-10-21 15:31:26 -07005146 getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
Selim Cinek6811d722016-01-19 17:53:12 -08005147 }
5148 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08005149 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08005150 }
5151 }
5152
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005153 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek3776fe02016-02-04 13:32:43 -08005154 public boolean isExpanded() {
5155 return mIsExpanded;
5156 }
5157
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005158 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupin4e023812018-04-02 21:19:23 -07005159 public void setPulsing(boolean pulsing, boolean animated) {
yoshiki iguchi4e30e762018-02-06 12:09:23 +09005160 if (!mPulsing && !pulsing) {
Adrian Roosb2a87292017-02-13 15:05:03 +01005161 return;
5162 }
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005163 mPulsing = pulsing;
Selim Cinekebf42342017-07-13 15:46:10 +02005164 mAmbientState.setPulsing(pulsing);
Selim Cinekd0b48e32019-05-24 20:49:23 -07005165 mSwipeHelper.setPulsing(pulsing);
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005166 updateNotificationAnimationStates();
Lucas Dupin6bf7b642018-01-22 18:56:24 -08005167 updateAlgorithmHeightAndPadding();
Adrian Roos7a9551a2017-01-11 12:27:49 -08005168 updateContentHeight();
Adrian Roosd83e9992017-03-16 15:17:57 -07005169 requestChildrenUpdate();
Lucas Dupin4e023812018-04-02 21:19:23 -07005170 notifyHeightChangeListener(null, animated);
Selim Cinekcd5b22f2016-03-08 16:15:41 -08005171 }
5172
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005173 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekbc243a92016-09-27 16:35:13 -07005174 public void setQsExpanded(boolean qsExpanded) {
5175 mQsExpanded = qsExpanded;
5176 updateAlgorithmLayoutMinHeight();
Riddle Hsu065c01c2018-05-10 23:14:19 +08005177 updateScrollability();
Selim Cinekbc243a92016-09-27 16:35:13 -07005178 }
5179
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005180 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
shawnlin8e4e92c2018-04-12 18:47:24 +08005181 public void setQsExpansionFraction(float qsExpansionFraction) {
5182 mQsExpansionFraction = qsExpansionFraction;
5183 }
5184
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005185 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Gus Prevas0fa58d62019-01-11 13:58:40 -05005186 private void setOwnScrollY(int ownScrollY) {
5187 assert !ANCHOR_SCROLLING;
Selim Cinekef406062016-09-29 17:33:13 -07005188 if (ownScrollY != mOwnScrollY) {
Selim Cinek9212de82017-02-06 16:04:28 -08005189 // We still want to call the normal scrolled changed for accessibility reasons
5190 onScrollChanged(mScrollX, ownScrollY, mScrollX, mOwnScrollY);
Selim Cinekef406062016-09-29 17:33:13 -07005191 mOwnScrollY = ownScrollY;
Gus Prevas0fa58d62019-01-11 13:58:40 -05005192 updateOnScrollChange();
5193 }
5194 }
5195
5196 private void updateOnScrollChange() {
5197 updateForwardAndBackwardScrollability();
5198 requestChildrenUpdate();
5199 }
5200
5201 private void updateScrollAnchor() {
5202 int anchorIndex = indexOfChild(mScrollAnchorView);
5203 // If the anchor view has been scrolled off the top, move to the next view.
5204 while (mScrollAnchorViewY < 0) {
5205 View nextAnchor = null;
5206 for (int i = anchorIndex + 1; i < getChildCount(); i++) {
5207 View child = getChildAt(i);
5208 if (child.getVisibility() != View.GONE
5209 && child instanceof ExpandableNotificationRow) {
5210 anchorIndex = i;
5211 nextAnchor = child;
5212 break;
5213 }
5214 }
5215 if (nextAnchor == null) {
5216 break;
5217 }
5218 mScrollAnchorViewY +=
5219 (int) (nextAnchor.getTranslationY() - mScrollAnchorView.getTranslationY());
5220 mScrollAnchorView = nextAnchor;
5221 }
5222 // If the view above the anchor view is fully visible, make it the anchor view.
5223 while (anchorIndex > 0 && mScrollAnchorViewY > 0) {
5224 View prevAnchor = null;
5225 for (int i = anchorIndex - 1; i >= 0; i--) {
5226 View child = getChildAt(i);
5227 if (child.getVisibility() != View.GONE
5228 && child instanceof ExpandableNotificationRow) {
5229 anchorIndex = i;
5230 prevAnchor = child;
5231 break;
5232 }
5233 }
5234 if (prevAnchor == null) {
5235 break;
5236 }
5237 float distanceToPreviousAnchor =
5238 mScrollAnchorView.getTranslationY() - prevAnchor.getTranslationY();
5239 if (distanceToPreviousAnchor < mScrollAnchorViewY) {
5240 mScrollAnchorViewY -= (int) distanceToPreviousAnchor;
5241 mScrollAnchorView = prevAnchor;
5242 }
Selim Cinekef406062016-09-29 17:33:13 -07005243 }
5244 }
5245
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005246 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek281c2022016-10-13 19:14:43 -07005247 public void setShelf(NotificationShelf shelf) {
Selim Cinek281c2022016-10-13 19:14:43 -07005248 int index = -1;
5249 if (mShelf != null) {
5250 index = indexOfChild(mShelf);
5251 removeView(mShelf);
5252 }
Selim Cinek0e8d77e2016-11-29 10:35:42 -08005253 mShelf = shelf;
Selim Cinek281c2022016-10-13 19:14:43 -07005254 addView(mShelf, index);
5255 mAmbientState.setShelf(shelf);
Selim Cinekeccb5de2016-10-28 15:04:05 -07005256 mStateAnimator.setShelf(shelf);
Selim Cinekc383fd02016-10-21 15:31:26 -07005257 shelf.bind(mAmbientState, this);
Gus Prevas0fa58d62019-01-11 13:58:40 -05005258 if (ANCHOR_SCROLLING) {
5259 mScrollAnchorView = mShelf;
5260 }
Selim Cinek281c2022016-10-13 19:14:43 -07005261 }
5262
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005263 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek281c2022016-10-13 19:14:43 -07005264 public NotificationShelf getNotificationShelf() {
5265 return mShelf;
5266 }
5267
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005268 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekad7fac02016-10-18 17:09:15 -07005269 public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
5270 if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
5271 mMaxDisplayedNotifications = maxDisplayedNotifications;
5272 updateContentHeight();
5273 notifyHeightChangeListener(mShelf);
5274 }
5275 }
5276
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005277 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
shawnlin8e4e92c2018-04-12 18:47:24 +08005278 public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
Jason Monke59dc402018-08-16 12:05:01 -04005279 mShouldShowShelfOnly = shouldShowShelfOnly;
shawnlin8e4e92c2018-04-12 18:47:24 +08005280 updateAlgorithmLayoutMinHeight();
5281 }
5282
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005283 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinek48ff9b42016-11-09 19:31:51 -08005284 public int getMinExpansionHeight() {
Selim Cinekd127d792016-11-01 19:11:41 -07005285 return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
Selim Cinek48ff9b42016-11-09 19:31:51 -08005286 }
5287
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005288 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekcafa87f2016-10-26 17:00:17 -07005289 public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
5290 mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
5291 updateClipping();
5292 }
5293
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005294 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekcafa87f2016-10-26 17:00:17 -07005295 public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
5296 mHeadsUpAnimatingAway = headsUpAnimatingAway;
5297 updateClipping();
5298 }
5299
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005300 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monk297c04e2018-08-23 17:16:59 -04005301 @VisibleForTesting
5302 protected void setStatusBarState(int statusBarState) {
Selim Cinek355652a2016-12-07 13:32:12 -08005303 mStatusBarState = statusBarState;
5304 mAmbientState.setStatusBarState(statusBarState);
Evan Laird91d0f102018-09-18 17:39:55 -04005305 }
5306
5307 private void onStatePostChange() {
Jason Monk1fd3fc32018-08-14 17:20:09 -04005308 boolean onKeyguard = onKeyguard();
5309 boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode();
Evan Laird91d0f102018-09-18 17:39:55 -04005310
Jason Monk1fd3fc32018-08-14 17:20:09 -04005311 if (mHeadsUpAppearanceController != null) {
5312 mHeadsUpAppearanceController.setPublicMode(publicMode);
5313 }
5314
Beverly8fdb5332019-02-04 14:29:49 -05005315 SysuiStatusBarStateController state = (SysuiStatusBarStateController)
5316 Dependency.get(StatusBarStateController.class);
Jason Monk1fd3fc32018-08-14 17:20:09 -04005317 setHideSensitive(publicMode, state.goingToFullShade() /* animate */);
5318 setDimmed(onKeyguard, state.fromShadeLocked() /* animate */);
5319 setExpandingEnabled(!onKeyguard);
5320 ActivatableNotificationView activatedChild = getActivatedChild();
5321 setActivatedChild(null);
5322 if (activatedChild != null) {
5323 activatedChild.makeInactive(false /* animate */);
5324 }
Jason Monke59dc402018-08-16 12:05:01 -04005325 updateFooter();
Dave Mankoff57445802018-10-10 14:47:34 -04005326 requestChildrenUpdate();
Jason Monke59dc402018-08-16 12:05:01 -04005327 onUpdateRowStates();
Evan Laird91d0f102018-09-18 17:39:55 -04005328
5329 mEntryManager.updateNotifications();
Selim Cinek355652a2016-12-07 13:32:12 -08005330 }
5331
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005332 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekd5ab6452016-12-08 16:34:00 -08005333 public void setExpandingVelocity(float expandingVelocity) {
5334 mAmbientState.setExpandingVelocity(expandingVelocity);
5335 }
5336
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005337 @ShadeViewRefactor(RefactorComponent.COORDINATOR)
Selim Cinekfcff4c62016-12-27 14:26:06 +01005338 public float getOpeningHeight() {
5339 if (mEmptyShadeView.getVisibility() == GONE) {
5340 return getMinExpansionHeight();
5341 } else {
5342 return getAppearEndPosition();
5343 }
5344 }
5345
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005346 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekfcff4c62016-12-27 14:26:06 +01005347 public void setIsFullWidth(boolean isFullWidth) {
5348 mAmbientState.setPanelFullWidth(isFullWidth);
5349 }
5350
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005351 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekec29d342017-05-05 18:31:49 -07005352 public void setUnlockHintRunning(boolean running) {
5353 mAmbientState.setUnlockHintRunning(running);
5354 }
5355
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005356 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek5cf1d052017-06-01 17:36:46 -07005357 public void setQsCustomizerShowing(boolean isShowing) {
5358 mAmbientState.setQsCustomizerShowing(isShowing);
5359 requestChildrenUpdate();
5360 }
5361
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005362 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek5cf1d052017-06-01 17:36:46 -07005363 public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
5364 mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
5365 }
5366
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005367 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupin0cd882f2018-01-30 12:19:49 -08005368 public void setAntiBurnInOffsetX(int antiBurnInOffsetX) {
5369 mAntiBurnInOffsetX = antiBurnInOffsetX;
Bill Line60aa1e2018-06-13 18:07:15 +08005370 updatePanelTranslation();
Adrian Roosdc747bd2017-06-01 16:09:15 -07005371 }
5372
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005373 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek707e2072017-06-30 18:32:40 +02005374 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
5375 pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s"
shawnlin8e4e92c2018-04-12 18:47:24 +08005376 + " alpha:%f scrollY:%d maxTopPadding:%d showShelfOnly=%s"
5377 + " qsExpandFraction=%f]",
Selim Cinek707e2072017-06-30 18:32:40 +02005378 this.getClass().getSimpleName(),
Jason Monke59dc402018-08-16 12:05:01 -04005379 mPulsing ? "T" : "f",
5380 mAmbientState.isQsCustomizerShowing() ? "T" : "f",
Selim Cinek707e2072017-06-30 18:32:40 +02005381 getVisibility() == View.VISIBLE ? "visible"
5382 : getVisibility() == View.GONE ? "gone"
5383 : "invisible",
5384 getAlpha(),
shawnlin8e4e92c2018-04-12 18:47:24 +08005385 mAmbientState.getScrollY(),
5386 mMaxTopPadding,
Jason Monke59dc402018-08-16 12:05:01 -04005387 mShouldShowShelfOnly ? "T" : "f",
shawnlin8e4e92c2018-04-12 18:47:24 +08005388 mQsExpansionFraction));
Selim Cinek30887662018-10-15 17:37:21 -07005389 int childCount = getChildCount();
5390 pw.println(" Number of children: " + childCount);
5391 pw.println();
5392
5393 for (int i = 0; i < childCount; i++) {
5394 ExpandableView child = (ExpandableView) getChildAt(i);
5395 child.dump(fd, pw, args);
5396 if (!(child instanceof ExpandableNotificationRow)) {
5397 pw.println(" " + child.getClass().getSimpleName());
5398 // Notifications dump it's viewstate as part of their dump to support children
Dave Mankoffa4d195d2018-11-16 13:33:27 -05005399 ExpandableViewState viewState = child.getViewState();
Selim Cinek30887662018-10-15 17:37:21 -07005400 if (viewState == null) {
5401 pw.println(" no viewState!!!");
5402 } else {
5403 pw.print(" ");
5404 viewState.dump(fd, pw, args);
5405 pw.println();
5406 pw.println();
5407 }
5408 }
5409 }
Selim Cinek30887662018-10-15 17:37:21 -07005410 int transientViewCount = getTransientViewCount();
Selim Cinekd4c32302018-11-19 19:43:14 -08005411 pw.println(" Transient Views: " + transientViewCount);
Selim Cinek30887662018-10-15 17:37:21 -07005412 for (int i = 0; i < transientViewCount; i++) {
5413 ExpandableView child = (ExpandableView) getTransientView(i);
5414 child.dump(fd, pw, args);
5415 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -05005416 ArrayList<ExpandableView> draggedViews = mAmbientState.getDraggedViews();
Selim Cinekd4c32302018-11-19 19:43:14 -08005417 int draggedCount = draggedViews.size();
5418 pw.println(" Dragged Views: " + draggedCount);
5419 for (int i = 0; i < draggedCount; i++) {
5420 ExpandableView child = (ExpandableView) draggedViews.get(i);
5421 child.dump(fd, pw, args);
5422 }
Selim Cinek707e2072017-06-30 18:32:40 +02005423 }
5424
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005425 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Lucas Dupin16cfe452018-02-08 13:14:50 -08005426 public boolean isFullyDark() {
5427 return mAmbientState.isFullyDark();
5428 }
5429
Selim Cinek7103fd42016-05-09 22:22:33 -04005430 /**
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005431 * Add a listener whenever the expanded height changes. The first value passed as an argument
5432 * is the expanded height and the second one is the appearFraction.
5433 *
5434 * @param listener the listener to notify.
5435 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005436 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekaa9db1f2018-02-27 17:35:47 -08005437 public void addOnExpandedHeightListener(BiConsumer<Float, Float> listener) {
5438 mExpandedHeightListeners.add(listener);
5439 }
5440
5441 /**
Selim Cinek60ffea62018-03-22 13:16:44 -07005442 * Stop a listener from listening to the expandedHeight.
5443 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005444 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek60ffea62018-03-22 13:16:44 -07005445 public void removeOnExpandedHeightListener(BiConsumer<Float, Float> listener) {
5446 mExpandedHeightListeners.remove(listener);
5447 }
5448
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005449 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinekf0c79e12018-05-14 17:17:31 -07005450 public void setHeadsUpAppearanceController(
5451 HeadsUpAppearanceController headsUpAppearanceController) {
5452 mHeadsUpAppearanceController = headsUpAppearanceController;
5453 }
5454
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005455 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek9bfc7a52018-06-11 16:09:00 -07005456 public void setIconAreaController(NotificationIconAreaController controller) {
5457 mIconAreaController = controller;
5458 }
5459
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005460 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -04005461 public void manageNotifications(View v) {
5462 Intent intent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
5463 mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
5464 }
5465
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005466 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -04005467 private void clearNotifications(
5468 @SelectedRows int selection,
5469 boolean closeShade) {
Jason Monke59dc402018-08-16 12:05:01 -04005470 // animate-swipe all dismissable notifications, then animate the shade closed
5471 int numChildren = getChildCount();
5472
5473 final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
5474 final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
5475 for (int i = 0; i < numChildren; i++) {
5476 final View child = getChildAt(i);
5477 if (child instanceof ExpandableNotificationRow) {
5478 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
5479 boolean parentVisible = false;
5480 boolean hasClipBounds = child.getClipBounds(mTmpRect);
Ned Burns61269442019-05-02 18:27:23 -04005481 if (includeChildInDismissAll(row, selection)) {
Jason Monke59dc402018-08-16 12:05:01 -04005482 viewsToRemove.add(row);
5483 if (child.getVisibility() == View.VISIBLE
5484 && (!hasClipBounds || mTmpRect.height() > 0)) {
5485 viewsToHide.add(child);
5486 parentVisible = true;
5487 }
5488 } else if (child.getVisibility() == View.VISIBLE
5489 && (!hasClipBounds || mTmpRect.height() > 0)) {
5490 parentVisible = true;
5491 }
5492 List<ExpandableNotificationRow> children = row.getNotificationChildren();
5493 if (children != null) {
5494 for (ExpandableNotificationRow childRow : children) {
Ned Burns61269442019-05-02 18:27:23 -04005495 if (includeChildInDismissAll(row, selection)) {
5496 viewsToRemove.add(childRow);
5497 if (parentVisible && row.areChildrenExpanded()) {
5498 hasClipBounds = childRow.getClipBounds(mTmpRect);
5499 if (childRow.getVisibility() == View.VISIBLE
5500 && (!hasClipBounds || mTmpRect.height() > 0)) {
5501 viewsToHide.add(childRow);
5502 }
Jason Monke59dc402018-08-16 12:05:01 -04005503 }
5504 }
5505 }
5506 }
5507 }
5508 }
Ned Burns61269442019-05-02 18:27:23 -04005509
Jason Monke59dc402018-08-16 12:05:01 -04005510 if (viewsToRemove.isEmpty()) {
Ned Burns61269442019-05-02 18:27:23 -04005511 if (closeShade) {
5512 mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
5513 }
Jason Monke59dc402018-08-16 12:05:01 -04005514 return;
5515 }
5516
Ned Burns61269442019-05-02 18:27:23 -04005517 performDismissAllAnimations(viewsToHide, closeShade, () -> {
Jason Monke59dc402018-08-16 12:05:01 -04005518 for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
Ned Burns61269442019-05-02 18:27:23 -04005519 if (StackScrollAlgorithm.canChildBeDismissed(rowToRemove)) {
5520 if (selection == ROWS_ALL) {
5521 // TODO: This is a listener method; we shouldn't be calling it. Can we just
5522 // call performRemoveNotification as below?
5523 mEntryManager.removeNotification(
5524 rowToRemove.getEntry().key,
5525 null /* ranking */,
5526 NotificationListenerService.REASON_CANCEL_ALL);
5527 } else {
5528 mEntryManager.performRemoveNotification(
5529 rowToRemove.getEntry().notification,
5530 NotificationListenerService.REASON_CANCEL_ALL);
5531 }
Jason Monke59dc402018-08-16 12:05:01 -04005532 } else {
5533 rowToRemove.resetTranslation();
5534 }
5535 }
Ned Burns61269442019-05-02 18:27:23 -04005536 if (selection == ROWS_ALL) {
5537 try {
5538 mBarService.onClearAllNotifications(mLockscreenUserManager.getCurrentUserId());
5539 } catch (Exception ex) {
5540 }
Jason Monke59dc402018-08-16 12:05:01 -04005541 }
5542 });
Jason Monke59dc402018-08-16 12:05:01 -04005543 }
5544
Ned Burns61269442019-05-02 18:27:23 -04005545 private boolean includeChildInDismissAll(
5546 ExpandableNotificationRow row,
5547 @SelectedRows int selection) {
5548 return StackScrollAlgorithm.canChildBeDismissed(row) && matchesSelection(row, selection);
5549 }
5550
5551 /**
5552 * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
5553 * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
5554 * handler.
5555 *
5556 * @param hideAnimatedList List of rows to animated away. Should only be views that are
5557 * currently visible, or else the stagger will look funky.
5558 * @param closeShade Whether to close the shade after the stagger animation completes.
5559 * @param onAnimationComplete Called after the entire animation completes (including the shade
5560 * closing if appropriate). The rows must be dismissed for real here.
5561 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005562 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Ned Burns61269442019-05-02 18:27:23 -04005563 private void performDismissAllAnimations(
5564 final ArrayList<View> hideAnimatedList,
5565 final boolean closeShade,
5566 final Runnable onAnimationComplete) {
5567
5568 final Runnable onSlideAwayAnimationComplete = () -> {
5569 if (closeShade) {
5570 mShadeController.addPostCollapseAction(() -> {
5571 setDismissAllInProgress(false);
5572 onAnimationComplete.run();
5573 });
5574 mStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
5575 } else {
5576 setDismissAllInProgress(false);
5577 onAnimationComplete.run();
5578 }
Jason Monke59dc402018-08-16 12:05:01 -04005579 };
5580
5581 if (hideAnimatedList.isEmpty()) {
Ned Burns61269442019-05-02 18:27:23 -04005582 onSlideAwayAnimationComplete.run();
Jason Monke59dc402018-08-16 12:05:01 -04005583 return;
5584 }
5585
5586 // let's disable our normal animations
5587 setDismissAllInProgress(true);
5588
5589 // Decrease the delay for every row we animate to give the sense of
5590 // accelerating the swipes
5591 int rowDelayDecrement = 10;
5592 int currentDelay = 140;
5593 int totalDelay = 180;
5594 int numItems = hideAnimatedList.size();
5595 for (int i = numItems - 1; i >= 0; i--) {
5596 View view = hideAnimatedList.get(i);
5597 Runnable endRunnable = null;
5598 if (i == 0) {
Ned Burns61269442019-05-02 18:27:23 -04005599 endRunnable = onSlideAwayAnimationComplete;
Jason Monke59dc402018-08-16 12:05:01 -04005600 }
Lucas Dupinfb8bdbb2018-12-02 15:09:37 -08005601 dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
Jason Monke59dc402018-08-16 12:05:01 -04005602 currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
5603 totalDelay += currentDelay;
5604 }
5605 }
5606
5607 @VisibleForTesting
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005608 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jason Monke59dc402018-08-16 12:05:01 -04005609 protected void inflateFooterView() {
5610 FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
5611 R.layout.status_bar_notification_footer, this, false);
5612 footerView.setDismissButtonClickListener(v -> {
5613 mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
Ned Burns61269442019-05-02 18:27:23 -04005614 clearNotifications(ROWS_ALL, true /* closeShade */);
Jason Monke59dc402018-08-16 12:05:01 -04005615 });
5616 footerView.setManageButtonClickListener(this::manageNotifications);
5617 setFooterView(footerView);
5618 }
5619
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005620 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5621 private void inflateEmptyShadeView() {
Jason Monke59dc402018-08-16 12:05:01 -04005622 EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
5623 R.layout.status_bar_no_notifications, this, false);
5624 view.setText(R.string.empty_shade_text);
5625 setEmptyShadeView(view);
5626 }
5627
5628 /**
5629 * Updates expanded, dimmed and locked states of notification rows.
5630 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005631 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Jason Monke59dc402018-08-16 12:05:01 -04005632 public void onUpdateRowStates() {
5633 changeViewPosition(mFooterView, -1);
5634
5635 // The following views will be moved to the end of mStackScroller. This counter represents
5636 // the offset from the last child. Initialized to 1 for the very last position. It is post-
5637 // incremented in the following "changeViewPosition" calls so that its value is correct for
5638 // subsequent calls.
5639 int offsetFromEnd = 1;
5640 changeViewPosition(mEmptyShadeView,
5641 getChildCount() - offsetFromEnd++);
5642
5643 // No post-increment for this call because it is the last one. Make sure to add one if
5644 // another "changeViewPosition" call is ever added.
5645 changeViewPosition(mShelf,
5646 getChildCount() - offsetFromEnd);
Jason Monke59dc402018-08-16 12:05:01 -04005647 }
5648
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005649 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5650 public void setNotificationPanel(NotificationPanelView notificationPanelView) {
Jason Monke59dc402018-08-16 12:05:01 -04005651 mNotificationPanel = notificationPanelView;
5652 }
5653
Jason Monk297c04e2018-08-23 17:16:59 -04005654 public void updateIconAreaViews() {
5655 mIconAreaController.updateNotificationIcons();
5656 }
5657
Selim Cinek60ffea62018-03-22 13:16:44 -07005658 /**
Selim Cinek3d6ae232019-01-04 14:14:33 -08005659 * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
5660 * notification positions accordingly.
5661 * @param height the new wake up height
5662 * @return the overflow how much the height is further than he lowest notification
5663 */
Selim Cinek5040f2e2019-02-14 18:22:42 -08005664 public float setPulseHeight(float height) {
5665 mAmbientState.setPulseHeight(height);
Selim Cinek3d6ae232019-01-04 14:14:33 -08005666 requestChildrenUpdate();
5667 return Math.max(0, height - mAmbientState.getInnerHeight(true /* ignorePulseHeight */));
5668 }
5669
5670 /**
5671 * Set the amount how much we're dozing. This is different from how dark the shade is, when
5672 * the notification is pulsing.
5673 */
5674 public void setDozeAmount(float dozeAmount) {
5675 mAmbientState.setDozeAmount(dozeAmount);
Selim Cinekae55d832019-02-22 17:43:43 -08005676 updateContinuousBackgroundDrawing();
Selim Cinek3d6ae232019-01-04 14:14:33 -08005677 requestChildrenUpdate();
5678 }
5679
Selim Cinek459aee32019-02-20 11:18:56 -08005680 public void wakeUpFromPulse() {
Selim Cinek34518f62019-02-28 19:41:18 -08005681 setPulseHeight(getPulseHeight());
Selim Cinek459aee32019-02-20 11:18:56 -08005682 // Let's place the hidden views at the end of the pulsing notification to make sure we have
5683 // a smooth animation
5684 boolean firstVisibleView = true;
5685 float wakeUplocation = -1f;
5686 int childCount = getChildCount();
5687 for (int i = 0; i < childCount; i++) {
5688 ExpandableView view = (ExpandableView) getChildAt(i);
5689 if (view.getVisibility() == View.GONE) {
5690 continue;
5691 }
5692 boolean isShelf = view == mShelf;
5693 if (!(view instanceof ExpandableNotificationRow) && !isShelf) {
5694 continue;
5695 }
5696 if (view.getVisibility() == View.VISIBLE && !isShelf) {
5697 if (firstVisibleView) {
5698 firstVisibleView = false;
5699 wakeUplocation = view.getTranslationY()
5700 + view.getActualHeight() - mShelf.getIntrinsicHeight();
5701 }
5702 } else if (!firstVisibleView) {
5703 view.setTranslationY(wakeUplocation);
5704 }
5705 }
Selim Cinekf434a742019-05-28 17:39:49 -07005706 mDimmedNeedsAnimation = true;
Selim Cinek459aee32019-02-20 11:18:56 -08005707 }
5708
Selim Cinek6f0a62a2019-04-09 18:40:12 -07005709 @Override
5710 public void onDynamicPrivacyChanged() {
5711 if (mIsExpanded) {
5712 // The bottom might change because we're using the final actual height of the view
5713 mAnimateBottomOnLayout = true;
5714 }
5715 }
5716
Selim Cinek3d6ae232019-01-04 14:14:33 -08005717 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005718 * A listener that is notified when the empty space below the notifications is clicked on
5719 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005720 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005721 public interface OnEmptySpaceClickListener {
Anthony Chen3cb3ad92016-12-01 10:58:47 -08005722 void onEmptySpaceClicked(float x, float y);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01005723 }
5724
5725 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02005726 * A listener that gets notified when the overscroll at the top has changed.
5727 */
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005728 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Jorim Jaggi290600a2014-05-30 17:02:20 +02005729 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02005730
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005731 /**
5732 * Notifies a listener that the overscroll has changed.
5733 *
5734 * @param amount the amount of overscroll, in pixels
5735 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
5736 * unrubberbanded motion to directly expand overscroll view (e.g
5737 * expand
5738 * QS)
5739 */
5740 void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02005741
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005742 /**
5743 * Notify a listener that the scroller wants to escape from the scrolling motion and
5744 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
5745 *
5746 * @param velocity The velocity that the Scroller had when over flinging
5747 * @param open Should the fling open or close the overscroll view.
5748 */
5749 void flingTopOverscroll(float velocity, boolean open);
5750 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02005751
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005752 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
5753 public boolean hasActiveNotifications() {
Jason Monke59dc402018-08-16 12:05:01 -04005754 return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty();
5755 }
5756
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04005757 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005758 public void updateSpeedBumpIndex() {
Jason Monke59dc402018-08-16 12:05:01 -04005759 int speedBumpIndex = 0;
5760 int currentIndex = 0;
5761 final int N = getChildCount();
5762 for (int i = 0; i < N; i++) {
5763 View view = getChildAt(i);
5764 if (view.getVisibility() == View.GONE || !(view instanceof ExpandableNotificationRow)) {
5765 continue;
5766 }
5767 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
5768 currentIndex++;
Gus Prevas33619af2018-10-26 15:40:27 -04005769 boolean beforeSpeedBump;
Selim Cinek3c9b0542019-06-11 15:41:28 -07005770 if (mHighPriorityBeforeSpeedBump) {
Gus Prevascaed15c2019-01-18 14:19:51 -05005771 beforeSpeedBump = row.getEntry().isHighPriority();
Selim Cinek3c9b0542019-06-11 15:41:28 -07005772 } else {
5773 beforeSpeedBump = !row.getEntry().ambient;
Gus Prevas33619af2018-10-26 15:40:27 -04005774 }
5775 if (beforeSpeedBump) {
Jason Monke59dc402018-08-16 12:05:01 -04005776 speedBumpIndex = currentIndex;
5777 }
5778 }
5779 boolean noAmbient = speedBumpIndex == N;
5780 updateSpeedBumpIndex(speedBumpIndex, noAmbient);
5781 }
5782
Gus Prevase2d6f042018-10-17 15:25:30 -04005783 /** Updates the indices of the boundaries between sections. */
5784 @ShadeViewRefactor(RefactorComponent.INPUT)
5785 public void updateSectionBoundaries() {
Ned Burns9eb06332019-04-23 16:02:12 -04005786 mSectionsManager.updateSectionBoundaries();
Gus Prevase2d6f042018-10-17 15:25:30 -04005787 }
5788
Selim Cinekae55d832019-02-22 17:43:43 -08005789 private void updateContinuousBackgroundDrawing() {
5790 boolean continuousBackground = !mAmbientState.isFullyAwake()
5791 && !mAmbientState.getDraggedViews().isEmpty();
5792 if (continuousBackground != mContinuousBackgroundUpdate) {
5793 mContinuousBackgroundUpdate = continuousBackground;
5794 if (continuousBackground) {
5795 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
5796 } else {
5797 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
5798 }
5799 }
5800 }
5801
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005802 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
Selim Cinek33223572016-02-19 19:32:22 -08005803 private void updateContinuousShadowDrawing() {
5804 boolean continuousShadowUpdate = mAnimationRunning
5805 || !mAmbientState.getDraggedViews().isEmpty();
5806 if (continuousShadowUpdate != mContinuousShadowUpdate) {
5807 if (continuousShadowUpdate) {
5808 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
5809 } else {
5810 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
5811 }
Jorim Jaggi38b5ec92016-04-12 01:39:49 -07005812 mContinuousShadowUpdate = continuousShadowUpdate;
Selim Cinek33223572016-02-19 19:32:22 -08005813 }
5814 }
5815
Eliot Courtney2b4c3a02017-11-27 13:27:46 +09005816 @Override
Aaron Heuckroth441b7dd2018-08-24 15:52:49 -04005817 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
Mady Mellor95d743c2017-01-10 12:05:27 -08005818 public void resetExposedMenuView(boolean animate, boolean force) {
5819 mSwipeHelper.resetExposedMenuView(animate, force);
Mady Mellor7a9b2a62016-03-23 07:41:47 -07005820 }
5821
Ned Burns61269442019-05-02 18:27:23 -04005822 private static boolean matchesSelection(
5823 ExpandableNotificationRow row,
5824 @SelectedRows int selection) {
5825 switch (selection) {
5826 case ROWS_ALL:
5827 return true;
5828 case ROWS_HIGH_PRIORITY:
5829 return row.getEntry().isHighPriority();
5830 case ROWS_GENTLE:
5831 return !row.getEntry().isHighPriority();
5832 default:
5833 throw new IllegalArgumentException("Unknown selection: " + selection);
5834 }
5835 }
5836
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04005837 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
5838 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02005839
Jason Monke59dc402018-08-16 12:05:01 -04005840 static AnimationFilter[] FILTERS = new AnimationFilter[]{
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005841
5842 // ANIMATION_TYPE_ADD
5843 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005844 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005845 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005846 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005847 .animateZ()
5848 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005849
5850 // ANIMATION_TYPE_REMOVE
5851 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005852 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005853 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005854 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005855 .animateZ()
5856 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005857
5858 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
5859 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005860 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005861 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005862 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005863 .animateZ()
5864 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005865
5866 // ANIMATION_TYPE_TOP_PADDING_CHANGED
5867 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005868 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005869 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005870 .animateY()
5871 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005872 .animateZ(),
5873
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005874 // ANIMATION_TYPE_ACTIVATED_CHILD
5875 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08005876 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005877
5878 // ANIMATION_TYPE_DIMMED
5879 new AnimationFilter()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005880 .animateDimmed(),
5881
5882 // ANIMATION_TYPE_CHANGE_POSITION
5883 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08005884 .animateAlpha() // maybe the children change positions
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005885 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02005886 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005887 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04005888 .animateZ(),
5889
5890 // ANIMATION_TYPE_DARK
Adrian Roos28f90c72017-05-08 17:24:26 -07005891 null, // Unused
Jorim Jaggi60d07c52014-07-31 15:38:21 +02005892
5893 // ANIMATION_TYPE_GO_TO_FULL_SHADE
5894 new AnimationFilter()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02005895 .animateHeight()
5896 .animateTopInset()
5897 .animateY()
5898 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02005899 .animateZ()
5900 .hasDelays(),
5901
5902 // ANIMATION_TYPE_HIDE_SENSITIVE
5903 new AnimationFilter()
5904 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02005905
5906 // ANIMATION_TYPE_VIEW_RESIZE
5907 new AnimationFilter()
Selim Cineka5e211b2014-08-11 17:35:48 +02005908 .animateHeight()
5909 .animateTopInset()
5910 .animateY()
5911 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02005912
Selim Cinekb5605e52015-02-20 18:21:41 +01005913 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
5914 new AnimationFilter()
5915 .animateAlpha()
5916 .animateHeight()
5917 .animateTopInset()
5918 .animateY()
5919 .animateZ(),
5920
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005921 // ANIMATION_TYPE_HEADS_UP_APPEAR
5922 new AnimationFilter()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005923 .animateHeight()
5924 .animateTopInset()
5925 .animateY()
5926 .animateZ(),
5927
5928 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
5929 new AnimationFilter()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005930 .animateHeight()
5931 .animateTopInset()
5932 .animateY()
Selim Cinek332c23f2018-03-16 17:37:50 -07005933 .animateZ()
5934 .hasDelays(),
Selim Cinekb8f09cf2015-03-16 17:09:28 -07005935
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07005936 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
5937 new AnimationFilter()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07005938 .animateHeight()
5939 .animateTopInset()
5940 .animateY()
5941 .animateZ()
5942 .hasDelays(),
5943
Selim Cineka59ecc32015-04-07 10:51:49 -07005944 // ANIMATION_TYPE_HEADS_UP_OTHER
5945 new AnimationFilter()
Selim Cineka59ecc32015-04-07 10:51:49 -07005946 .animateHeight()
5947 .animateTopInset()
5948 .animateY()
5949 .animateZ(),
5950
Selim Cinekd9acca52014-09-01 22:33:25 +02005951 // ANIMATION_TYPE_EVERYTHING
5952 new AnimationFilter()
5953 .animateAlpha()
5954 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02005955 .animateDimmed()
5956 .animateHideSensitive()
5957 .animateHeight()
5958 .animateTopInset()
5959 .animateY()
5960 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02005961 };
5962
Jason Monke59dc402018-08-16 12:05:01 -04005963 static int[] LENGTHS = new int[]{
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005964
5965 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005966 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005967
5968 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005969 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005970
5971 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
5972 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5973
5974 // ANIMATION_TYPE_TOP_PADDING_CHANGED
5975 StackStateAnimator.ANIMATION_DURATION_STANDARD,
5976
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02005977 // ANIMATION_TYPE_ACTIVATED_CHILD
5978 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
5979
5980 // ANIMATION_TYPE_DIMMED
5981 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02005982
5983 // ANIMATION_TYPE_CHANGE_POSITION
5984 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04005985
5986 // ANIMATION_TYPE_DARK
Adrian Roos28f90c72017-05-08 17:24:26 -07005987 StackStateAnimator.ANIMATION_DURATION_WAKEUP,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02005988
5989 // ANIMATION_TYPE_GO_TO_FULL_SHADE
5990 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02005991
5992 // ANIMATION_TYPE_HIDE_SENSITIVE
5993 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02005994
5995 // ANIMATION_TYPE_VIEW_RESIZE
5996 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02005997
Selim Cinekb5605e52015-02-20 18:21:41 +01005998 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08005999 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01006000
Selim Cinekb8f09cf2015-03-16 17:09:28 -07006001 // ANIMATION_TYPE_HEADS_UP_APPEAR
6002 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
6003
6004 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
6005 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6006
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07006007 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
6008 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
6009
Selim Cineka59ecc32015-04-07 10:51:49 -07006010 // ANIMATION_TYPE_HEADS_UP_OTHER
6011 StackStateAnimator.ANIMATION_DURATION_STANDARD,
6012
Selim Cinekd9acca52014-09-01 22:33:25 +02006013 // ANIMATION_TYPE_EVERYTHING
6014 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006015 };
6016
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006017 static final int ANIMATION_TYPE_ADD = 0;
6018 static final int ANIMATION_TYPE_REMOVE = 1;
6019 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
6020 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
Selim Cinekff2ffec2018-11-19 18:52:01 -08006021 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 4;
6022 static final int ANIMATION_TYPE_DIMMED = 5;
6023 static final int ANIMATION_TYPE_CHANGE_POSITION = 6;
6024 static final int ANIMATION_TYPE_DARK = 7;
6025 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 8;
6026 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 9;
6027 static final int ANIMATION_TYPE_VIEW_RESIZE = 10;
6028 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 11;
6029 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 12;
6030 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 13;
6031 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 14;
6032 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 15;
6033 static final int ANIMATION_TYPE_EVERYTHING = 16;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02006034
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01006035 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
6036 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
6037
Selim Cinek572bbd42014-04-25 16:43:27 +02006038 final long eventStartTime;
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006039 final ExpandableView mChangingView;
Selim Cinek572bbd42014-04-25 16:43:27 +02006040 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02006041 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006042 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02006043 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01006044 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07006045 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02006046
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006047 AnimationEvent(ExpandableView view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02006048 this(view, type, LENGTHS[type]);
6049 }
6050
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006051 AnimationEvent(ExpandableView view, int type, AnimationFilter filter) {
Adrian Roos28f90c72017-05-08 17:24:26 -07006052 this(view, type, LENGTHS[type], filter);
6053 }
6054
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006055 AnimationEvent(ExpandableView view, int type, long length) {
Adrian Roos28f90c72017-05-08 17:24:26 -07006056 this(view, type, length, FILTERS[type]);
6057 }
6058
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006059 AnimationEvent(ExpandableView view, int type, long length, AnimationFilter filter) {
Selim Cinek572bbd42014-04-25 16:43:27 +02006060 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006061 mChangingView = view;
Selim Cinek572bbd42014-04-25 16:43:27 +02006062 animationType = type;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02006063 this.length = length;
Adrian Roos28f90c72017-05-08 17:24:26 -07006064 this.filter = filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006065 }
6066
6067 /**
6068 * Combines the length of several animation events into a single value.
6069 *
6070 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006071 * @return The combined length. Depending on the event types, this might be the maximum of
Jason Monke59dc402018-08-16 12:05:01 -04006072 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006073 */
6074 static long combineLength(ArrayList<AnimationEvent> events) {
6075 long length = 0;
6076 int size = events.size();
6077 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02006078 AnimationEvent event = events.get(i);
6079 length = Math.max(length, event.length);
6080 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
6081 return event.length;
6082 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02006083 }
6084 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02006085 }
6086 }
Jason Monke59dc402018-08-16 12:05:01 -04006087
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006088 @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
6089 private final StateListener mStateListener = new StateListener() {
Jason Monke59dc402018-08-16 12:05:01 -04006090 @Override
6091 public void onStatePreChange(int oldState, int newState) {
6092 if (oldState == StatusBarState.SHADE_LOCKED && newState == StatusBarState.KEYGUARD) {
6093 requestAnimateEverything();
6094 }
6095 }
6096
6097 @Override
6098 public void onStateChanged(int newState) {
6099 setStatusBarState(newState);
6100 }
Evan Laird91d0f102018-09-18 17:39:55 -04006101
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006102 @Override
6103 public void onStatePostChange() {
Evan Laird91d0f102018-09-18 17:39:55 -04006104 NotificationStackScrollLayout.this.onStatePostChange();
6105 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006106 };
6107
Will Brockmane718d582019-01-17 16:38:38 -05006108 @VisibleForTesting
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006109 @ShadeViewRefactor(RefactorComponent.INPUT)
Will Brockmane718d582019-01-17 16:38:38 -05006110 protected final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006111 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006112 public void onMenuClicked(View view, int x, int y, MenuItem item) {
6113 if (mLongPressListener == null) {
6114 return;
6115 }
6116 if (view instanceof ExpandableNotificationRow) {
6117 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Will Brockmane718d582019-01-17 16:38:38 -05006118 mMetricsLogger.write(row.getStatusBarNotification().getLogMaker()
6119 .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
6120 .setType(MetricsEvent.TYPE_ACTION)
6121 );
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006122 }
6123 mLongPressListener.onLongPress(view, x, y, item);
6124 }
6125
6126 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006127 public void onMenuReset(View row) {
6128 View translatingParentView = mSwipeHelper.getTranslatingParentView();
6129 if (translatingParentView != null && row == translatingParentView) {
6130 mSwipeHelper.clearExposedMenuView();
6131 mSwipeHelper.clearTranslatingParentView();
Gus Prevas211181532018-12-13 14:49:33 -05006132 if (row instanceof ExpandableNotificationRow) {
6133 mHeadsUpManager.setMenuShown(
6134 ((ExpandableNotificationRow) row).getEntry(), false);
6135
6136 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006137 }
6138 }
6139
6140 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006141 public void onMenuShown(View row) {
6142 if (row instanceof ExpandableNotificationRow) {
Gus Prevas211181532018-12-13 14:49:33 -05006143 ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
Will Brockmane718d582019-01-17 16:38:38 -05006144 mMetricsLogger.write(notificationRow.getStatusBarNotification().getLogMaker()
6145 .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
6146 .setType(MetricsEvent.TYPE_ACTION));
Gus Prevas211181532018-12-13 14:49:33 -05006147 mHeadsUpManager.setMenuShown(notificationRow.getEntry(), true);
Evan Lairde55c6012019-03-13 12:54:37 -04006148 mSwipeHelper.onMenuShown(row);
Steve Elliottff6c25e2019-05-30 17:38:52 -04006149 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6150 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6151 false /* resetMenu */);
Evan Lairde55c6012019-03-13 12:54:37 -04006152
6153 // Check to see if we want to go directly to the notfication guts
6154 NotificationMenuRowPlugin provider = notificationRow.getProvider();
6155 if (provider.shouldShowGutsOnSnapOpen()) {
6156 MenuItem item = provider.menuItemToExposeOnSnap();
6157 if (item != null) {
6158 Point origin = provider.getRevealAnimationOrigin();
Steve Elliottff6c25e2019-05-30 17:38:52 -04006159 mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
Evan Lairde55c6012019-03-13 12:54:37 -04006160 } else {
6161 Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
6162 + "menu item in menuItemtoExposeOnSnap. Skipping.");
6163 }
6164
6165 // Close the menu row since we went directly to the guts
6166 resetExposedMenuView(false, true);
6167 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006168 }
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006169 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006170 };
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006171
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006172 @ShadeViewRefactor(RefactorComponent.INPUT)
6173 private final NotificationSwipeHelper.NotificationCallback mNotificationCallback =
6174 new NotificationSwipeHelper.NotificationCallback() {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006175 @Override
6176 public void onDismiss() {
6177 mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */,
6178 false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
6179 false /* resetMenu */);
6180 }
6181
6182 @Override
6183 public void onSnooze(StatusBarNotification sbn,
6184 NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
6185 mStatusBar.setNotificationSnoozed(sbn, snoozeOption);
6186 }
6187
6188 @Override
Selim Cinekae55d832019-02-22 17:43:43 -08006189 public boolean shouldDismissQuickly() {
6190 return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006191 }
6192
6193 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006194 public void onDragCancelled(View v) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006195 setSwipingInProgress(false);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006196 mFalsingManager.onNotificatonStopDismissing();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006197 }
6198
6199 /**
6200 * Handles cleanup after the given {@code view} has been fully swiped out (including
6201 * re-invoking dismiss logic in case the notification has not made its way out yet).
6202 */
6203 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006204 public void onChildDismissed(View view) {
6205 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6206 if (!row.isDismissed()) {
6207 handleChildViewDismissed(view);
6208 }
6209 ViewGroup transientContainer = row.getTransientContainer();
6210 if (transientContainer != null) {
6211 transientContainer.removeTransientView(view);
6212 }
6213 }
6214
6215 /**
6216 * Starts up notification dismiss and tells the notification, if any, to remove itself from
6217 * layout.
6218 *
6219 * @param view view (e.g. notification) to dismiss from the layout
6220 */
6221
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006222 public void handleChildViewDismissed(View view) {
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006223 setSwipingInProgress(false);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006224 if (mDismissAllInProgress) {
6225 return;
6226 }
6227
6228 boolean isBlockingHelperShown = false;
6229
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006230 mAmbientState.onDragFinished(view);
6231 updateContinuousShadowDrawing();
6232
6233 if (view instanceof ExpandableNotificationRow) {
6234 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
6235 if (row.isHeadsUp()) {
6236 mHeadsUpManager.addSwipedOutNotification(
6237 row.getStatusBarNotification().getKey());
6238 }
6239 isBlockingHelperShown =
6240 row.performDismissWithBlockingHelper(false /* fromAccessibility */);
6241 }
6242
6243 if (!isBlockingHelperShown) {
6244 mSwipedOutViews.add(view);
6245 }
6246 mFalsingManager.onNotificationDismissed();
6247 if (mFalsingManager.shouldEnforceBouncer()) {
6248 mStatusBar.executeRunnableDismissingKeyguard(
6249 null,
6250 null /* cancelAction */,
6251 false /* dismissShade */,
6252 true /* afterKeyguardGone */,
6253 false /* deferred */);
6254 }
6255 }
6256
6257 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006258 public boolean isAntiFalsingNeeded() {
6259 return onKeyguard();
6260 }
6261
6262 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006263 public View getChildAtPosition(MotionEvent ev) {
6264 View child = NotificationStackScrollLayout.this.getChildAtPosition(ev.getX(),
6265 ev.getY());
6266 if (child instanceof ExpandableNotificationRow) {
6267 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
6268 ExpandableNotificationRow parent = row.getNotificationParent();
6269 if (parent != null && parent.areChildrenExpanded()
6270 && (parent.areGutsExposed()
6271 || mSwipeHelper.getExposedMenuView() == parent
6272 || (parent.getNotificationChildren().size() == 1
Evan Laird94492852018-10-25 13:43:01 -04006273 && parent.getEntry().isClearable()))) {
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006274 // In this case the group is expanded and showing the menu for the
6275 // group, further interaction should apply to the group, not any
6276 // child notifications so we use the parent of the child. We also do the same
6277 // if we only have a single child.
6278 child = parent;
6279 }
6280 }
6281 return child;
6282 }
6283
6284 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006285 public void onBeginDrag(View v) {
6286 mFalsingManager.onNotificatonStartDismissing();
Aaron Heuckroth9dc9d4f2018-11-15 11:04:01 -05006287 setSwipingInProgress(true);
Dave Mankoffa4d195d2018-11-16 13:33:27 -05006288 mAmbientState.onBeginDrag((ExpandableView) v);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006289 updateContinuousShadowDrawing();
Selim Cinekae55d832019-02-22 17:43:43 -08006290 updateContinuousBackgroundDrawing();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006291 requestChildrenUpdate();
6292 }
6293
6294 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006295 public void onChildSnappedBack(View animView, float targetLeft) {
6296 mAmbientState.onDragFinished(animView);
6297 updateContinuousShadowDrawing();
Selim Cinekae55d832019-02-22 17:43:43 -08006298 updateContinuousBackgroundDrawing();
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006299 }
6300
6301 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006302 public boolean updateSwipeProgress(View animView, boolean dismissable,
6303 float swipeProgress) {
6304 // Returning true prevents alpha fading.
6305 return !mFadeNotificationsOnDismiss;
6306 }
6307
6308 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006309 public float getFalsingThresholdFactor() {
6310 return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
6311 }
6312
6313 @Override
Gus Prevasc4e68d42019-01-17 15:45:21 -05006314 public int getConstrainSwipeStartPosition() {
6315 NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
6316 if (menuRow != null) {
6317 return Math.abs(menuRow.getMenuSnapTarget());
6318 }
6319 return 0;
6320 }
6321
6322 @Override
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006323 public boolean canChildBeDismissed(View v) {
Ned Burns61269442019-05-02 18:27:23 -04006324 return StackScrollAlgorithm.canChildBeDismissed(v);
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006325 }
Gus Prevas37d67e22018-11-02 14:48:55 -04006326
6327 @Override
6328 public boolean canChildBeDismissedInDirection(View v, boolean isRightOrDown) {
Evan Laird30b9b162019-04-24 15:22:24 -04006329 //TODO: b/131242807 for why this doesn't do anything with direction
6330 return canChildBeDismissed(v);
Gus Prevas37d67e22018-11-02 14:48:55 -04006331 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006332 };
6333
6334 // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------
6335
6336 @ShadeViewRefactor(RefactorComponent.INPUT)
6337 private final DragDownCallback mDragDownCallback = new DragDownCallback() {
6338
6339 /* Only ever called as a consequence of a lockscreen expansion gesture. */
6340 @Override
6341 public boolean onDraggedDown(View startingChild, int dragLengthY) {
6342 if (mStatusBarState == StatusBarState.KEYGUARD
Lucas Dupin55c6e802018-09-27 18:07:36 -07006343 && hasActiveNotifications()) {
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006344 mLockscreenGestureLogger.write(
6345 MetricsEvent.ACTION_LS_SHADE,
6346 (int) (dragLengthY / mDisplayMetrics.density),
6347 0 /* velocityDp - N/A */);
6348
Lucas Dupinc7804042018-12-21 12:26:33 -08006349 if (!mAmbientState.isDark() || startingChild != null) {
Lucas Dupin55c6e802018-09-27 18:07:36 -07006350 // We have notifications, go to locked shade.
Jason Monk297c04e2018-08-23 17:16:59 -04006351 mShadeController.goToLockedShade(startingChild);
Lucas Dupin55c6e802018-09-27 18:07:36 -07006352 if (startingChild instanceof ExpandableNotificationRow) {
6353 ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
6354 row.onExpandedByGesture(true /* drag down is always an open */);
6355 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006356 }
Lucas Dupin55c6e802018-09-27 18:07:36 -07006357
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006358 return true;
6359 } else {
6360 // abort gesture.
6361 return false;
6362 }
6363 }
6364
6365 @Override
6366 public void onDragDownReset() {
6367 setDimmed(true /* dimmed */, true /* animated */);
6368 resetScrollPosition();
6369 resetCheckSnoozeLeavebehind();
6370 }
6371
6372 @Override
6373 public void onCrossedThreshold(boolean above) {
6374 setDimmed(!above /* dimmed */, true /* animate */);
6375 }
6376
6377 @Override
6378 public void onTouchSlopExceeded() {
6379 cancelLongPress();
6380 checkSnoozeLeavebehind();
6381 }
6382
6383 @Override
6384 public void setEmptyDragAmount(float amount) {
6385 mNotificationPanel.setEmptyDragAmount(amount);
6386 }
6387
6388 @Override
6389 public boolean isFalsingCheckNeeded() {
6390 return mStatusBarState == StatusBarState.KEYGUARD;
6391 }
6392 };
6393
6394 public DragDownCallback getDragDownCallback() { return mDragDownCallback; }
6395
6396 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6397 private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() {
6398 @Override
6399 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6400 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6401 }
6402
6403 @Override
6404 public boolean isExpanded() {
6405 return mIsExpanded;
6406 }
6407
6408 @Override
6409 public Context getContext() {
6410 return mContext;
6411 }
6412 };
6413
6414 public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
6415
6416
6417 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6418 private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() {
6419 @Override
6420 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
6421 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
6422 && (mIsExpanded || changedRow.isPinned());
6423 if (animated) {
6424 mExpandedGroupView = changedRow;
6425 mNeedsAnimation = true;
6426 }
6427 changedRow.setChildrenExpanded(expanded, animated);
6428 if (!mGroupExpandedForMeasure) {
6429 onHeightChanged(changedRow, false /* needsAnimation */);
6430 }
6431 runAfterAnimationFinished(new Runnable() {
6432 @Override
6433 public void run() {
6434 changedRow.onFinishedExpansionChange();
6435 }
6436 });
6437 }
6438
6439 @Override
6440 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
6441 mStatusBar.requestNotificationUpdate();
6442 }
6443
6444 @Override
6445 public void onGroupsChanged() {
6446 mStatusBar.requestNotificationUpdate();
6447 }
6448 };
6449
6450 @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
6451 private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
6452 @Override
6453 public ExpandableView getChildAtPosition(float touchX, float touchY) {
6454 return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY);
6455 }
6456
6457 @Override
6458 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
6459 return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY);
6460 }
6461
6462 @Override
6463 public boolean canChildBeExpanded(View v) {
6464 return v instanceof ExpandableNotificationRow
6465 && ((ExpandableNotificationRow) v).isExpandable()
6466 && !((ExpandableNotificationRow) v).areGutsExposed()
6467 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
6468 }
6469
6470 /* Only ever called as a consequence of an expansion gesture in the shade. */
6471 @Override
6472 public void setUserExpandedChild(View v, boolean userExpanded) {
6473 if (v instanceof ExpandableNotificationRow) {
6474 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
6475 if (userExpanded && onKeyguard()) {
6476 // Due to a race when locking the screen while touching, a notification may be
6477 // expanded even after we went back to keyguard. An example of this happens if
6478 // you click in the empty space while expanding a group.
6479
6480 // We also need to un-user lock it here, since otherwise the content height
6481 // calculated might be wrong. We also can't invert the two calls since
6482 // un-userlocking it will trigger a layout switch in the content view.
6483 row.setUserLocked(false);
6484 updateContentHeight();
6485 notifyHeightChangeListener(row);
6486 return;
6487 }
6488 row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
6489 row.onExpandedByGesture(userExpanded);
6490 }
6491 }
6492
6493 @Override
6494 public void setExpansionCancelled(View v) {
6495 if (v instanceof ExpandableNotificationRow) {
6496 ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
6497 }
6498 }
6499
6500 @Override
6501 public void setUserLockedChild(View v, boolean userLocked) {
6502 if (v instanceof ExpandableNotificationRow) {
6503 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
6504 }
6505 cancelLongPress();
6506 requestDisallowInterceptTouchEvent(true);
6507 }
6508
6509 @Override
6510 public void expansionStateChanged(boolean isExpanding) {
6511 mExpandingNotification = isExpanding;
6512 if (!mExpandedInThisMotion) {
Gus Prevas0fa58d62019-01-11 13:58:40 -05006513 if (ANCHOR_SCROLLING) {
6514 // TODO
6515 } else {
6516 mMaxScrollAfterExpand = mOwnScrollY;
6517 }
Aaron Heuckrothcd944dc2018-10-01 16:31:08 -04006518 mExpandedInThisMotion = true;
6519 }
6520 }
6521
6522 @Override
6523 public int getMaxExpandHeight(ExpandableView view) {
6524 return view.getMaxContentHeight();
6525 }
6526 };
6527
6528 public ExpandHelper.Callback getExpandHelperCallback() {
6529 return mExpandHelperCallback;
Aaron Heuckroth45d20be2018-09-18 13:47:26 -04006530 }
Ned Burns61269442019-05-02 18:27:23 -04006531
6532 /** Enum for selecting some or all notification rows (does not included non-notif views). */
6533 @Retention(SOURCE)
6534 @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
6535 public @interface SelectedRows {}
6536 /** All rows representing notifs. */
6537 public static final int ROWS_ALL = 0;
6538 /** Only rows where entry.isHighPriority() is true. */
6539 public static final int ROWS_HIGH_PRIORITY = 1;
6540 /** Only rows where entry.isHighPriority() is false. */
6541 public static final int ROWS_GENTLE = 2;
Selim Cinek67b22602014-03-10 15:40:16 +01006542}