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