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