• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 
17 
18 package com.android.systemui.shade;
19 
20 import static android.view.WindowInsets.Type.ime;
21 
22 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
23 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
24 import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
25 import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE;
26 import static com.android.systemui.shade.NotificationPanelViewController.FLING_EXPAND;
27 import static com.android.systemui.shade.NotificationPanelViewController.FLING_HIDE;
28 import static com.android.systemui.shade.NotificationPanelViewController.QS_PARALLAX_AMOUNT;
29 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
30 import static com.android.systemui.statusbar.StatusBarState.SHADE;
31 
32 import android.animation.Animator;
33 import android.animation.AnimatorListenerAdapter;
34 import android.animation.ValueAnimator;
35 import android.app.Fragment;
36 import android.content.res.Resources;
37 import android.graphics.Rect;
38 import android.graphics.Region;
39 import android.util.Log;
40 import android.util.MathUtils;
41 import android.view.MotionEvent;
42 import android.view.VelocityTracker;
43 import android.view.View;
44 import android.view.ViewConfiguration;
45 import android.view.ViewGroup;
46 import android.view.accessibility.AccessibilityManager;
47 import android.widget.FrameLayout;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.jank.InteractionJankMonitor;
51 import com.android.internal.logging.MetricsLogger;
52 import com.android.internal.logging.nano.MetricsProto;
53 import com.android.internal.policy.ScreenDecorationsUtils;
54 import com.android.internal.policy.SystemBarUtils;
55 import com.android.keyguard.FaceAuthApiRequestReason;
56 import com.android.keyguard.KeyguardUpdateMonitor;
57 import com.android.systemui.R;
58 import com.android.systemui.animation.Interpolators;
59 import com.android.systemui.classifier.Classifier;
60 import com.android.systemui.classifier.FalsingCollector;
61 import com.android.systemui.flags.FeatureFlags;
62 import com.android.systemui.fragments.FragmentHostManager;
63 import com.android.systemui.media.controls.pipeline.MediaDataManager;
64 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
65 import com.android.systemui.plugins.FalsingManager;
66 import com.android.systemui.plugins.qs.QS;
67 import com.android.systemui.screenrecord.RecordingController;
68 import com.android.systemui.shade.transition.ShadeTransitionController;
69 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
70 import com.android.systemui.statusbar.NotificationRemoteInputManager;
71 import com.android.systemui.statusbar.NotificationShadeDepthController;
72 import com.android.systemui.statusbar.PulseExpansionHandler;
73 import com.android.systemui.statusbar.QsFrameTranslateController;
74 import com.android.systemui.statusbar.StatusBarState;
75 import com.android.systemui.statusbar.notification.stack.AmbientState;
76 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
77 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
78 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
79 import com.android.systemui.statusbar.phone.KeyguardBypassController;
80 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
81 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
82 import com.android.systemui.statusbar.phone.ScrimController;
83 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
84 import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
85 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
86 import com.android.systemui.statusbar.policy.KeyguardStateController;
87 import com.android.systemui.util.LargeScreenUtils;
88 
89 import javax.inject.Inject;
90 
91 import dagger.Lazy;
92 
93 /** Handles QuickSettings touch handling, expansion and animation state
94  * TODO (b/264460656) make this dumpable
95  */
96 @CentralSurfacesComponent.CentralSurfacesScope
97 public class QuickSettingsController {
98     public static final String TAG = "QuickSettingsController";
99 
100     private QS mQs;
101     private final Lazy<NotificationPanelViewController> mPanelViewControllerLazy;
102 
103     private final NotificationPanelView mPanelView;
104     private final KeyguardStatusBarView mKeyguardStatusBar;
105     private final FrameLayout mQsFrame;
106 
107     private final QsFrameTranslateController mQsFrameTranslateController;
108     private final ShadeTransitionController mShadeTransitionController;
109     private final PulseExpansionHandler mPulseExpansionHandler;
110     private final ShadeExpansionStateManager mShadeExpansionStateManager;
111     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
112     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
113     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
114     private final NotificationShadeDepthController mDepthController;
115     private final ShadeHeaderController mShadeHeaderController;
116     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
117     private final KeyguardStateController mKeyguardStateController;
118     private final KeyguardBypassController mKeyguardBypassController;
119     private final NotificationRemoteInputManager mRemoteInputManager;
120     private VelocityTracker mQsVelocityTracker;
121     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
122     private final ScrimController mScrimController;
123     private final MediaDataManager mMediaDataManager;
124     private final MediaHierarchyManager mMediaHierarchyManager;
125     private final AmbientState mAmbientState;
126     private final RecordingController mRecordingController;
127     private final FalsingCollector mFalsingCollector;
128     private final LockscreenGestureLogger mLockscreenGestureLogger;
129     private final ShadeLogger mShadeLog;
130     private final FeatureFlags mFeatureFlags;
131     private final InteractionJankMonitor mInteractionJankMonitor;
132     private final FalsingManager mFalsingManager;
133     private final AccessibilityManager mAccessibilityManager;
134     private final MetricsLogger mMetricsLogger;
135     private final Resources mResources;
136 
137     /** Whether the notifications are displayed full width (no margins on the side). */
138     private boolean mIsFullWidth;
139     private int mTouchSlop;
140     private float mSlopMultiplier;
141     /** the current {@link StatusBarState} */
142     private int mBarState;
143     private int mStatusBarMinHeight;
144     private boolean mScrimEnabled = true;
145     private int mScrimCornerRadius;
146     private int mScreenCornerRadius;
147     private boolean mUseLargeScreenShadeHeader;
148     private int mLargeScreenShadeHeaderHeight;
149     private int mDisplayRightInset = 0; // in pixels
150     private int mDisplayLeftInset = 0; // in pixels
151     private boolean mSplitShadeEnabled;
152     /**
153      * The padding between the start of notifications and the qs boundary on the lockscreen.
154      * On lockscreen, notifications aren't inset this extra amount, but we still want the
155      * qs boundary to be padded.
156      */
157     private int mLockscreenNotificationPadding;
158     private int mSplitShadeNotificationsScrimMarginBottom;
159     private boolean mDozing;
160     private boolean mEnableClipping;
161     private int mFalsingThreshold;
162     /**
163      * Position of the qs bottom during the full shade transition. This is needed as the toppadding
164      * can change during state changes, which makes it much harder to do animations
165      */
166     private int mTransitionToFullShadePosition;
167     private boolean mCollapsedOnDown;
168     private float mShadeExpandedHeight = 0;
169     private boolean mLastShadeFlingWasExpanding;
170 
171     private float mInitialHeightOnTouch;
172     private float mInitialTouchX;
173     private float mInitialTouchY;
174     /** whether current touch Y delta is above falsing threshold */
175     private boolean mTouchAboveFalsingThreshold;
176     /** whether we are tracking a touch on QS container */
177     private boolean mTracking;
178     /** pointerId of the pointer we're currently tracking */
179     private int mTrackingPointer;
180 
181     /**
182      * Indicates that QS is in expanded state which can happen by:
183      * - single pane shade: expanding shade and then expanding QS
184      * - split shade: just expanding shade (QS are expanded automatically)
185      */
186     private boolean mExpanded;
187     /** Indicates QS is at its max height */
188     private boolean mFullyExpanded;
189     /**
190      * Determines if QS should be already expanded when expanding shade.
191      * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
192      * It needs to be set when movement starts as it resets at the end of expansion/collapse.
193      */
194     private boolean mExpandImmediate;
195     private boolean mExpandedWhenExpandingStarted;
196     private boolean mAnimatingHiddenFromCollapsed;
197     private boolean mVisible;
198     private float mExpansionHeight;
199     /**
200      * QS height when QS expansion fraction is 0 so when QS is collapsed. That state doesn't really
201      * exist for split shade so currently this value is always 0 then.
202      */
203     private int mMinExpansionHeight;
204     /** QS height when QS expansion fraction is 1 so qs is fully expanded */
205     private int mMaxExpansionHeight;
206     /** Expansion fraction of the notification shade */
207     private float mShadeExpandedFraction;
208     private int mPeekHeight;
209     private float mLastOverscroll;
210     private boolean mExpansionFromOverscroll;
211     private boolean mExpansionEnabledPolicy = true;
212     private boolean mExpansionEnabledAmbient = true;
213     private float mQuickQsHeaderHeight;
214     /**
215      * Determines if QS should be already expanded when expanding shade.
216      * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
217      * It needs to be set when movement starts as it resets at the end of expansion/collapse.
218      */
219     private boolean mTwoFingerExpandPossible;
220     /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
221     private boolean mConflictingExpansionGesture;
222     /**
223      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
224      * need to take this into account in our panel height calculation.
225      */
226     private boolean mAnimatorExpand;
227 
228     /**
229      * The amount of progress we are currently in if we're transitioning to the full shade.
230      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
231      * shade. This value can also go beyond 1.1 when we're overshooting!
232      */
233     private float mTransitioningToFullShadeProgress;
234     /** Distance a full shade transition takes in order for qs to fully transition to the shade. */
235     private int mDistanceForFullShadeTransition;
236     private boolean mStackScrollerOverscrolling;
237     /** Indicates QS is animating - set by flingQs */
238     private boolean mAnimating;
239     /** Whether the current animator is resetting the qs translation. */
240     private boolean mIsTranslationResettingAnimator;
241     /** Whether the current animator is resetting the pulse expansion after a drag down. */
242     private boolean mIsPulseExpansionResettingAnimator;
243     /** The translation amount for QS for the full shade transition. */
244     private float mTranslationForFullShadeTransition;
245     /** Should we animate the next bounds update. */
246     private boolean mAnimateNextNotificationBounds;
247     /** The delay for the next bounds animation. */
248     private long mNotificationBoundsAnimationDelay;
249     /** The duration of the notification bounds animation. */
250     private long mNotificationBoundsAnimationDuration;
251 
252     /** TODO(b/273591201): remove after bug resolved */
253     private int mLastClippingTopBound;
254     private int mLastNotificationsTopPadding;
255     private int mLastNotificationsClippingTopBound;
256     private int mLastNotificationsClippingTopBoundNssl;
257 
258     private final Region mInterceptRegion = new Region();
259     /** The end bounds of a clipping animation. */
260     private final Rect mClippingAnimationEndBounds = new Rect();
261     private final Rect mLastClipBounds = new Rect();
262 
263     /** The animator for the qs clipping bounds. */
264     private ValueAnimator mClippingAnimator = null;
265     /** The main animator for QS expansion */
266     private ValueAnimator mExpansionAnimator;
267     /** The animator for QS size change */
268     private ValueAnimator mSizeChangeAnimator;
269 
270     private ExpansionHeightListener mExpansionHeightListener;
271     private QsStateUpdateListener mQsStateUpdateListener;
272     private ApplyClippingImmediatelyListener mApplyClippingImmediatelyListener;
273     private FlingQsWithoutClickListener mFlingQsWithoutClickListener;
274     private ExpansionHeightSetToMaxListener mExpansionHeightSetToMaxListener;
275     private final QS.HeightListener mQsHeightListener = this::onHeightChanged;
276     private final Runnable mQsCollapseExpandAction = this::collapseOrExpandQs;
277     private final QS.ScrollListener mQsScrollListener = this::onScroll;
278 
279     @Inject
QuickSettingsController( Lazy<NotificationPanelViewController> panelViewControllerLazy, NotificationPanelView panelView, QsFrameTranslateController qsFrameTranslateController, ShadeTransitionController shadeTransitionController, PulseExpansionHandler pulseExpansionHandler, NotificationRemoteInputManager remoteInputManager, ShadeExpansionStateManager shadeExpansionStateManager, StatusBarKeyguardViewManager statusBarKeyguardViewManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, LockscreenShadeTransitionController lockscreenShadeTransitionController, NotificationShadeDepthController notificationShadeDepthController, ShadeHeaderController shadeHeaderController, StatusBarTouchableRegionManager statusBarTouchableRegionManager, KeyguardStateController keyguardStateController, KeyguardBypassController keyguardBypassController, KeyguardUpdateMonitor keyguardUpdateMonitor, ScrimController scrimController, MediaDataManager mediaDataManager, MediaHierarchyManager mediaHierarchyManager, AmbientState ambientState, RecordingController recordingController, FalsingManager falsingManager, FalsingCollector falsingCollector, AccessibilityManager accessibilityManager, LockscreenGestureLogger lockscreenGestureLogger, MetricsLogger metricsLogger, FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, ShadeLogger shadeLog )280     public QuickSettingsController(
281             Lazy<NotificationPanelViewController> panelViewControllerLazy,
282             NotificationPanelView panelView,
283             QsFrameTranslateController qsFrameTranslateController,
284             ShadeTransitionController shadeTransitionController,
285             PulseExpansionHandler pulseExpansionHandler,
286             NotificationRemoteInputManager remoteInputManager,
287             ShadeExpansionStateManager shadeExpansionStateManager,
288             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
289             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
290             LockscreenShadeTransitionController lockscreenShadeTransitionController,
291             NotificationShadeDepthController notificationShadeDepthController,
292             ShadeHeaderController shadeHeaderController,
293             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
294             KeyguardStateController keyguardStateController,
295             KeyguardBypassController keyguardBypassController,
296             KeyguardUpdateMonitor keyguardUpdateMonitor,
297             ScrimController scrimController,
298             MediaDataManager mediaDataManager,
299             MediaHierarchyManager mediaHierarchyManager,
300             AmbientState ambientState,
301             RecordingController recordingController,
302             FalsingManager falsingManager,
303             FalsingCollector falsingCollector,
304             AccessibilityManager accessibilityManager,
305             LockscreenGestureLogger lockscreenGestureLogger,
306             MetricsLogger metricsLogger,
307             FeatureFlags featureFlags,
308             InteractionJankMonitor interactionJankMonitor,
309             ShadeLogger shadeLog
310     ) {
311         mPanelViewControllerLazy = panelViewControllerLazy;
312         mPanelView = panelView;
313         mQsFrame = mPanelView.findViewById(R.id.qs_frame);
314         mKeyguardStatusBar = mPanelView.findViewById(R.id.keyguard_header);
315         mResources = mPanelView.getResources();
316         mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
317         mQsFrameTranslateController = qsFrameTranslateController;
318         mShadeTransitionController = shadeTransitionController;
319         mPulseExpansionHandler = pulseExpansionHandler;
320         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
321             if (mQs != null) {
322                 mQs.animateHeaderSlidingOut();
323             }
324         });
325         mRemoteInputManager = remoteInputManager;
326         mShadeExpansionStateManager = shadeExpansionStateManager;
327         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
328         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
329         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
330         mDepthController = notificationShadeDepthController;
331         mShadeHeaderController = shadeHeaderController;
332         mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
333         mKeyguardStateController = keyguardStateController;
334         mKeyguardBypassController = keyguardBypassController;
335         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
336         mScrimController = scrimController;
337         mMediaDataManager = mediaDataManager;
338         mMediaHierarchyManager = mediaHierarchyManager;
339         mAmbientState = ambientState;
340         mRecordingController = recordingController;
341         mFalsingManager = falsingManager;
342         mFalsingCollector = falsingCollector;
343         mAccessibilityManager = accessibilityManager;
344 
345         mLockscreenGestureLogger = lockscreenGestureLogger;
346         mMetricsLogger = metricsLogger;
347         mShadeLog = shadeLog;
348         mFeatureFlags = featureFlags;
349         mInteractionJankMonitor = interactionJankMonitor;
350 
351         mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
352     }
353 
354     @VisibleForTesting
setQs(QS qs)355     void setQs(QS qs) {
356         mQs = qs;
357     }
358 
setExpansionHeightListener(ExpansionHeightListener listener)359     public void setExpansionHeightListener(ExpansionHeightListener listener) {
360         mExpansionHeightListener = listener;
361     }
362 
setQsStateUpdateListener(QsStateUpdateListener listener)363     public void setQsStateUpdateListener(QsStateUpdateListener listener) {
364         mQsStateUpdateListener = listener;
365     }
366 
setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener)367     public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
368         mApplyClippingImmediatelyListener = listener;
369     }
370 
setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener)371     public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
372         mFlingQsWithoutClickListener = listener;
373     }
374 
setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback)375     public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
376         mExpansionHeightSetToMaxListener = callback;
377     }
378 
loadDimens()379     void loadDimens() {
380         final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext());
381         mTouchSlop = configuration.getScaledTouchSlop();
382         mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
383         mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
384         mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext());
385         mScrimCornerRadius = mResources.getDimensionPixelSize(
386                 R.dimen.notification_scrim_corner_radius);
387         mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(
388                 mPanelView.getContext());
389         mFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
390         mLockscreenNotificationPadding = mResources.getDimensionPixelSize(
391                 R.dimen.notification_side_paddings);
392         mDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
393                 R.dimen.lockscreen_shade_qs_transition_distance);
394     }
395 
updateResources()396     void updateResources() {
397         mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
398         if (mQs != null) {
399             mQs.setInSplitShade(mSplitShadeEnabled);
400         }
401         mSplitShadeNotificationsScrimMarginBottom =
402                 mResources.getDimensionPixelSize(
403                         R.dimen.split_shade_notifications_scrim_margin_bottom);
404 
405         mUseLargeScreenShadeHeader =
406                 LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources());
407         mLargeScreenShadeHeaderHeight =
408                 mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
409         int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
410                 mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
411         mShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
412         mAmbientState.setStackTopMargin(topMargin);
413 
414         mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight;
415 
416         mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping);
417     }
418 
419     // TODO (b/265054088): move this and others to a CoreStartable
initNotificationStackScrollLayoutController()420     void initNotificationStackScrollLayoutController() {
421         mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
422                 new NsslOverscrollTopChangedListener());
423         mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged);
424         mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
425     }
426 
onStackYChanged(boolean shouldAnimate)427     private void onStackYChanged(boolean shouldAnimate) {
428         if (isQsFragmentCreated()) {
429             if (shouldAnimate) {
430                 setAnimateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD,
431                         0 /* delay */);
432             }
433             setClippingBounds();
434         }
435     }
436 
onNotificationScrolled(int newScrollPosition)437     private void onNotificationScrolled(int newScrollPosition) {
438         updateExpansionEnabledAmbient();
439     }
440 
441     @VisibleForTesting
setStatusBarMinHeight(int height)442     void setStatusBarMinHeight(int height) {
443         mStatusBarMinHeight = height;
444     }
445 
getHeaderHeight()446     int getHeaderHeight() {
447         return mQs.getHeader().getHeight();
448     }
449 
450     /** Returns the padding of the stackscroller when unlocked */
getUnlockedStackScrollerPadding()451     int getUnlockedStackScrollerPadding() {
452         return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
453     }
454 
isRemoteInputActiveWithKeyboardUp()455     private boolean isRemoteInputActiveWithKeyboardUp() {
456         //TODO(b/227115380) remove the isVisible(ime()) check once isRemoteInputActive is fixed.
457         // The check for keyboard visibility is a temporary workaround that allows QS to expand
458         // even when isRemoteInputActive is mistakenly returning true.
459         return mRemoteInputManager.isRemoteInputActive()
460                 && mPanelView.getRootWindowInsets().isVisible(ime());
461     }
462 
isExpansionEnabled()463     public boolean isExpansionEnabled() {
464         return mExpansionEnabledPolicy && mExpansionEnabledAmbient
465             && !isRemoteInputActiveWithKeyboardUp();
466     }
467 
getTransitioningToFullShadeProgress()468     public float getTransitioningToFullShadeProgress() {
469         return mTransitioningToFullShadeProgress;
470     }
471 
472     /** */
473     @VisibleForTesting
isExpandImmediate()474     boolean isExpandImmediate() {
475         return mExpandImmediate;
476     }
477 
getInitialTouchY()478     float getInitialTouchY() {
479         return mInitialTouchY;
480     }
481 
482     /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */
isSplitShadeAndTouchXOutsideQs(float touchX)483     private boolean isSplitShadeAndTouchXOutsideQs(float touchX) {
484         return mSplitShadeEnabled && touchX < mQsFrame.getX()
485                 || touchX > mQsFrame.getX() + mQsFrame.getWidth();
486     }
487 
488     /** Returns whether touch is within QS area */
isTouchInQsArea(float x, float y)489     private boolean isTouchInQsArea(float x, float y) {
490         if (isSplitShadeAndTouchXOutsideQs(x)) {
491             return false;
492         }
493         // TODO (b/265193930): remove dependency on NPVC
494         // Let's reject anything at the very bottom around the home handle in gesture nav
495         if (mPanelViewControllerLazy.get().isInGestureNavHomeHandleArea(x, y)) {
496             return false;
497         }
498         return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
499                 || y <= mQs.getView().getY() + mQs.getView().getHeight();
500     }
501 
502     /** Returns whether or not event should open QS */
503     @VisibleForTesting
isOpenQsEvent(MotionEvent event)504     boolean isOpenQsEvent(MotionEvent event) {
505         final int pointerCount = event.getPointerCount();
506         final int action = event.getActionMasked();
507 
508         final boolean
509                 twoFingerDrag =
510                 action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2;
511 
512         final boolean
513                 stylusButtonClickDrag =
514                 action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
515                         MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed(
516                         MotionEvent.BUTTON_STYLUS_SECONDARY));
517 
518         final boolean
519                 mouseButtonClickDrag =
520                 action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
521                         MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(
522                         MotionEvent.BUTTON_TERTIARY));
523 
524         return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
525     }
526 
527 
getExpanded()528     public boolean getExpanded() {
529         return mExpanded;
530     }
531 
532     @VisibleForTesting
isTracking()533     boolean isTracking() {
534         return mTracking;
535     }
536 
getFullyExpanded()537     public boolean getFullyExpanded() {
538         return mFullyExpanded;
539     }
540 
isGoingBetweenClosedShadeAndExpandedQs()541     boolean isGoingBetweenClosedShadeAndExpandedQs() {
542         // Below is true when QS are expanded and we swipe up from the same bottom of panel to
543         // close the whole shade with one motion. Also this will be always true when closing
544         // split shade as there QS are always expanded so every collapsing motion is motion from
545         // expanded QS to closed panel
546         return mExpandImmediate || (mExpanded
547                 && !mTracking && !isExpansionAnimating()
548                 && !mExpansionFromOverscroll);
549     }
550 
isQsFragmentCreated()551     private boolean isQsFragmentCreated() {
552         return mQs != null;
553     }
554 
isCustomizing()555     public boolean isCustomizing() {
556         return isQsFragmentCreated() && mQs.isCustomizing();
557     }
558 
getExpansionHeight()559     public float getExpansionHeight() {
560         return mExpansionHeight;
561     }
562 
getExpandedWhenExpandingStarted()563     public boolean getExpandedWhenExpandingStarted() {
564         return mExpandedWhenExpandingStarted;
565     }
566 
getMinExpansionHeight()567     public int getMinExpansionHeight() {
568         return mMinExpansionHeight;
569     }
570 
isFullyExpandedAndTouchesDisallowed()571     public boolean isFullyExpandedAndTouchesDisallowed() {
572         return isQsFragmentCreated() && getFullyExpanded() && disallowTouches();
573     }
574 
getMaxExpansionHeight()575     public int getMaxExpansionHeight() {
576         return mMaxExpansionHeight;
577     }
578 
isQsFalseTouch()579     private boolean isQsFalseTouch() {
580         if (mFalsingManager.isClassifierEnabled()) {
581             return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS);
582         }
583         return !mTouchAboveFalsingThreshold;
584     }
585 
getFalsingThreshold()586     public int getFalsingThreshold() {
587         return mFalsingThreshold;
588     }
589 
590     /**
591      * Returns Whether we should intercept a gesture to open Quick Settings.
592      */
shouldQuickSettingsIntercept(float x, float y, float yDiff)593     public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
594         boolean keyguardShowing = mBarState == KEYGUARD;
595         if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing
596                 && mKeyguardBypassController.getBypassEnabled()) || mSplitShadeEnabled) {
597             return false;
598         }
599         View header = keyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
600         int frameTop = keyguardShowing
601                 || mQs == null ? 0 : mQsFrame.getTop();
602         mInterceptRegion.set(
603                 /* left= */ (int) mQsFrame.getX(),
604                 /* top= */ header.getTop() + frameTop,
605                 /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
606                 /* bottom= */ header.getBottom() + frameTop);
607         // Also allow QS to intercept if the touch is near the notch.
608         mStatusBarTouchableRegionManager.updateRegionForNotch(mInterceptRegion);
609         final boolean onHeader = mInterceptRegion.contains((int) x, (int) y);
610 
611         if (getExpanded()) {
612             return onHeader || (yDiff < 0 && isTouchInQsArea(x, y));
613         } else {
614             return onHeader;
615         }
616     }
617 
618     /** Returns amount header should be translated */
getHeaderTranslation()619     private float getHeaderTranslation() {
620         if (mSplitShadeEnabled) {
621             // in split shade QS don't translate, just (un)squish and overshoot
622             return 0;
623         }
624         if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
625             return -mQs.getQsMinExpansionHeight();
626         }
627         float appearAmount = mNotificationStackScrollLayoutController
628                 .calculateAppearFraction(mShadeExpandedHeight);
629         float startHeight = -getExpansionHeight();
630         if (mBarState == SHADE) {
631             // Small parallax as we pull down and clip QS
632             startHeight = -getExpansionHeight() * QS_PARALLAX_AMOUNT;
633         }
634         if (mKeyguardBypassController.getBypassEnabled() && mBarState == KEYGUARD) {
635             appearAmount = mNotificationStackScrollLayoutController.calculateAppearFractionBypass();
636             startHeight = -mQs.getQsMinExpansionHeight();
637         }
638         float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount));
639         return Math.min(0, translation);
640     }
641 
642     /**
643      * Can the panel collapse in this motion because it was started on QQS?
644      *
645      * @param downX the x location where the touch started
646      * @param downY the y location where the touch started
647      * Returns true if the panel could be collapsed because it stared on QQS
648      */
canPanelCollapseOnQQS(float downX, float downY)649     public boolean canPanelCollapseOnQQS(float downX, float downY) {
650         if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) {
651             return false;
652         }
653         View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader();
654         return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth()
655                 && downY <= header.getBottom();
656     }
657 
658     /** Closes the Qs customizer. */
closeQsCustomizer()659     public void closeQsCustomizer() {
660         mQs.closeCustomizer();
661     }
662 
663     /** Returns whether touches from the notification panel should be disallowed */
disallowTouches()664     public boolean disallowTouches() {
665         return mQs.disallowPanelTouches();
666     }
667 
setListening(boolean listening)668     void setListening(boolean listening) {
669         if (mQs != null) {
670             mQs.setListening(listening);
671         }
672     }
673 
hideQsImmediately()674     void hideQsImmediately() {
675         if (mQs != null) {
676             mQs.hideImmediately();
677         }
678     }
679 
setDozing(boolean dozing)680     public void setDozing(boolean dozing) {
681         mDozing = dozing;
682     }
683 
684     /**
685      * This method closes QS but in split shade it should be used only in special cases: to make
686      * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing
687      * from split shade
688      */
closeQs()689     public void closeQs() {
690         if (mSplitShadeEnabled) {
691             mShadeLog.d("Closing QS while in split shade");
692         }
693         cancelExpansionAnimation();
694         setExpansionHeight(getMinExpansionHeight());
695         // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
696         // middle of animation - we need to make sure that value is always false when shade if
697         // fully collapsed or expanded
698         setExpandImmediate(false);
699     }
700 
701     @VisibleForTesting
setExpanded(boolean expanded)702     void setExpanded(boolean expanded) {
703         boolean changed = mExpanded != expanded;
704         if (changed) {
705             mExpanded = expanded;
706             updateQsState();
707             mShadeExpansionStateManager.onQsExpansionChanged(expanded);
708             mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
709                     getMinExpansionHeight(), getMaxExpansionHeight(),
710                     mStackScrollerOverscrolling, mAnimatorExpand, mAnimating);
711         }
712     }
713 
setLastShadeFlingWasExpanding(boolean expanding)714     void setLastShadeFlingWasExpanding(boolean expanding) {
715         mLastShadeFlingWasExpanding = expanding;
716         mShadeLog.logLastFlingWasExpanding(expanding);
717     }
718 
719     /** update Qs height state */
setExpansionHeight(float height)720     public void setExpansionHeight(float height) {
721         checkCorrectSplitShadeState(height);
722         int maxHeight = getMaxExpansionHeight();
723         height = Math.min(Math.max(
724                 height, getMinExpansionHeight()), maxHeight);
725         mFullyExpanded = height == maxHeight && maxHeight != 0;
726         boolean qsAnimatingAway = !mAnimatorExpand && mAnimating;
727         if (height > getMinExpansionHeight() && !getExpanded()
728                 && !mStackScrollerOverscrolling
729                 && !mDozing && !qsAnimatingAway) {
730             setExpanded(true);
731         } else if (height <= getMinExpansionHeight()
732                 && getExpanded()) {
733             setExpanded(false);
734         }
735         mExpansionHeight = height;
736         updateExpansion();
737 
738         if (mExpansionHeightListener != null) {
739             mExpansionHeightListener.onQsSetExpansionHeightCalled(getFullyExpanded());
740         }
741     }
742 
743     /** TODO(b/269742565) Remove this logging */
checkCorrectSplitShadeState(float height)744     private void checkCorrectSplitShadeState(float height) {
745         if (mSplitShadeEnabled && height == 0
746                 && mPanelViewControllerLazy.get().isShadeFullyOpen()) {
747             Log.wtfStack(TAG, "qsExpansion set to 0 while split shade is expanding or open");
748         }
749     }
750 
751     /** */
setHeightOverrideToDesiredHeight()752     public void setHeightOverrideToDesiredHeight() {
753         if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
754             mQs.setHeightOverride(mQs.getDesiredHeight());
755         }
756     }
757 
758     /** Updates quick setting heights and returns old max height. */
updateHeightsOnShadeLayoutChange()759     int updateHeightsOnShadeLayoutChange() {
760         int oldMaxHeight = getMaxExpansionHeight();
761         if (isQsFragmentCreated()) {
762             updateMinHeight();
763             mMaxExpansionHeight = mQs.getDesiredHeight();
764             mNotificationStackScrollLayoutController.setMaxTopPadding(
765                     getMaxExpansionHeight());
766         }
767         return oldMaxHeight;
768     }
769 
770     /** Called when Shade view layout changed. Updates QS expansion or
771      * starts size change animation if height has changed. */
handleShadeLayoutChanged(int oldMaxHeight)772     void handleShadeLayoutChanged(int oldMaxHeight) {
773         if (mExpanded && mFullyExpanded) {
774             mExpansionHeight = mMaxExpansionHeight;
775             if (mExpansionHeightSetToMaxListener != null) {
776                 mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
777             }
778 
779             // Size has changed, start an animation.
780             if (getMaxExpansionHeight() != oldMaxHeight) {
781                 startSizeChangeAnimation(oldMaxHeight,
782                         getMaxExpansionHeight());
783             }
784         } else if (!getExpanded()
785                 && !isExpansionAnimating()) {
786             setExpansionHeight(getMinExpansionHeight() + mLastOverscroll);
787         } else {
788             mShadeLog.v("onLayoutChange: qs expansion not set");
789         }
790     }
791 
isSizeChangeAnimationRunning()792     private boolean isSizeChangeAnimationRunning() {
793         return mSizeChangeAnimator != null;
794     }
795 
startSizeChangeAnimation(int oldHeight, final int newHeight)796     private void startSizeChangeAnimation(int oldHeight, final int newHeight) {
797         if (mSizeChangeAnimator != null) {
798             oldHeight = (int) mSizeChangeAnimator.getAnimatedValue();
799             mSizeChangeAnimator.cancel();
800         }
801         mSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
802         mSizeChangeAnimator.setDuration(300);
803         mSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
804         mSizeChangeAnimator.addUpdateListener(animation -> {
805             if (mExpansionHeightSetToMaxListener != null) {
806                 mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
807             }
808 
809             int height = (int) mSizeChangeAnimator.getAnimatedValue();
810             mQs.setHeightOverride(height);
811         });
812         mSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
813             @Override
814             public void onAnimationEnd(Animator animation) {
815                 mSizeChangeAnimator = null;
816             }
817         });
818         mSizeChangeAnimator.start();
819     }
820 
setNotificationPanelFullWidth(boolean isFullWidth)821     void setNotificationPanelFullWidth(boolean isFullWidth) {
822         mIsFullWidth = isFullWidth;
823         if (mQs != null) {
824             mQs.setIsNotificationPanelFullWidth(isFullWidth);
825         }
826     }
827 
setBarState(int barState)828     void setBarState(int barState) {
829         mBarState = barState;
830     }
831 
832     /** */
setExpansionEnabledPolicy(boolean expansionEnabledPolicy)833     public void setExpansionEnabledPolicy(boolean expansionEnabledPolicy) {
834         mExpansionEnabledPolicy = expansionEnabledPolicy;
835         if (mQs != null) {
836             mQs.setHeaderClickable(isExpansionEnabled());
837         }
838     }
839 
setOverScrollAmount(int overExpansion)840     void setOverScrollAmount(int overExpansion) {
841         mQs.setOverScrollAmount(overExpansion);
842     }
843 
setOverScrolling(boolean overscrolling)844     private void setOverScrolling(boolean overscrolling) {
845         mStackScrollerOverscrolling = overscrolling;
846         if (mQs != null) {
847             mQs.setOverscrolling(overscrolling);
848         }
849     }
850 
851     /** Sets Qs ScrimEnabled and updates QS state. */
setScrimEnabled(boolean scrimEnabled)852     public void setScrimEnabled(boolean scrimEnabled) {
853         boolean changed = mScrimEnabled != scrimEnabled;
854         mScrimEnabled = scrimEnabled;
855         if (changed) {
856             updateQsState();
857         }
858     }
859 
setCollapsedOnDown(boolean collapsedOnDown)860     void setCollapsedOnDown(boolean collapsedOnDown) {
861         mCollapsedOnDown = collapsedOnDown;
862     }
863 
setShadeExpansion(float expandedHeight, float expandedFraction)864     void setShadeExpansion(float expandedHeight, float expandedFraction) {
865         mShadeExpandedHeight = expandedHeight;
866         mShadeExpandedFraction = expandedFraction;
867     }
868 
869     @VisibleForTesting
getShadeExpandedHeight()870     float getShadeExpandedHeight() {
871         return mShadeExpandedHeight;
872     }
873 
874     @VisibleForTesting
setExpandImmediate(boolean expandImmediate)875     void setExpandImmediate(boolean expandImmediate) {
876         if (expandImmediate != mExpandImmediate) {
877             mShadeLog.logQsExpandImmediateChanged(expandImmediate);
878             mExpandImmediate = expandImmediate;
879             mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
880         }
881     }
882 
setTwoFingerExpandPossible(boolean expandPossible)883     void setTwoFingerExpandPossible(boolean expandPossible) {
884         mTwoFingerExpandPossible = expandPossible;
885     }
886 
887     @VisibleForTesting
isTwoFingerExpandPossible()888     boolean isTwoFingerExpandPossible() {
889         return mTwoFingerExpandPossible;
890     }
891 
892     /** Called when Qs starts expanding */
onExpansionStarted()893     private void onExpansionStarted() {
894         cancelExpansionAnimation();
895         // TODO (b/265193930): remove dependency on NPVC
896         mPanelViewControllerLazy.get().cancelHeightAnimator();
897         // end
898 
899         // Reset scroll position and apply that position to the expanded height.
900         float height = mExpansionHeight;
901         setExpansionHeight(height);
902         mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
903 
904         // When expanding QS, let's authenticate the user if possible,
905         // this will speed up notification actions.
906         if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
907             mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
908         }
909     }
910 
updateQsState()911     void updateQsState() {
912         boolean qsFullScreen = mExpanded && !mSplitShadeEnabled;
913         mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen);
914         mNotificationStackScrollLayoutController.setScrollingEnabled(
915                 mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll));
916 
917         if (mQsStateUpdateListener != null) {
918             mQsStateUpdateListener.onQsStateUpdated(mExpanded, mStackScrollerOverscrolling);
919         }
920 
921         if (mQs == null) return;
922         mQs.setExpanded(mExpanded);
923     }
924 
925     /** update expanded state of QS */
updateExpansion()926     public void updateExpansion() {
927         if (mQs == null) return;
928         final float squishiness;
929         if ((mExpandImmediate || mExpanded) && !mSplitShadeEnabled) {
930             squishiness = 1;
931         } else if (mTransitioningToFullShadeProgress > 0.0f) {
932             squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction();
933         } else {
934             squishiness = mNotificationStackScrollLayoutController
935                     .getNotificationSquishinessFraction();
936         }
937         final float qsExpansionFraction = computeExpansionFraction();
938         final float adjustedExpansionFraction = mSplitShadeEnabled
939                 ? 1f : computeExpansionFraction();
940         mQs.setQsExpansion(
941                 adjustedExpansionFraction,
942                 mShadeExpandedFraction,
943                 getHeaderTranslation(),
944                 squishiness
945         );
946         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
947         int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
948         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
949         setClippingBounds();
950 
951         if (mSplitShadeEnabled) {
952             // In split shade we want to pretend that QS are always collapsed so their behaviour and
953             // interactions don't influence notifications as they do in portrait. But we want to set
954             // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1.
955             mNotificationStackScrollLayoutController.setQsExpansionFraction(0);
956         } else {
957             mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
958         }
959 
960         mDepthController.setQsPanelExpansion(qsExpansionFraction);
961         mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction);
962 
963         // TODO (b/265193930): remove dependency on NPVC
964         float shadeExpandedFraction = mBarState == KEYGUARD
965                 ? mPanelViewControllerLazy.get().getLockscreenShadeDragProgress()
966                 : mShadeExpandedFraction;
967         mShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
968         mShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
969         mShadeHeaderController.setQsVisible(mVisible);
970     }
971 
972     /** */
updateExpansionEnabledAmbient()973     public void updateExpansionEnabledAmbient() {
974         final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
975         mExpansionEnabledAmbient = mSplitShadeEnabled
976                 || (mAmbientState.getScrollY() <= scrollRangeToTop);
977         if (mQs != null) {
978             mQs.setHeaderClickable(isExpansionEnabled());
979         }
980     }
981 
982     /** Calculate y value of bottom of QS */
calculateBottomPosition(float qsExpansionFraction)983     private int calculateBottomPosition(float qsExpansionFraction) {
984         if (mTransitioningToFullShadeProgress > 0.0f) {
985             return mTransitionToFullShadePosition;
986         } else {
987             int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
988             int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
989             int qsBottomYTo = mQs.getDesiredHeight() + expandedTopMargin;
990             return (int) MathUtils.lerp(qsBottomYFrom, qsBottomYTo, qsExpansionFraction);
991         }
992     }
993 
994     /** Calculate fraction of current QS expansion state */
computeExpansionFraction()995     public float computeExpansionFraction() {
996         if (mAnimatingHiddenFromCollapsed) {
997             // When hiding QS from collapsed state, the expansion can sometimes temporarily
998             // be larger than 0 because of the timing, leading to flickers.
999             return 0.0f;
1000         }
1001         return Math.min(
1002                 1f, (mExpansionHeight - mMinExpansionHeight) / (mMaxExpansionHeight
1003                         - mMinExpansionHeight));
1004     }
1005 
updateMinHeight()1006     void updateMinHeight() {
1007         float previousMin = mMinExpansionHeight;
1008         if (mBarState == KEYGUARD || mSplitShadeEnabled) {
1009             mMinExpansionHeight = 0;
1010         } else {
1011             mMinExpansionHeight = mQs.getQsMinExpansionHeight();
1012         }
1013         if (mExpansionHeight == previousMin) {
1014             mExpansionHeight = mMinExpansionHeight;
1015         }
1016     }
1017 
updateQsFrameTranslation()1018     void updateQsFrameTranslation() {
1019         // TODO (b/265193930): remove dependency on NPVC
1020         mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs,
1021                 mPanelViewControllerLazy.get().getNavigationBarBottomHeight()
1022                         + mAmbientState.getStackTopMargin());
1023     }
1024 
1025     /** Called when shade starts expanding. */
onExpandingStarted(boolean qsFullyExpanded)1026     public void onExpandingStarted(boolean qsFullyExpanded) {
1027         mNotificationStackScrollLayoutController.onExpansionStarted();
1028         mExpandedWhenExpandingStarted = qsFullyExpanded;
1029         mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
1030                 /* We also start expanding when flinging closed Qs. Let's exclude that */
1031                 && !mAnimating);
1032         if (mExpanded) {
1033             onExpansionStarted();
1034         }
1035         // Since there are QS tiles in the header now, we need to make sure we start listening
1036         // immediately so they can be up to date.
1037         if (mQs == null) return;
1038         mQs.setHeaderListening(true);
1039     }
1040 
1041     /** Set animate next notification bounds. */
setAnimateNextNotificationBounds(long duration, long delay)1042     private void setAnimateNextNotificationBounds(long duration, long delay) {
1043         mAnimateNextNotificationBounds = true;
1044         mNotificationBoundsAnimationDuration = duration;
1045         mNotificationBoundsAnimationDelay = delay;
1046     }
1047 
1048     /**
1049      * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping
1050      * as well based on the bounds of the shade and QS state.
1051      */
setClippingBounds()1052     private void setClippingBounds() {
1053         float qsExpansionFraction = computeExpansionFraction();
1054         final int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
1055         // Split shade has no QQS
1056         final boolean qqsVisible =
1057                 !mSplitShadeEnabled && qsExpansionFraction == 0 && qsPanelBottomY > 0;
1058         final boolean qsVisible = qsExpansionFraction > 0;
1059         final boolean qsOrQqsVisible = qqsVisible || qsVisible;
1060         checkCorrectScrimVisibility(qsExpansionFraction);
1061 
1062         int top = calculateTopClippingBound(qsPanelBottomY);
1063         int bottom = calculateBottomClippingBound(top);
1064         int left = calculateLeftClippingBound();
1065         int right = calculateRightClippingBound();
1066         // top should never be lower than bottom, otherwise it will be invisible.
1067         top = Math.min(top, bottom);
1068         applyClippingBounds(left, top, right, bottom, qsOrQqsVisible);
1069     }
1070 
1071     /**
1072      * Applies clipping to quick settings, notifications layout and
1073      * updates bounds of the notifications background (notifications scrim).
1074      *
1075      * The parameters are bounds of the notifications area rectangle, this function
1076      * calculates bounds for the QS clipping based on the notifications bounds.
1077      */
applyClippingBounds(int left, int top, int right, int bottom, boolean qsVisible)1078     private void applyClippingBounds(int left, int top, int right, int bottom,
1079             boolean qsVisible) {
1080         if (!mAnimateNextNotificationBounds || mLastClipBounds.isEmpty()) {
1081             if (mClippingAnimator != null) {
1082                 // update the end position of the animator
1083                 mClippingAnimationEndBounds.set(left, top, right, bottom);
1084             } else {
1085                 applyClippingImmediately(left, top, right, bottom, qsVisible);
1086             }
1087         } else {
1088             mClippingAnimationEndBounds.set(left, top, right, bottom);
1089             final int startLeft = mLastClipBounds.left;
1090             final int startTop = mLastClipBounds.top;
1091             final int startRight = mLastClipBounds.right;
1092             final int startBottom = mLastClipBounds.bottom;
1093             if (mClippingAnimator != null) {
1094                 mClippingAnimator.cancel();
1095             }
1096             mClippingAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
1097             mClippingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
1098             mClippingAnimator.setDuration(mNotificationBoundsAnimationDuration);
1099             mClippingAnimator.setStartDelay(mNotificationBoundsAnimationDelay);
1100             mClippingAnimator.addUpdateListener(animation -> {
1101                 float fraction = animation.getAnimatedFraction();
1102                 int animLeft = (int) MathUtils.lerp(startLeft,
1103                         mClippingAnimationEndBounds.left, fraction);
1104                 int animTop = (int) MathUtils.lerp(startTop,
1105                         mClippingAnimationEndBounds.top, fraction);
1106                 logClippingTopBound("interpolated top bound", top);
1107                 int animRight = (int) MathUtils.lerp(startRight,
1108                         mClippingAnimationEndBounds.right, fraction);
1109                 int animBottom = (int) MathUtils.lerp(startBottom,
1110                         mClippingAnimationEndBounds.bottom, fraction);
1111                 applyClippingImmediately(animLeft, animTop, animRight, animBottom,
1112                         qsVisible /* qsVisible */);
1113             });
1114             mClippingAnimator.addListener(new AnimatorListenerAdapter() {
1115                 @Override
1116                 public void onAnimationEnd(Animator animation) {
1117                     mClippingAnimator = null;
1118                     mIsTranslationResettingAnimator = false;
1119                     mIsPulseExpansionResettingAnimator = false;
1120                 }
1121             });
1122             mClippingAnimator.start();
1123         }
1124         mAnimateNextNotificationBounds = false;
1125         mNotificationBoundsAnimationDelay = 0;
1126     }
1127 
applyClippingImmediately(int left, int top, int right, int bottom, boolean qsVisible)1128     private void applyClippingImmediately(int left, int top, int right, int bottom,
1129             boolean qsVisible) {
1130         int radius = mScrimCornerRadius;
1131         boolean clipStatusView = false;
1132         mLastClipBounds.set(left, top, right, bottom);
1133         if (mIsFullWidth) {
1134             clipStatusView = qsVisible;
1135             float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius;
1136             radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius,
1137                     Math.min(top / (float) mScrimCornerRadius, 1f));
1138         }
1139         if (isQsFragmentCreated()) {
1140             float qsTranslation = 0;
1141             boolean pulseExpanding = mPulseExpansionHandler.isExpanding();
1142             if (mTransitioningToFullShadeProgress > 0.0f
1143                     || pulseExpanding || (mClippingAnimator != null
1144                     && (mIsTranslationResettingAnimator || mIsPulseExpansionResettingAnimator))) {
1145                 if (pulseExpanding || mIsPulseExpansionResettingAnimator) {
1146                     // qsTranslation should only be positive during pulse expansion because it's
1147                     // already translating in from the top
1148                     qsTranslation = Math.max(0, (top - getHeaderHeight()) / 2.0f);
1149                 } else if (!mSplitShadeEnabled) {
1150                     qsTranslation = (top - getHeaderHeight()) * QS_PARALLAX_AMOUNT;
1151                 }
1152             }
1153             mTranslationForFullShadeTransition = qsTranslation;
1154             updateQsFrameTranslation();
1155             float currentTranslation = mQsFrame.getTranslationY();
1156             int clipTop = mEnableClipping
1157                     ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0;
1158             int clipBottom = mEnableClipping
1159                     ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0;
1160             mVisible = qsVisible;
1161             mQs.setQsVisible(qsVisible);
1162             mQs.setFancyClipping(
1163                     clipTop,
1164                     clipBottom,
1165                     radius,
1166                     qsVisible && !mSplitShadeEnabled);
1167 
1168         }
1169 
1170         // Increase the height of the notifications scrim when not in split shade
1171         // (e.g. portrait tablet) so the rounded corners are not visible at the bottom,
1172         // in this case they are rendered off-screen
1173         final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius;
1174         mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom);
1175 
1176         if (mApplyClippingImmediatelyListener != null) {
1177             mApplyClippingImmediatelyListener.onQsClippingImmediatelyApplied(clipStatusView,
1178                     mLastClipBounds, top, isQsFragmentCreated(), mVisible);
1179         }
1180 
1181         mScrimController.setScrimCornerRadius(radius);
1182 
1183         // Convert global clipping coordinates to local ones,
1184         // relative to NotificationStackScrollLayout
1185         int nsslLeft = calculateNsslLeft(left);
1186         int nsslRight = calculateNsslRight(right);
1187         int nsslTop = getNotificationsClippingTopBounds(top);
1188         int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
1189         int bottomRadius = mSplitShadeEnabled ? radius : 0;
1190         // TODO (b/265193930): remove dependency on NPVC
1191         int topRadius = mSplitShadeEnabled
1192                 && mPanelViewControllerLazy.get().isExpandingFromHeadsUp() ? 0 : radius;
1193         mNotificationStackScrollLayoutController.setRoundedClippingBounds(
1194                 nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
1195     }
1196 
setDisplayInsets(int leftInset, int rightInset)1197     void setDisplayInsets(int leftInset, int rightInset) {
1198         mDisplayLeftInset = leftInset;
1199         mDisplayRightInset = rightInset;
1200     }
1201 
calculateNsslLeft(int nsslLeftAbsolute)1202     private int calculateNsslLeft(int nsslLeftAbsolute) {
1203         int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft();
1204         if (mIsFullWidth) {
1205             return left;
1206         }
1207         return left - mDisplayLeftInset;
1208     }
1209 
calculateNsslRight(int nsslRightAbsolute)1210     private int calculateNsslRight(int nsslRightAbsolute) {
1211         int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft();
1212         if (mIsFullWidth) {
1213             return right;
1214         }
1215         return right - mDisplayLeftInset;
1216     }
1217 
getNotificationsClippingTopBounds(int qsTop)1218     private int getNotificationsClippingTopBounds(int qsTop) {
1219         // TODO (b/265193930): remove dependency on NPVC
1220         if (mSplitShadeEnabled && mPanelViewControllerLazy.get().isExpandingFromHeadsUp()) {
1221             // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
1222             // to set top clipping bound to negative value to allow HUN to go up to the top edge of
1223             // the screen without clipping.
1224             return -mAmbientState.getStackTopMargin();
1225         } else {
1226             logNotificationsClippingTopBound(qsTop,
1227                     mNotificationStackScrollLayoutController.getTop());
1228             return qsTop - mNotificationStackScrollLayoutController.getTop();
1229         }
1230     }
1231 
checkCorrectScrimVisibility(float expansionFraction)1232     private void checkCorrectScrimVisibility(float expansionFraction) {
1233         // issues with scrims visible on keyguard occur only in split shade
1234         if (mSplitShadeEnabled) {
1235             // TODO (b/265193930): remove dependency on NPVC
1236             boolean keyguardViewsVisible = mBarState == KEYGUARD
1237                             && mPanelViewControllerLazy.get().getKeyguardOnlyContentAlpha() == 1;
1238             // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
1239             // on QS expansion
1240             if (expansionFraction == 1 && keyguardViewsVisible) {
1241                 Log.wtf(TAG,
1242                         "Incorrect state, scrim is visible at the same time when clock is visible");
1243             }
1244         }
1245     }
1246 
1247     /** Calculate top padding for notifications */
calculateNotificationsTopPadding(boolean isShadeExpanding, int keyguardNotificationStaticPadding, float expandedFraction)1248     public float calculateNotificationsTopPadding(boolean isShadeExpanding,
1249             int keyguardNotificationStaticPadding, float expandedFraction) {
1250         float topPadding;
1251         boolean keyguardShowing = mBarState == KEYGUARD;
1252         if (mSplitShadeEnabled) {
1253             return keyguardShowing
1254                     ? keyguardNotificationStaticPadding : 0;
1255         }
1256         if (keyguardShowing && (isExpandImmediate()
1257                 || isShadeExpanding && getExpandedWhenExpandingStarted())) {
1258 
1259             // Either QS pushes the notifications down when fully expanded, or QS is fully above the
1260             // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
1261             // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
1262             // panel. We need to take the maximum and linearly interpolate with the panel expansion
1263             // for a nice motion.
1264             int maxQsPadding = getMaxExpansionHeight();
1265             int max = keyguardShowing ? Math.max(
1266                     keyguardNotificationStaticPadding, maxQsPadding) : maxQsPadding;
1267             topPadding = (int) MathUtils.lerp((float) getMinExpansionHeight(),
1268                     (float) max, expandedFraction);
1269             logNotificationsTopPadding("keyguard and expandImmediate", topPadding);
1270             return topPadding;
1271         } else if (isSizeChangeAnimationRunning()) {
1272             topPadding = Math.max((int) mSizeChangeAnimator.getAnimatedValue(),
1273                     keyguardNotificationStaticPadding);
1274             logNotificationsTopPadding("size change animation running", topPadding);
1275             return topPadding;
1276         } else if (keyguardShowing) {
1277             // We can only do the smoother transition on Keyguard when we also are not collapsing
1278             // from a scrolled quick settings.
1279             topPadding = MathUtils.lerp((float) keyguardNotificationStaticPadding,
1280                     (float) (getMaxExpansionHeight()), computeExpansionFraction());
1281             logNotificationsTopPadding("keyguard", topPadding);
1282             return topPadding;
1283         } else {
1284             topPadding = Math.max(mQsFrameTranslateController.getNotificationsTopPadding(
1285                     mExpansionHeight, mNotificationStackScrollLayoutController),
1286                     mQuickQsHeaderHeight);
1287             logNotificationsTopPadding("default case", topPadding);
1288             return topPadding;
1289         }
1290     }
1291 
1292     /** Calculate height of QS panel */
calculatePanelHeightExpanded(int stackScrollerPadding)1293     public int calculatePanelHeightExpanded(int stackScrollerPadding) {
1294         float
1295                 notificationHeight =
1296                 mNotificationStackScrollLayoutController.getHeight()
1297                         - mNotificationStackScrollLayoutController.getEmptyBottomMargin()
1298                         - mNotificationStackScrollLayoutController.getTopPadding();
1299 
1300         // When only empty shade view is visible in QS collapsed state, simulate that we would have
1301         // it in expanded QS state as well so we don't run into troubles when fading the view in/out
1302         // and expanding/collapsing the whole panel from/to quick settings.
1303         if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0
1304                 && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) {
1305             notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight();
1306         }
1307         int maxQsHeight = mMaxExpansionHeight;
1308 
1309         // If an animation is changing the size of the QS panel, take the animated value.
1310         if (mSizeChangeAnimator != null) {
1311             maxQsHeight = (int) mSizeChangeAnimator.getAnimatedValue();
1312         }
1313         float totalHeight = Math.max(maxQsHeight, mBarState == KEYGUARD ? stackScrollerPadding : 0)
1314                 + notificationHeight
1315                 + mNotificationStackScrollLayoutController.getTopPaddingOverflow();
1316         if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) {
1317             float
1318                     fullyCollapsedHeight =
1319                     maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight();
1320             totalHeight = Math.max(fullyCollapsedHeight,
1321                     mNotificationStackScrollLayoutController.getHeight());
1322         }
1323         return (int) totalHeight;
1324     }
1325 
getEdgePosition()1326     private float getEdgePosition() {
1327         // TODO: replace StackY with unified calculation
1328         return Math.max(mQuickQsHeaderHeight * mAmbientState.getExpansionFraction(),
1329                 mAmbientState.getStackY()
1330                         // need to adjust for extra margin introduced by large screen shade header
1331                         + mAmbientState.getStackTopMargin() * mAmbientState.getExpansionFraction()
1332                         - mAmbientState.getScrollY());
1333     }
1334 
1335     /** TODO(b/273591201): remove after bug resolved */
logNotificationsTopPadding(String message, float rawPadding)1336     private void logNotificationsTopPadding(String message, float rawPadding) {
1337         int padding =  ((int) rawPadding / 10) * 10;
1338         if (mBarState != KEYGUARD && padding != mLastNotificationsTopPadding && !mExpanded) {
1339             mLastNotificationsTopPadding = padding;
1340             mShadeLog.logNotificationsTopPadding(message, padding);
1341         }
1342     }
1343 
1344     /** TODO(b/273591201): remove after bug resolved */
logClippingTopBound(String message, int top)1345     private void logClippingTopBound(String message, int top) {
1346         top = (top / 10) * 10;
1347         if (mBarState != KEYGUARD && mShadeExpandedFraction == 1
1348                 && top != mLastClippingTopBound && !mExpanded) {
1349             mLastClippingTopBound = top;
1350             mShadeLog.logClippingTopBound(message, top);
1351         }
1352     }
1353 
1354     /** TODO(b/273591201): remove after bug resolved */
logNotificationsClippingTopBound(int top, int nsslTop)1355     private void logNotificationsClippingTopBound(int top, int nsslTop) {
1356         top = (top / 10) * 10;
1357         nsslTop = (nsslTop / 10) * 10;
1358         if (mBarState == SHADE && mShadeExpandedFraction == 1
1359                 && (top != mLastNotificationsClippingTopBound
1360                 || nsslTop != mLastNotificationsClippingTopBoundNssl) && !mExpanded) {
1361             mLastNotificationsClippingTopBound = top;
1362             mLastNotificationsClippingTopBoundNssl = nsslTop;
1363             mShadeLog.logNotificationsClippingTopBound(top, nsslTop);
1364         }
1365     }
1366 
calculateTopClippingBound(int qsPanelBottomY)1367     private int calculateTopClippingBound(int qsPanelBottomY) {
1368         int top;
1369         if (mSplitShadeEnabled) {
1370             top = Math.min(qsPanelBottomY, mLargeScreenShadeHeaderHeight);
1371         } else {
1372             if (mTransitioningToFullShadeProgress > 0.0f) {
1373                 // If we're transitioning, let's use the actual value. The else case
1374                 // can be wrong during transitions when waiting for the keyguard to unlock
1375                 top = mTransitionToFullShadePosition;
1376                 logClippingTopBound("set while transitioning to full shade", top);
1377             } else {
1378                 final float notificationTop = getEdgePosition();
1379                 if (mBarState == KEYGUARD) {
1380                     if (mKeyguardBypassController.getBypassEnabled()) {
1381                         // When bypassing on the keyguard, let's use the panel bottom.
1382                         // this should go away once we unify the stackY position and don't have
1383                         // to do this min anymore below.
1384                         top = qsPanelBottomY;
1385                         logClippingTopBound("bypassing keyguard", top);
1386                     } else {
1387                         top = (int) Math.min(qsPanelBottomY, notificationTop);
1388                         logClippingTopBound("keyguard default case", top);
1389                     }
1390                 } else {
1391                     top = (int) notificationTop;
1392                 }
1393             }
1394             // TODO (b/265193930): remove dependency on NPVC
1395             top += mPanelViewControllerLazy.get().getOverStretchAmount();
1396             logClippingTopBound("including overstretch", top);
1397             // Correction for instant expansion caused by HUN pull down/
1398             float minFraction = mPanelViewControllerLazy.get().getMinFraction();
1399             if (minFraction > 0f && minFraction < 1f) {
1400                 float realFraction = (mShadeExpandedFraction
1401                         - minFraction) / (1f - minFraction);
1402                 top *= MathUtils.saturate(realFraction / minFraction);
1403                 logClippingTopBound("after adjusted fraction", top);
1404             }
1405         }
1406         return top;
1407     }
1408 
calculateBottomClippingBound(int top)1409     private int calculateBottomClippingBound(int top) {
1410         if (mSplitShadeEnabled) {
1411             return top + mNotificationStackScrollLayoutController.getHeight()
1412                     + mSplitShadeNotificationsScrimMarginBottom;
1413         } else {
1414             return mPanelView.getBottom();
1415         }
1416     }
1417 
calculateLeftClippingBound()1418     private int calculateLeftClippingBound() {
1419         if (mIsFullWidth) {
1420             // left bounds can ignore insets, it should always reach the edge of the screen
1421             return 0;
1422         } else {
1423             return mNotificationStackScrollLayoutController.getLeft()
1424                     + mDisplayLeftInset;
1425         }
1426     }
1427 
calculateRightClippingBound()1428     private int calculateRightClippingBound() {
1429         if (mIsFullWidth) {
1430             return mPanelView.getRight()
1431                     + mDisplayRightInset;
1432         } else {
1433             return mNotificationStackScrollLayoutController.getRight()
1434                     + mDisplayLeftInset;
1435         }
1436     }
1437 
trackMovement(MotionEvent event)1438     private void trackMovement(MotionEvent event) {
1439         if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
1440     }
1441 
initVelocityTracker()1442     private void initVelocityTracker() {
1443         if (mQsVelocityTracker != null) {
1444             mQsVelocityTracker.recycle();
1445         }
1446         mQsVelocityTracker = VelocityTracker.obtain();
1447     }
1448 
getCurrentVelocity()1449     private float getCurrentVelocity() {
1450         if (mQsVelocityTracker == null) {
1451             return 0;
1452         }
1453         mQsVelocityTracker.computeCurrentVelocity(1000);
1454         return mQsVelocityTracker.getYVelocity();
1455     }
1456 
updateAndGetTouchAboveFalsingThreshold()1457     boolean updateAndGetTouchAboveFalsingThreshold() {
1458         mTouchAboveFalsingThreshold = mFullyExpanded;
1459         return mTouchAboveFalsingThreshold;
1460     }
1461 
1462     @VisibleForTesting
onHeightChanged()1463     void onHeightChanged() {
1464         mMaxExpansionHeight = isQsFragmentCreated() ? mQs.getDesiredHeight() : 0;
1465         if (mExpanded && mFullyExpanded) {
1466             mExpansionHeight = mMaxExpansionHeight;
1467             if (mExpansionHeightSetToMaxListener != null) {
1468                 mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true);
1469             }
1470         }
1471         if (mAccessibilityManager.isEnabled()) {
1472             // TODO (b/265193930): remove dependency on NPVC
1473             mPanelView.setAccessibilityPaneTitle(
1474                     mPanelViewControllerLazy.get().determineAccessibilityPaneTitle());
1475         }
1476         mNotificationStackScrollLayoutController.setMaxTopPadding(mMaxExpansionHeight);
1477     }
1478 
collapseOrExpandQs()1479     private void collapseOrExpandQs() {
1480         if (mSplitShadeEnabled) {
1481             return; // QS is always expanded in split shade
1482         }
1483         onExpansionStarted();
1484         if (getExpanded()) {
1485             flingQs(0, FLING_COLLAPSE, null, true);
1486         } else if (isExpansionEnabled()) {
1487             mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
1488             flingQs(0, FLING_EXPAND, null, true);
1489         }
1490     }
1491 
onScroll(int scrollY)1492     private void onScroll(int scrollY) {
1493         mShadeHeaderController.setQsScrollY(scrollY);
1494         if (scrollY > 0 && !mFullyExpanded) {
1495             // TODO (b/265193930): remove dependency on NPVC
1496             // If we are scrolling QS, we should be fully expanded.
1497             mPanelViewControllerLazy.get().expandWithQs();
1498         }
1499     }
1500 
1501     @VisibleForTesting
isTrackingBlocked()1502     boolean isTrackingBlocked() {
1503         return mConflictingExpansionGesture && getExpanded();
1504     }
1505 
isExpansionAnimating()1506     boolean isExpansionAnimating() {
1507         return mExpansionAnimator != null;
1508     }
1509 
1510     @VisibleForTesting
isConflictingExpansionGesture()1511     boolean isConflictingExpansionGesture() {
1512         return mConflictingExpansionGesture;
1513     }
1514 
1515     /** handles touches in Qs panel area */
handleTouch(MotionEvent event, boolean isFullyCollapsed, boolean isShadeOrQsHeightAnimationRunning)1516     public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
1517             boolean isShadeOrQsHeightAnimationRunning) {
1518         if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
1519             return false;
1520         }
1521         final int action = event.getActionMasked();
1522         boolean collapsedQs = !getExpanded() && !mSplitShadeEnabled;
1523         boolean expandedShadeCollapsedQs = mShadeExpandedFraction == 1f
1524                 && mBarState != KEYGUARD && collapsedQs && isExpansionEnabled();
1525         if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) {
1526             // Down in the empty area while fully expanded - go to QS.
1527             mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled");
1528             mTracking = true;
1529             traceQsJank(true, false);
1530             mConflictingExpansionGesture = true;
1531             onExpansionStarted();
1532             mInitialHeightOnTouch = mExpansionHeight;
1533             mInitialTouchY = event.getY();
1534             mInitialTouchX = event.getX();
1535         }
1536         if (!isFullyCollapsed && !isShadeOrQsHeightAnimationRunning) {
1537             handleDown(event);
1538         }
1539         // defer touches on QQS to shade while shade is collapsing. Added margin for error
1540         // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS.
1541         if (!mSplitShadeEnabled && !mLastShadeFlingWasExpanding
1542                 && computeExpansionFraction() <= 0.01 && mShadeExpandedFraction < 1.0) {
1543             mShadeLog.logMotionEvent(event,
1544                     "handleQsTouch: shade touched while shade collapsing, QS tracking disabled");
1545             mTracking = false;
1546         }
1547         if (!isExpandImmediate() && mTracking) {
1548             onTouch(event);
1549             if (!mConflictingExpansionGesture && !mSplitShadeEnabled) {
1550                 mShadeLog.logMotionEvent(event,
1551                         "handleQsTouch: not immediate expand or conflicting gesture");
1552                 return true;
1553             }
1554         }
1555         if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1556             mConflictingExpansionGesture = false;
1557         }
1558         if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed && isExpansionEnabled()) {
1559             mTwoFingerExpandPossible = true;
1560         }
1561         if (mTwoFingerExpandPossible && isOpenQsEvent(event)
1562                 && event.getY(event.getActionIndex())
1563                 < mStatusBarMinHeight) {
1564             mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
1565             setExpandImmediate(true);
1566             mNotificationStackScrollLayoutController.setShouldShowShelfOnly(!mSplitShadeEnabled);
1567             if (mExpansionHeightSetToMaxListener != null) {
1568                 mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(false);
1569             }
1570 
1571             // Normally, we start listening when the panel is expanded, but here we need to start
1572             // earlier so the state is already up to date when dragging down.
1573             setListening(true);
1574         }
1575         return false;
1576     }
1577 
handleDown(MotionEvent event)1578     private void handleDown(MotionEvent event) {
1579         if (event.getActionMasked() == MotionEvent.ACTION_DOWN
1580                 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
1581             mFalsingCollector.onQsDown();
1582             mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
1583             mTracking = true;
1584             onExpansionStarted();
1585             mInitialHeightOnTouch = mExpansionHeight;
1586             mInitialTouchY = event.getY();
1587             mInitialTouchX = event.getX();
1588             // TODO (b/265193930): remove dependency on NPVC
1589             // If we interrupt an expansion gesture here, make sure to update the state correctly.
1590             mPanelViewControllerLazy.get().notifyExpandingFinished();
1591         }
1592     }
1593 
onTouch(MotionEvent event)1594     private void onTouch(MotionEvent event) {
1595         int pointerIndex = event.findPointerIndex(mTrackingPointer);
1596         if (pointerIndex < 0) {
1597             pointerIndex = 0;
1598             mTrackingPointer = event.getPointerId(pointerIndex);
1599         }
1600         final float y = event.getY(pointerIndex);
1601         final float x = event.getX(pointerIndex);
1602         final float h = y - mInitialTouchY;
1603 
1604         switch (event.getActionMasked()) {
1605             case MotionEvent.ACTION_DOWN:
1606                 mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled");
1607                 mTracking = true;
1608                 traceQsJank(true, false);
1609                 mInitialTouchY = y;
1610                 mInitialTouchX = x;
1611                 onExpansionStarted();
1612                 mInitialHeightOnTouch = mExpansionHeight;
1613                 initVelocityTracker();
1614                 trackMovement(event);
1615                 break;
1616 
1617             case MotionEvent.ACTION_POINTER_UP:
1618                 final int upPointer = event.getPointerId(event.getActionIndex());
1619                 if (mTrackingPointer == upPointer) {
1620                     // gesture is ongoing, find a new pointer to track
1621                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
1622                     final float newY = event.getY(newIndex);
1623                     final float newX = event.getX(newIndex);
1624                     mTrackingPointer = event.getPointerId(newIndex);
1625                     mInitialHeightOnTouch = mExpansionHeight;
1626                     mInitialTouchY = newY;
1627                     mInitialTouchX = newX;
1628                 }
1629                 break;
1630 
1631             case MotionEvent.ACTION_MOVE:
1632                 mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
1633                 setExpansionHeight(h + mInitialHeightOnTouch);
1634                 // TODO (b/265193930): remove dependency on NPVC
1635                 if (h >= mPanelViewControllerLazy.get().getFalsingThreshold()) {
1636                     mTouchAboveFalsingThreshold = true;
1637                 }
1638                 trackMovement(event);
1639                 break;
1640 
1641             case MotionEvent.ACTION_UP:
1642             case MotionEvent.ACTION_CANCEL:
1643                 mShadeLog.logMotionEvent(event,
1644                         "onQsTouch: up/cancel action, QS tracking disabled");
1645                 mTracking = false;
1646                 mTrackingPointer = -1;
1647                 trackMovement(event);
1648                 float fraction = computeExpansionFraction();
1649                 if (fraction != 0f || y >= mInitialTouchY) {
1650                     flingQsWithCurrentVelocity(y,
1651                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
1652                 } else {
1653                     traceQsJank(false,
1654                             event.getActionMasked() == MotionEvent.ACTION_CANCEL);
1655                 }
1656                 if (mQsVelocityTracker != null) {
1657                     mQsVelocityTracker.recycle();
1658                     mQsVelocityTracker = null;
1659                 }
1660                 break;
1661         }
1662     }
1663 
1664     /** intercepts touches on Qs panel area. */
onIntercept(MotionEvent event)1665     public boolean onIntercept(MotionEvent event) {
1666         int pointerIndex = event.findPointerIndex(mTrackingPointer);
1667         if (pointerIndex < 0) {
1668             pointerIndex = 0;
1669             mTrackingPointer = event.getPointerId(pointerIndex);
1670         }
1671         final float x = event.getX(pointerIndex);
1672         final float y = event.getY(pointerIndex);
1673 
1674         switch (event.getActionMasked()) {
1675             case MotionEvent.ACTION_DOWN:
1676                 mInitialTouchY = y;
1677                 mInitialTouchX = x;
1678                 initVelocityTracker();
1679                 trackMovement(event);
1680                 float qsExpansionFraction = computeExpansionFraction();
1681                 // Intercept the touch if QS is between fully collapsed and fully expanded state
1682                 if (!mSplitShadeEnabled
1683                         && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) {
1684                     mShadeLog.logMotionEvent(event,
1685                             "onQsIntercept: down action, QS partially expanded/collapsed");
1686                     return true;
1687                 }
1688                 // TODO (b/265193930): remove dependency on NPVC
1689                 if (mPanelViewControllerLazy.get().getKeyguardShowing()
1690                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
1691                     // Dragging down on the lockscreen statusbar should prohibit other interactions
1692                     // immediately, otherwise we'll wait on the touchslop. This is to allow
1693                     // dragging down to expanded quick settings directly on the lockscreen.
1694                     mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
1695                 }
1696                 if (mExpansionAnimator != null) {
1697                     mInitialHeightOnTouch = mExpansionHeight;
1698                     mShadeLog.logMotionEvent(event,
1699                             "onQsIntercept: down action, QS tracking enabled");
1700                     mTracking = true;
1701                     traceQsJank(true, false);
1702                     mNotificationStackScrollLayoutController.cancelLongPress();
1703                 }
1704                 break;
1705             case MotionEvent.ACTION_POINTER_UP:
1706                 final int upPointer = event.getPointerId(event.getActionIndex());
1707                 if (mTrackingPointer == upPointer) {
1708                     // gesture is ongoing, find a new pointer to track
1709                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
1710                     mTrackingPointer = event.getPointerId(newIndex);
1711                     mInitialTouchX = event.getX(newIndex);
1712                     mInitialTouchY = event.getY(newIndex);
1713                 }
1714                 break;
1715 
1716             case MotionEvent.ACTION_MOVE:
1717                 final float h = y - mInitialTouchY;
1718                 trackMovement(event);
1719                 if (mTracking) {
1720 
1721                     // Already tracking because onOverscrolled was called. We need to update here
1722                     // so we don't stop for a frame until the next touch event gets handled in
1723                     // onTouchEvent.
1724                     setExpansionHeight(h + mInitialHeightOnTouch);
1725                     trackMovement(event);
1726                     return true;
1727                 } else {
1728                     mShadeLog.logMotionEvent(event,
1729                             "onQsIntercept: move ignored because qs tracking disabled");
1730                 }
1731                 // TODO (b/265193930): remove dependency on NPVC
1732                 float touchSlop = event.getClassification()
1733                         == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE
1734                         ? mTouchSlop * mSlopMultiplier
1735                         : mTouchSlop;
1736                 if ((h > touchSlop || (h < -touchSlop && getExpanded()))
1737                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
1738                         && shouldQuickSettingsIntercept(
1739                         mInitialTouchX, mInitialTouchY, h)) {
1740                     mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
1741                     mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
1742                     mTracking = true;
1743                     traceQsJank(true, false);
1744                     onExpansionStarted();
1745                     mPanelViewControllerLazy.get().notifyExpandingFinished();
1746                     mInitialHeightOnTouch = mExpansionHeight;
1747                     mInitialTouchY = y;
1748                     mInitialTouchX = x;
1749                     mNotificationStackScrollLayoutController.cancelLongPress();
1750                     return true;
1751                 } else {
1752                     mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop,
1753                             getExpanded(), mPanelViewControllerLazy.get().getKeyguardShowing(),
1754                             isExpansionEnabled());
1755                 }
1756                 break;
1757 
1758             case MotionEvent.ACTION_CANCEL:
1759             case MotionEvent.ACTION_UP:
1760                 trackMovement(event);
1761                 mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled");
1762                 mTracking = false;
1763                 break;
1764         }
1765         return false;
1766     }
1767 
1768     /**
1769      * Animate QS closing by flinging it.
1770      * If QS is expanded, it will collapse into QQS and stop.
1771      * If in split shade, it will collapse the whole shade.
1772      *
1773      * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
1774      */
animateCloseQs(boolean animateAway)1775     public void animateCloseQs(boolean animateAway) {
1776         if (mExpansionAnimator != null) {
1777             if (!mAnimatorExpand) {
1778                 return;
1779             }
1780             float height = mExpansionHeight;
1781             mExpansionAnimator.cancel();
1782             setExpansionHeight(height);
1783         }
1784         flingQs(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
1785     }
1786 
cancelExpansionAnimation()1787     private void cancelExpansionAnimation() {
1788         if (mExpansionAnimator != null) {
1789             mExpansionAnimator.cancel();
1790         }
1791     }
1792 
1793     /** @see #flingQs(float, int, Runnable, boolean) */
flingQs(float vel, int type)1794     public void flingQs(float vel, int type) {
1795         flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */);
1796     }
1797 
1798     /**
1799      * Animates QS or QQS as if the user had swiped up or down.
1800      *
1801      * @param vel              Finger velocity or 0 when not initiated by touch events.
1802      * @param type             Either FLING_EXPAND, FLING_COLLAPSE or FLING_HIDE.
1803      * @param onFinishRunnable Runnable to be executed at the end of animation.
1804      * @param isClick          If originated by click (different interpolator and duration.)
1805      */
flingQs(float vel, int type, final Runnable onFinishRunnable, boolean isClick)1806     private void flingQs(float vel, int type, final Runnable onFinishRunnable,
1807             boolean isClick) {
1808         mShadeLog.flingQs(type, isClick);
1809         float target;
1810         switch (type) {
1811             case FLING_EXPAND:
1812                 target = getMaxExpansionHeight();
1813                 break;
1814             case FLING_COLLAPSE:
1815                 if (mSplitShadeEnabled) { // TODO:(b/269742565) remove below log
1816                     Log.wtfStack(TAG, "FLING_COLLAPSE called in split shade");
1817                 }
1818                 target = getMinExpansionHeight();
1819                 break;
1820             case FLING_HIDE:
1821             default:
1822                 if (isQsFragmentCreated()) {
1823                     mQs.closeDetail();
1824                 }
1825                 target = 0;
1826         }
1827         if (target == mExpansionHeight) {
1828             if (onFinishRunnable != null) {
1829                 onFinishRunnable.run();
1830             }
1831             traceQsJank(false, type != FLING_EXPAND);
1832             return;
1833         }
1834 
1835         // If we move in the opposite direction, reset velocity and use a different duration.
1836         boolean oppositeDirection = false;
1837         boolean expanding = type == FLING_EXPAND;
1838         if (vel > 0 && !expanding || vel < 0 && expanding) {
1839             vel = 0;
1840             oppositeDirection = true;
1841         }
1842         ValueAnimator animator = ValueAnimator.ofFloat(
1843                 mExpansionHeight, target);
1844         if (isClick) {
1845             animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
1846             animator.setDuration(368);
1847         } else {
1848             if (mFlingQsWithoutClickListener != null) {
1849                 mFlingQsWithoutClickListener.onFlingQsWithoutClick(animator, mExpansionHeight,
1850                         target, vel);
1851             }
1852         }
1853         if (oppositeDirection) {
1854             animator.setDuration(350);
1855         }
1856         animator.addUpdateListener(
1857                 animation -> setExpansionHeight((Float) animation.getAnimatedValue()));
1858         animator.addListener(new AnimatorListenerAdapter() {
1859             private boolean mIsCanceled;
1860 
1861             @Override
1862             public void onAnimationStart(Animator animation) {
1863                 mPanelViewControllerLazy.get().notifyExpandingStarted();
1864             }
1865 
1866             @Override
1867             public void onAnimationCancel(Animator animation) {
1868                 mIsCanceled = true;
1869             }
1870 
1871             @Override
1872             public void onAnimationEnd(Animator animation) {
1873                 mAnimatingHiddenFromCollapsed = false;
1874                 mAnimating = false;
1875                 mPanelViewControllerLazy.get().notifyExpandingFinished();
1876                 mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind();
1877                 mExpansionAnimator = null;
1878                 if (onFinishRunnable != null) {
1879                     onFinishRunnable.run();
1880                 }
1881                 traceQsJank(false, mIsCanceled);
1882             }
1883         });
1884         // Let's note that we're animating QS. Moving the animator here will cancel it immediately,
1885         // so we need a separate flag.
1886         mAnimating = true;
1887         animator.start();
1888         mExpansionAnimator = animator;
1889         mAnimatorExpand = expanding;
1890         mAnimatingHiddenFromCollapsed =
1891                 computeExpansionFraction() == 0.0f && target == 0;
1892     }
1893 
flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent)1894     private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
1895         float vel = getCurrentVelocity();
1896         // TODO (b/265193930): remove dependency on NPVC
1897         boolean expandsQs = mPanelViewControllerLazy.get().flingExpandsQs(vel);
1898         if (expandsQs) {
1899             if (mFalsingManager.isUnlockingDisabled() || isQsFalseTouch()) {
1900                 expandsQs = false;
1901             } else {
1902                 logQsSwipeDown(y);
1903             }
1904         } else if (vel < 0) {
1905             mFalsingManager.isFalseTouch(QS_COLLAPSE);
1906         }
1907 
1908         int flingType;
1909         if (expandsQs && !isCancelMotionEvent) {
1910             flingType = FLING_EXPAND;
1911         } else if (mSplitShadeEnabled) {
1912             flingType = FLING_HIDE;
1913         } else {
1914             flingType = FLING_COLLAPSE;
1915         }
1916         flingQs(vel, flingType);
1917     }
1918 
logQsSwipeDown(float y)1919     private void logQsSwipeDown(float y) {
1920         float vel = getCurrentVelocity();
1921         final int gesture = mBarState == KEYGUARD ? MetricsProto.MetricsEvent.ACTION_LS_QS
1922                 : MetricsProto.MetricsEvent.ACTION_SHADE_QS_PULL;
1923         // TODO (b/265193930): remove dependency on NPVC
1924         float displayDensity = mPanelViewControllerLazy.get().getDisplayDensity();
1925         mLockscreenGestureLogger.write(gesture,
1926                 (int) ((y - getInitialTouchY()) / displayDensity), (int) (vel / displayDensity));
1927     }
1928 
1929     /** */
getQsFragmentListener()1930     public FragmentHostManager.FragmentListener getQsFragmentListener() {
1931         return new QsFragmentListener();
1932     }
1933 
1934     /** */
1935     public final class QsFragmentListener implements FragmentHostManager.FragmentListener {
1936         /** */
1937         @Override
onFragmentViewCreated(String tag, Fragment fragment)1938         public void onFragmentViewCreated(String tag, Fragment fragment) {
1939             mQs = (QS) fragment;
1940             mQs.setPanelView(mQsHeightListener);
1941             mQs.setCollapseExpandAction(mQsCollapseExpandAction);
1942             mQs.setHeaderClickable(isExpansionEnabled());
1943             mQs.setOverscrolling(mStackScrollerOverscrolling);
1944             mQs.setInSplitShade(mSplitShadeEnabled);
1945             mQs.setIsNotificationPanelFullWidth(mIsFullWidth);
1946 
1947             // recompute internal state when qspanel height changes
1948             mQs.getView().addOnLayoutChangeListener(
1949                     (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
1950                         final int height = bottom - top;
1951                         final int oldHeight = oldBottom - oldTop;
1952                         if (height != oldHeight) {
1953                             onHeightChanged();
1954                         }
1955                     });
1956             mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
1957                 if (mQs.getHeader().isShown()) {
1958                     setAnimateNextNotificationBounds(
1959                             StackStateAnimator.ANIMATION_DURATION_STANDARD, 0);
1960                     mNotificationStackScrollLayoutController.animateNextTopPaddingChange();
1961                 }
1962             });
1963             mLockscreenShadeTransitionController.setQS(mQs);
1964             mShadeTransitionController.setQs(mQs);
1965             mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
1966             mQs.setScrollListener(mQsScrollListener);
1967             updateExpansion();
1968         }
1969 
1970         /** */
1971         @Override
onFragmentViewDestroyed(String tag, Fragment fragment)1972         public void onFragmentViewDestroyed(String tag, Fragment fragment) {
1973             // Manual handling of fragment lifecycle is only required because this bridges
1974             // non-fragment and fragment code. Once we are using a fragment for the notification
1975             // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
1976             if (fragment == mQs) {
1977                 mQs = null;
1978             }
1979         }
1980     }
1981 
1982     private final class LockscreenShadeTransitionCallback
1983             implements LockscreenShadeTransitionController.Callback {
1984         /** Called when pulse expansion has finished and this is going to the full shade. */
1985         @Override
onPulseExpansionFinished()1986         public void onPulseExpansionFinished() {
1987             setAnimateNextNotificationBounds(
1988                     StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0);
1989             mIsPulseExpansionResettingAnimator = true;
1990         }
1991 
1992         @Override
setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay)1993         public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
1994             if (animate && mIsFullWidth) {
1995                 setAnimateNextNotificationBounds(
1996                         StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, delay);
1997                 mIsTranslationResettingAnimator = mTranslationForFullShadeTransition > 0.0f;
1998             }
1999             float endPosition = 0;
2000             if (pxAmount > 0.0f) {
2001                 if (mSplitShadeEnabled) {
2002                     float qsHeight = MathUtils.lerp(getMinExpansionHeight(),
2003                             getMaxExpansionHeight(),
2004                             mLockscreenShadeTransitionController.getQSDragProgress());
2005                     setExpansionHeight(qsHeight);
2006                 }
2007                 if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
2008                         && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
2009                     // No notifications are visible, let's animate to the height of qs instead
2010                     if (isQsFragmentCreated()) {
2011                         // Let's interpolate to the header height instead of the top padding,
2012                         // because the toppadding is way too low because of the large clock.
2013                         // we still want to take into account the edgePosition though as that nicely
2014                         // overshoots in the stackscroller
2015                         endPosition = getEdgePosition()
2016                                 - mNotificationStackScrollLayoutController.getTopPadding()
2017                                 + getHeaderHeight();
2018                     }
2019                 } else {
2020                     // Interpolating to the new bottom edge position!
2021                     endPosition = getEdgePosition() + mNotificationStackScrollLayoutController
2022                             .getFullShadeTransitionInset();
2023                     if (mBarState == KEYGUARD) {
2024                         endPosition -= mLockscreenNotificationPadding;
2025                     }
2026                 }
2027             }
2028 
2029             // Calculate the overshoot amount such that we're reaching the target after our desired
2030             // distance, but only reach it fully once we drag a full shade length.
2031             mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
2032                     MathUtils.saturate(pxAmount / mDistanceForFullShadeTransition));
2033 
2034             int position = (int) MathUtils.lerp((float) 0, endPosition,
2035                     mTransitioningToFullShadeProgress);
2036             if (mTransitioningToFullShadeProgress > 0.0f) {
2037                 // we want at least 1 pixel otherwise the panel won't be clipped
2038                 position = Math.max(1, position);
2039             }
2040             mTransitionToFullShadePosition = position;
2041             updateExpansion();
2042         }
2043     }
2044 
2045     private final class NsslOverscrollTopChangedListener implements
2046             NotificationStackScrollLayout.OnOverscrollTopChangedListener {
2047         @Override
onOverscrollTopChanged(float amount, boolean isRubberbanded)2048         public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
2049             // When in split shade, overscroll shouldn't carry through to QS
2050             if (mSplitShadeEnabled) {
2051                 return;
2052             }
2053             cancelExpansionAnimation();
2054             if (!isExpansionEnabled()) {
2055                 amount = 0f;
2056             }
2057             float rounded = amount >= 1f ? amount : 0f;
2058             setOverScrolling(rounded != 0f && isRubberbanded);
2059             mExpansionFromOverscroll = rounded != 0f;
2060             mLastOverscroll = rounded;
2061             updateQsState();
2062             setExpansionHeight(getMinExpansionHeight() + rounded);
2063         }
2064 
2065         @Override
flingTopOverscroll(float velocity, boolean open)2066         public void flingTopOverscroll(float velocity, boolean open) {
2067             // in split shade mode we want to expand/collapse QS only when touch happens within QS
2068             if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) {
2069                 return;
2070             }
2071             mLastOverscroll = 0f;
2072             mExpansionFromOverscroll = false;
2073             if (open) {
2074                 // During overscrolling, qsExpansion doesn't actually change that the qs is
2075                 // becoming expanded. Any layout could therefore reset the position again. Let's
2076                 // make sure we can expand
2077                 setOverScrolling(false);
2078             }
2079             setExpansionHeight(getExpansionHeight());
2080             boolean canExpand = isExpansionEnabled();
2081             flingQs(!canExpand && open ? 0f : velocity,
2082                     open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> {
2083                         setOverScrolling(false);
2084                         updateQsState();
2085                     }, false);
2086         }
2087     }
2088 
beginJankMonitoring(boolean isFullyCollapsed)2089     void beginJankMonitoring(boolean isFullyCollapsed) {
2090         if (mInteractionJankMonitor == null) {
2091             return;
2092         }
2093         // TODO (b/265193930): remove dependency on NPVC
2094         InteractionJankMonitor.Configuration.Builder builder =
2095                 InteractionJankMonitor.Configuration.Builder.withView(
2096                         InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
2097                         mPanelView).setTag(isFullyCollapsed ? "Expand" : "Collapse");
2098         mInteractionJankMonitor.begin(builder);
2099     }
2100 
endJankMonitoring()2101     void endJankMonitoring() {
2102         if (mInteractionJankMonitor == null) {
2103             return;
2104         }
2105         InteractionJankMonitor.getInstance().end(
2106                 InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
2107     }
2108 
cancelJankMonitoring()2109     void cancelJankMonitoring() {
2110         if (mInteractionJankMonitor == null) {
2111             return;
2112         }
2113         InteractionJankMonitor.getInstance().cancel(
2114                 InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
2115     }
2116 
traceQsJank(boolean startTracing, boolean wasCancelled)2117     void traceQsJank(boolean startTracing, boolean wasCancelled) {
2118         if (mInteractionJankMonitor == null) {
2119             return;
2120         }
2121         if (startTracing) {
2122             mInteractionJankMonitor.begin(mPanelView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
2123         } else {
2124             if (wasCancelled) {
2125                 mInteractionJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
2126             } else {
2127                 mInteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE);
2128             }
2129         }
2130     }
2131 
2132     interface ExpansionHeightSetToMaxListener {
onExpansionHeightSetToMax(boolean requestPaddingUpdate)2133         void onExpansionHeightSetToMax(boolean requestPaddingUpdate);
2134     }
2135 
2136     interface ExpansionHeightListener {
onQsSetExpansionHeightCalled(boolean qsFullyExpanded)2137         void onQsSetExpansionHeightCalled(boolean qsFullyExpanded);
2138     }
2139 
2140     interface QsStateUpdateListener {
onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling)2141         void onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling);
2142     }
2143 
2144     interface ApplyClippingImmediatelyListener {
onQsClippingImmediatelyApplied(boolean clipStatusView, Rect lastQsClipBounds, int top, boolean qsFragmentCreated, boolean qsVisible)2145         void onQsClippingImmediatelyApplied(boolean clipStatusView, Rect lastQsClipBounds,
2146                 int top, boolean qsFragmentCreated, boolean qsVisible);
2147     }
2148 
2149     interface FlingQsWithoutClickListener {
onFlingQsWithoutClick(ValueAnimator animator, float qsExpansionHeight, float target, float vel)2150         void onFlingQsWithoutClick(ValueAnimator animator, float qsExpansionHeight,
2151                 float target, float vel);
2152     }
2153 }
2154