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