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