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