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