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