From 206360a69408ee095dd8db9018c4da514348ba36 Mon Sep 17 00:00:00 2001 From: Shawn Lee Date: Thu, 15 Dec 2022 21:34:25 -0800 Subject: [PATCH] Extract most QS code from NPVC Moved QS-related code from NPVC to a new class that handles QS panel expansion Bug: 254870148 Test: manual, atest Change-Id: Ic1cbdc6c02e25cd3efaea4badbf2b8835895459b --- .../android/systemui/shade/DebugDrawable.java | 18 +- .../NotificationPanelViewController.java | 2087 +++-------------- .../shade/QuickSettingsController.java | 2050 ++++++++++++++++ .../com/android/systemui/shade/ShadeLogger.kt | 14 +- .../LockscreenShadeTransitionController.kt | 33 +- .../CentralSurfacesCommandQueueCallbacks.java | 8 +- .../statusbar/phone/CentralSurfacesImpl.java | 18 +- .../phone/StatusBarNotificationPresenter.java | 6 +- .../dagger/CentralSurfacesComponent.java | 4 + .../NotificationPanelViewControllerTest.java | 253 +- .../shade/QuickSettingsControllerTest.java | 415 ++++ ...LockscreenShadeTransitionControllerTest.kt | 6 +- ...tralSurfacesCommandQueueCallbacksTest.java | 3 + .../phone/CentralSurfacesImplTest.java | 3 + .../StatusBarNotificationPresenterTest.java | 2 + 15 files changed, 2910 insertions(+), 2010 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java diff --git a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java index ae303eb6d2039..fb2ddc15bab17 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/shade/DebugDrawable.java @@ -39,6 +39,7 @@ public class DebugDrawable extends Drawable { private final NotificationPanelView mView; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockIconViewController mLockIconViewController; + private final QuickSettingsController mQsController; private final Set mDebugTextUsedYPositions; private final Paint mDebugPaint; @@ -46,12 +47,14 @@ public class DebugDrawable extends Drawable { NotificationPanelViewController notificationPanelViewController, NotificationPanelView notificationPanelView, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - LockIconViewController lockIconViewController + LockIconViewController lockIconViewController, + QuickSettingsController quickSettingsController ) { mNotificationPanelViewController = notificationPanelViewController; mView = notificationPanelView; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mLockIconViewController = lockIconViewController; + mQsController = quickSettingsController; mDebugTextUsedYPositions = new HashSet<>(); mDebugPaint = new Paint(); } @@ -71,12 +74,19 @@ public class DebugDrawable extends Drawable { Color.RED, "getMaxPanelHeight()"); drawDebugInfo(canvas, (int) mNotificationPanelViewController.getExpandedHeight(), Color.BLUE, "getExpandedHeight()"); - drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(), + drawDebugInfo(canvas, mQsController.calculatePanelHeightExpanded( + mNotificationPanelViewController.getClockPositionResult() + .stackScrollerPadding), Color.GREEN, "calculatePanelHeightQsExpanded()"); - drawDebugInfo(canvas, mNotificationPanelViewController.calculatePanelHeightQsExpanded(), + drawDebugInfo(canvas, mQsController.calculatePanelHeightExpanded( + mNotificationPanelViewController.getClockPositionResult() + .stackScrollerPadding), Color.YELLOW, "calculatePanelHeightShade()"); drawDebugInfo(canvas, - (int) mNotificationPanelViewController.calculateNotificationsTopPadding(), + (int) mQsController.calculateNotificationsTopPadding( + mNotificationPanelViewController.isExpanding(), + mNotificationPanelViewController.getKeyguardNotificationStaticPadding(), + mNotificationPanelViewController.getExpandedFraction()), Color.MAGENTA, "calculateNotificationsTopPadding()"); drawDebugInfo(canvas, mNotificationPanelViewController.getClockPositionResult().clockY, Color.GRAY, "mClockPositionResult.clockY"); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index cd45b328345f2..8a166d003bbef 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -23,14 +23,12 @@ import static android.view.View.VISIBLE; import static androidx.constraintlayout.widget.ConstraintSet.END; import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; import static com.android.systemui.classifier.Classifier.GENERIC; -import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.classifier.Classifier.UNLOCK; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; @@ -53,7 +51,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Fragment; import android.app.StatusBarManager; import android.content.ContentResolver; import android.content.res.Resources; @@ -101,10 +98,8 @@ import android.widget.FrameLayout; import androidx.constraintlayout.widget.ConstraintSet; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.policy.SystemBarUtils; import com.android.internal.util.LatencyTracker; import com.android.keyguard.ActiveUnlockConfig; @@ -135,7 +130,6 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; @@ -177,7 +171,6 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -263,13 +256,13 @@ public final class NotificationPanelViewController implements Dumpable { private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false); /** The parallax amount of the quick settings translation when dragging down the panel. */ - private static final float QS_PARALLAX_AMOUNT = 0.175f; + public static final float QS_PARALLAX_AMOUNT = 0.175f; /** Fling expanding QS. */ public static final int FLING_EXPAND = 0; /** Fling collapsing QS, potentially stopping when QS becomes QQS. */ - private static final int FLING_COLLAPSE = 1; + public static final int FLING_COLLAPSE = 1; /** Fling until QS is completely hidden. */ - private static final int FLING_HIDE = 2; + public static final int FLING_HIDE = 2; /** The delay to reset the hint text when the hint animation is finished running. */ private static final int HINT_RESET_DELAY_MS = 1200; private static final long ANIMATION_DELAY_ICON_FADE_IN = @@ -291,7 +284,7 @@ public final class NotificationPanelViewController implements Dumpable { private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300; private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50; private static final String COUNTER_PANEL_OPEN = "panel_open"; - private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; + public static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1); private static final Rect EMPTY_RECT = new Rect(); @@ -310,14 +303,10 @@ public final class NotificationPanelViewController implements Dumpable { private final SystemClock mSystemClock; private final ShadeLogger mShadeLog; private final DozeParameters mDozeParameters; - private final Runnable mCollapseExpandAction = this::collapseOrExpand; - private final NsslOverscrollTopChangedListener mOnOverscrollTopChangedListener = - new NsslOverscrollTopChangedListener(); private final NotificationStackScrollLayout.OnEmptySpaceClickListener mOnEmptySpaceClickListener = (x, y) -> onEmptySpaceClick(); private final ShadeHeadsUpChangedListener mOnHeadsUpChangedListener = new ShadeHeadsUpChangedListener(); - private final QS.HeightListener mHeightListener = this::onQsHeightChanged; private final ConfigurationListener mConfigurationListener = new ConfigurationListener(); private final SettingsChangeObserver mSettingsChangeObserver; private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener(); @@ -327,7 +316,6 @@ public final class NotificationPanelViewController implements Dumpable { private final ConfigurationController mConfigurationController; private final Provider mFlingAnimationUtilsBuilder; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; - private final InteractionJankMonitor mInteractionJankMonitor; private final LayoutInflater mLayoutInflater; private final FeatureFlags mFeatureFlags; private final PowerManager mPowerManager; @@ -346,12 +334,9 @@ public final class NotificationPanelViewController implements Dumpable { private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; private final FragmentService mFragmentService; private final ScrimController mScrimController; - private final NotificationRemoteInputManager mRemoteInputManager; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; - private final ShadeTransitionController mShadeTransitionController; private final TapAgainViewController mTapAgainViewController; private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; - private final RecordingController mRecordingController; private final boolean mVibrateOnOpening; private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); private final FlingAnimationUtils mFlingAnimationUtilsClosing; @@ -363,12 +348,11 @@ public final class NotificationPanelViewController implements Dumpable { private final Interpolator mBounceInterpolator; private final NotificationShadeWindowController mNotificationShadeWindowController; private final ShadeExpansionStateManager mShadeExpansionStateManager; - private final QS.ScrollListener mQsScrollListener = this::onQsPanelScrollChanged; private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired; - private final FragmentListener mQsFragmentListener = new QsFragmentListener(); private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate(); private final NotificationGutsManager mGutsManager; private final AlternateBouncerInteractor mAlternateBouncerInteractor; + private final QuickSettingsController mQsController; private long mDownTime; private boolean mTouchSlopExceededBeforeDown; @@ -395,9 +379,6 @@ public final class NotificationPanelViewController implements Dumpable { private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; private KeyguardStatusBarViewController mKeyguardStatusBarViewController; - private QS mQs; - private FrameLayout mQsFrame; - private final QsFrameTranslateController mQsFrameTranslateController; private KeyguardStatusViewController mKeyguardStatusViewController; private final LockIconViewController mLockIconViewController; private NotificationsQuickSettingsContainer mNotificationContainerParent; @@ -405,47 +386,19 @@ public final class NotificationPanelViewController implements Dumpable { private final Provider mKeyguardBottomAreaViewControllerProvider; private boolean mAnimateNextPositionUpdate; - private float mQuickQsHeaderHeight; private final ScreenOffAnimationController mScreenOffAnimationController; private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; - private int mQsTrackingPointer; - private VelocityTracker mQsVelocityTracker; private TrackingStartedListener mTrackingStartedListener; private OpenCloseListener mOpenCloseListener; private GestureRecorder mGestureRecorder; - private boolean mQsTracking; - /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */ - private boolean mConflictingQsExpansionGesture; private boolean mPanelExpanded; - /** - * Indicates that QS is in expanded state which can happen by: - * - single pane shade: expanding shade and then expanding QS - * - split shade: just expanding shade (QS are expanded automatically) - */ - private boolean mQsExpanded; - private boolean mQsExpandedWhenExpandingStarted; - private boolean mQsFullyExpanded; - private boolean mKeyguardShowing; private boolean mKeyguardQsUserSwitchEnabled; private boolean mKeyguardUserSwitcherEnabled; private boolean mDozing; private boolean mDozingOnDown; private boolean mBouncerShowing; private int mBarState; - private float mInitialHeightOnTouch; - private float mInitialTouchX; - private float mInitialTouchY; - private float mQsExpansionHeight; - private int mQsMinExpansionHeight; - private int mQsMaxExpansionHeight; - private int mQsPeekHeight; - private boolean mStackScrollerOverscrolling; - private boolean mQsExpansionFromOverscroll; - private float mLastOverscroll; - private boolean mQsExpansionEnabledPolicy = true; - private boolean mQsExpansionEnabledAmbient = true; - private ValueAnimator mQsExpansionAnimator; private FlingAnimationUtils mFlingAnimationUtils; private int mStatusBarMinHeight; private int mStatusBarHeaderHeightKeyguard; @@ -455,8 +408,6 @@ public final class NotificationPanelViewController implements Dumpable { private int mDisplayTopInset = 0; // in pixels private int mDisplayRightInset = 0; // in pixels private int mDisplayLeftInset = 0; // in pixels - private int mLargeScreenShadeHeaderHeight; - private int mSplitShadeNotificationsScrimMarginBottom; private final KeyguardClockPositionAlgorithm mClockPositionAlgorithm = @@ -466,23 +417,7 @@ public final class NotificationPanelViewController implements Dumpable { new KeyguardClockPositionAlgorithm.Result(); private boolean mIsExpanding; - /** - * Determines if QS should be already expanded when expanding shade. - * Used for split shade, two finger gesture as well as accessibility shortcut to QS. - * It needs to be set when movement starts as it resets at the end of expansion/collapse. - */ - private boolean mQsExpandImmediate; - private boolean mTwoFingerQsExpandPossible; private String mHeaderDebugInfo; - /** - * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still - * need to take this into account in our panel height calculation. - */ - private boolean mQsAnimatorExpand; - private ValueAnimator mQsSizeChangeAnimator; - private boolean mQsScrimEnabled = true; - private boolean mQsTouchAboveFalsingThreshold; - private int mQsFalsingThreshold; /** * Indicates drag starting height when swiping down or up on heads-up notifications. @@ -564,51 +499,12 @@ public final class NotificationPanelViewController implements Dumpable { private Runnable mExpandAfterLayoutRunnable; private Runnable mHideExpandedRunnable; - /** - * The padding between the start of notifications and the qs boundary on the lockscreen. - * On lockscreen, notifications aren't inset this extra amount, but we still want the - * qs boundary to be padded. - */ - private int mLockscreenNotificationQSPadding; - /** - * The amount of progress we are currently in if we're transitioning to the full shade. - * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full - * shade. This value can also go beyond 1.1 when we're overshooting! - */ - private float mTransitioningToFullShadeProgress; - /** - * Position of the qs bottom during the full shade transition. This is needed as the toppadding - * can change during state changes, which makes it much harder to do animations - */ - private int mTransitionToFullShadeQSPosition; - /** Distance a full shade transition takes in order for qs to fully transition to the shade. */ - private int mDistanceForQSFullShadeTransition; - /** The translation amount for QS for the full shade transition. */ - private float mQsTranslationForFullShadeTransition; - /** The maximum overshoot allowed for the top padding for the full shade transition. */ private int mMaxOverscrollAmountForPulse; - /** Should we animate the next bounds update. */ - private boolean mAnimateNextNotificationBounds; - /** The delay for the next bounds animation. */ - private long mNotificationBoundsAnimationDelay; - /** The duration of the notification bounds animation. */ - private long mNotificationBoundsAnimationDuration; /** Whether a collapse that started on the panel should allow the panel to intercept. */ private boolean mIsPanelCollapseOnQQS; - private boolean mAnimatingQS; - /** The end bounds of a clipping animation. */ - private final Rect mQsClippingAnimationEndBounds = new Rect(); - /** The animator for the qs clipping bounds. */ - private ValueAnimator mQsClippingAnimation = null; - /** Whether the current animator is resetting the qs translation. */ - private boolean mIsQsTranslationResetAnimator; - /** Whether the current animator is resetting the pulse expansion after a drag down. */ - private boolean mIsPulseExpansionResetAnimator; - private final Rect mLastQsClipBounds = new Rect(); - private final Region mQsInterceptRegion = new Region(); /** Alpha of the views which only show on the keyguard but not in shade / shade locked. */ private float mKeyguardOnlyContentAlpha = 1.0f; /** Y translation of the views that only show on the keyguard but in shade / shade locked. */ @@ -618,15 +514,6 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mIsGestureNavigation; private int mOldLayoutDirection; private NotificationShelfController mNotificationShelfController; - private int mScrimCornerRadius; - private int mScreenCornerRadius; - private boolean mQSAnimatingHiddenFromCollapsed; - private boolean mUseLargeScreenShadeHeader; - private boolean mEnableQsClipping; - - private int mQsClipTop; - private int mQsClipBottom; - private boolean mQsVisible; private final ContentResolver mContentResolver; private float mMinFraction; @@ -685,7 +572,6 @@ public final class NotificationPanelViewController implements Dumpable { private boolean mInstantExpanding; private boolean mAnimateAfterExpanding; private boolean mIsFlinging; - private boolean mLastFlingWasExpanding; private String mViewName; private float mInitialExpandY; private float mInitialExpandX; @@ -803,6 +689,7 @@ public final class NotificationPanelViewController implements Dumpable { TapAgainViewController tapAgainViewController, NavigationModeController navigationModeController, NavigationBarController navigationBarController, + QuickSettingsController quickSettingsController, FragmentService fragmentService, ContentResolver contentResolver, RecordingController recordingController, @@ -812,8 +699,6 @@ public final class NotificationPanelViewController implements Dumpable { ShadeExpansionStateManager shadeExpansionStateManager, NotificationRemoteInputManager remoteInputManager, Optional unfoldComponent, - InteractionJankMonitor interactionJankMonitor, - QsFrameTranslateController qsFrameTranslateController, SysUiState sysUiState, Provider keyguardBottomAreaViewControllerProvider, KeyguardUnlockAnimationController keyguardUnlockAnimationController, @@ -873,6 +758,7 @@ public final class NotificationPanelViewController implements Dumpable { mResources = mView.getResources(); mKeyguardStateController = keyguardStateController; + mQsController = quickSettingsController; mKeyguardIndicationController = keyguardIndicationController; mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; mNotificationShadeWindowController = notificationShadeWindowController; @@ -903,7 +789,6 @@ public final class NotificationPanelViewController implements Dumpable { mVibratorHelper = vibratorHelper; mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; - mInteractionJankMonitor = interactionJankMonitor; mSystemClock = systemClock; mKeyguardMediaController = keyguardMediaController; mMetricsLogger = metricsLogger; @@ -939,7 +824,6 @@ public final class NotificationPanelViewController implements Dumpable { mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); setPanelAlpha(255, false /* animate */); mCommandQueue = commandQueue; - mRecordingController = recordingController; mDisplayId = displayId; mPulseExpansionHandler = pulseExpansionHandler; mDozeParameters = dozeParameters; @@ -948,20 +832,19 @@ public final class NotificationPanelViewController implements Dumpable { mMediaDataManager = mediaDataManager; mTapAgainViewController = tapAgainViewController; mSysUiState = sysUiState; - pulseExpansionHandler.setPulseExpandAbortListener(() -> { - if (mQs != null) { - mQs.animateHeaderSlidingOut(); - } - }); statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged); mKeyguardBypassController = bypassController; mUpdateMonitor = keyguardUpdateMonitor; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; - mShadeTransitionController = shadeTransitionController; lockscreenShadeTransitionController.setNotificationPanelController(this); shadeTransitionController.setNotificationPanelViewController(this); dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged); - + quickSettingsController.setExpansionHeightListener(this::onQsSetExpansionHeightCalled); + quickSettingsController.setQsStateUpdateListener(this::onQsStateUpdated); + quickSettingsController.setApplyClippingImmediatelyListener( + this::onQsClippingImmediatelyApplied); + quickSettingsController.setFlingQsWithoutClickListener(this::onFlingQsWithoutClick); + quickSettingsController.setExpansionHeightSetToMaxListener(this::onExpansionHeightSetToMax); shadeExpansionStateManager.addStateListener(this::onPanelStateChanged); mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0); @@ -976,7 +859,6 @@ public final class NotificationPanelViewController implements Dumpable { mLockIconViewController = lockIconViewController; mScreenOffAnimationController = screenOffAnimationController; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mRemoteInputManager = remoteInputManager; mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE); int currentMode = navigationModeController.addListener( @@ -995,7 +877,8 @@ public final class NotificationPanelViewController implements Dumpable { if (DEBUG_DRAWABLE) { mView.getOverlay().add(new DebugDrawable(this, mView, - mNotificationStackScrollLayoutController, mLockIconViewController)); + mNotificationStackScrollLayoutController, mLockIconViewController, + mQsController)); } mKeyguardUnfoldTransition = unfoldComponent.map( @@ -1004,8 +887,6 @@ public final class NotificationPanelViewController implements Dumpable { SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController); mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION); - - mQsFrameTranslateController = qsFrameTranslateController; updateUserSwitcherFlags(); mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel; mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; @@ -1121,19 +1002,16 @@ public final class NotificationPanelViewController implements Dumpable { mNotificationStackScrollLayoutController.attach(stackScrollLayout); mNotificationStackScrollLayoutController.setOnHeightChangedListener( new NsslHeightChangedListener()); - mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( - mOnOverscrollTopChangedListener); - mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); - mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged); mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mOnEmptySpaceClickListener); + mQsController.initNotificationStackScrollLayoutController(); + mShadeExpansionStateManager.addQsExpansionListener(this::onQsExpansionChanged); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area)); initBottomArea(); mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController); - mQsFrame = mView.findViewById(R.id.qs_frame); mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController); mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() { @Override @@ -1236,24 +1114,14 @@ public final class NotificationPanelViewController implements Dumpable { .setMaxLengthSeconds(0.4f).build(); mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext()); mStatusBarHeaderHeightKeyguard = Utils.getStatusBarHeaderHeightKeyguard(mView.getContext()); - mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height); mClockPositionAlgorithm.loadDimens(mResources); - mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold); mIndicationBottomPadding = mResources.getDimensionPixelSize( R.dimen.keyguard_indication_bottom_padding); int statusbarHeight = SystemBarUtils.getStatusBarHeight(mView.getContext()); mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize( R.dimen.heads_up_status_bar_padding); - mDistanceForQSFullShadeTransition = mResources.getDimensionPixelSize( - R.dimen.lockscreen_shade_qs_transition_distance); mMaxOverscrollAmountForPulse = mResources.getDimensionPixelSize( R.dimen.pulse_expansion_max_top_overshoot); - mScrimCornerRadius = mResources.getDimensionPixelSize( - R.dimen.notification_scrim_corner_radius); - mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius( - mView.getContext()); - mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize( - R.dimen.notification_side_paddings); mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); mSplitShadeScrimTransitionDistance = mResources.getDimensionPixelSize( R.dimen.split_shade_scrim_transition_distance); @@ -1267,6 +1135,8 @@ public final class NotificationPanelViewController implements Dumpable { R.dimen.gone_to_dreaming_transition_lockscreen_translation_y); mLockscreenToOccludedTransitionTranslationY = mResources.getDimensionPixelSize( R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y); + // TODO (b/265193930): remove this and make QsController listen to NotificationPanelViews + mQsController.loadDimens(); } private void updateViewControllers(KeyguardStatusView keyguardStatusView, @@ -1309,40 +1179,13 @@ public final class NotificationPanelViewController implements Dumpable { } public void updateResources() { - mSplitShadeNotificationsScrimMarginBottom = - mResources.getDimensionPixelSize( - R.dimen.split_shade_notifications_scrim_margin_bottom); final boolean newSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources); final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled; mSplitShadeEnabled = newSplitShadeEnabled; - - if (mQs != null) { - mQs.setInSplitShade(mSplitShadeEnabled); - } - - mUseLargeScreenShadeHeader = - LargeScreenUtils.shouldUseLargeScreenShadeHeader(mView.getResources()); - - mLargeScreenShadeHeaderHeight = - mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height); - // TODO: When the flag is eventually removed, it means that we have a single view that is - // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually - // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class - // that controls the view as the offset needs to be the same regardless. - if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) { - mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight; - } else { - mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mView.getContext()); - } - int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : - mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top); - mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader); - mAmbientState.setStackTopMargin(topMargin); + mQsController.updateResources(); mNotificationsQSContainerController.updateResources(); - updateKeyguardStatusViewAlignment(/* animate= */false); - mKeyguardMediaController.refreshMediaPosition(); if (splitShadeChanged) { @@ -1351,17 +1194,15 @@ public final class NotificationPanelViewController implements Dumpable { mSplitShadeFullTransitionDistance = mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance); - - mEnableQsClipping = mResources.getBoolean(R.bool.qs_enable_clipping); } private void onSplitShadeEnabledChanged() { // when we switch between split shade and regular shade we want to enforce setting qs to // the default state: expanded for split shade and collapsed otherwise if (!isOnKeyguard() && mPanelExpanded) { - setQsExpanded(mSplitShadeEnabled); + mQsController.setExpanded(mSplitShadeEnabled); } - if (isOnKeyguard() && mQsExpanded && mSplitShadeEnabled) { + if (isOnKeyguard() && mQsController.getExpanded() && mSplitShadeEnabled) { // In single column keyguard - when you swipe from the top - QS is fully expanded and // StatusBarState is KEYGUARD. That state doesn't make sense for split shade, // where notifications are always visible and we effectively go to fully expanded @@ -1371,7 +1212,7 @@ public final class NotificationPanelViewController implements Dumpable { mStatusBarStateController.setState(StatusBarState.SHADE_LOCKED, /* force= */false); } updateClockAppearance(); - updateQsState(); + mQsController.updateQsState(); mNotificationStackScrollLayoutController.updateFooter(); } @@ -1479,11 +1320,6 @@ public final class NotificationPanelViewController implements Dumpable { mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView)); } - @VisibleForTesting - void setQs(QS qs) { - mQs = qs; - } - private void attachSplitShadeMediaPlayerContainer(FrameLayout container) { mKeyguardMediaController.attachSplitShadeContainer(container); } @@ -1515,7 +1351,7 @@ public final class NotificationPanelViewController implements Dumpable { if (SPEW_LOGCAT) Log.d(TAG, "Skipping computeMaxKeyguardNotifications() by request"); } - if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) { + if (getKeyguardShowing() && !mKeyguardBypassController.getBypassEnabled()) { mNotificationStackScrollLayoutController.setMaxDisplayedNotifications( mMaxAllowedKeyguardNotifications); mNotificationStackScrollLayoutController.setKeyguardBottomPaddingForDebug( @@ -1564,39 +1400,14 @@ public final class NotificationPanelViewController implements Dumpable { mIsFullWidth = isFullWidth; mScrimController.setClipsQsScrim(isFullWidth); mNotificationStackScrollLayoutController.setIsFullWidth(isFullWidth); - if (mQs != null) { - mQs.setIsNotificationPanelFullWidth(isFullWidth); - } - } - - private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) { - if (mQsSizeChangeAnimator != null) { - oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); - mQsSizeChangeAnimator.cancel(); - } - mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight); - mQsSizeChangeAnimator.setDuration(300); - mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mQsSizeChangeAnimator.addUpdateListener(animation -> { - requestScrollerTopPaddingUpdate(false /* animate */); - updateExpandedHeightToMaxHeight(); - int height = (int) mQsSizeChangeAnimator.getAnimatedValue(); - mQs.setHeightOverride(height); - }); - mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mQsSizeChangeAnimator = null; - } - }); - mQsSizeChangeAnimator.start(); + mQsController.setNotificationPanelFullWidth(isFullWidth); } /** * Positions the clock and notifications dynamically depending on how many notifications are * showing. */ - private void positionClockAndNotifications() { + void positionClockAndNotifications() { positionClockAndNotifications(false /* forceUpdate */); } @@ -1621,7 +1432,7 @@ public final class NotificationPanelViewController implements Dumpable { // so we should not add a padding for them stackScrollerPadding = 0; } else { - stackScrollerPadding = getUnlockedStackScrollerPadding(); + stackScrollerPadding = mQsController.getUnlockedStackScrollerPadding(); } } else { stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded; @@ -1669,8 +1480,9 @@ public final class NotificationPanelViewController implements Dumpable { userSwitcherHeight, userSwitcherPreferredY, darkAmount, mOverStretchAmount, - bypassEnabled, getUnlockedStackScrollerPadding(), - computeQsExpansionFraction(), + bypassEnabled, + mQsController.getUnlockedStackScrollerPadding(), + mQsController.computeExpansionFraction(), mDisplayTopInset, mSplitShadeEnabled, udfpsAodTopLocation, @@ -1822,19 +1634,16 @@ public final class NotificationPanelViewController implements Dumpable { return mDozing && mDozeParameters.getAlwaysOn(); } + boolean isDozing() { + return mDozing; + } + private boolean hasVisibleNotifications() { return mNotificationStackScrollLayoutController .getVisibleNotificationCount() != 0 || mMediaDataManager.hasActiveMediaOrRecommendation(); } - /** - * @return the padding of the stackscroller when unlocked - */ - private int getUnlockedStackScrollerPadding() { - return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight; - } - /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */ private float getLockIconPadding() { float lockIconPadding = 0f; @@ -1952,23 +1761,23 @@ public final class NotificationPanelViewController implements Dumpable { mAnimateNextPositionUpdate = true; } - private void setQsExpansionEnabled() { - if (mQs == null) return; - mQs.setHeaderClickable(isQsExpansionEnabled()); - } + /** Animate QS closing. */ + public void animateCloseQs(boolean animateAway) { + if (mSplitShadeEnabled) { + collapsePanel(true, false, 1.0f); + } else { + mQsController.animateCloseQs(animateAway); + } - public void setQsExpansionEnabledPolicy(boolean qsExpansionEnabledPolicy) { - mQsExpansionEnabledPolicy = qsExpansionEnabledPolicy; - setQsExpansionEnabled(); } public void resetViews(boolean animate) { mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */, true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */); if (animate && !isFullyCollapsed()) { - animateCloseQs(true /* animateAway */); + animateCloseQs(true); } else { - closeQs(); + mQsController.closeQs(); } mNotificationStackScrollLayoutController.setOverScrollAmount(0f, true /* onTop */, animate, !animate /* cancelAnimators */); @@ -1999,8 +1808,8 @@ public final class NotificationPanelViewController implements Dumpable { return; } - if (mQsExpanded) { - setQsExpandImmediate(true); + if (mQsController.getExpanded()) { + mQsController.setExpandImmediate(true); setShowShelfOnly(true); } debugLog("collapse: %s", this); @@ -2019,33 +1828,11 @@ public final class NotificationPanelViewController implements Dumpable { } } - @VisibleForTesting - void setQsExpandImmediate(boolean expandImmediate) { - if (expandImmediate != mQsExpandImmediate) { - mQsExpandImmediate = expandImmediate; - mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate); - } - } - - @VisibleForTesting - boolean isQsExpandImmediate() { - return mQsExpandImmediate; - } - private void setShowShelfOnly(boolean shelfOnly) { mNotificationStackScrollLayoutController.setShouldShowShelfOnly( shelfOnly && !mSplitShadeEnabled); } - public void closeQs() { - cancelQsAnimation(); - setQsExpansionHeight(mQsMinExpansionHeight); - // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the - // middle of animation - we need to make sure that value is always false when shade if - // fully collapsed or expanded - setQsExpandImmediate(false); - } - @VisibleForTesting void cancelHeightAnimator() { if (mHeightAnimator != null) { @@ -2061,39 +1848,9 @@ public final class NotificationPanelViewController implements Dumpable { mView.animate().cancel(); } - /** - * Animate QS closing by flinging it. - * If QS is expanded, it will collapse into QQS and stop. - * If in split shade, it will collapse the whole shade. - * - * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore. - */ - public void animateCloseQs(boolean animateAway) { - if (mSplitShadeEnabled) { - collapsePanel( - /* animate= */true, /* delayed= */false, /* speedUpFactor= */1.0f); - return; - } - - if (mQsExpansionAnimator != null) { - if (!mQsAnimatorExpand) { - return; - } - float height = mQsExpansionHeight; - mQsExpansionAnimator.cancel(); - setQsExpansionHeight(height); - } - flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE); - } - - private boolean isQsExpansionEnabled() { - return mQsExpansionEnabledPolicy && mQsExpansionEnabledAmbient - && !mRemoteInputManager.isRemoteInputActive(); - } - public void expandWithQs() { - if (isQsExpansionEnabled()) { - setQsExpandImmediate(true); + if (mQsController.isExpansionEnabled()) { + mQsController.setExpandImmediate(true); setShowShelfOnly(true); } if (mSplitShadeEnabled && isOnKeyguard()) { @@ -2109,8 +1866,8 @@ public final class NotificationPanelViewController implements Dumpable { } else if (isFullyCollapsed()) { expand(true /* animate */); } else { - traceQsJank(true /* startTracing */, false /* wasCancelled */); - flingSettings(0 /* velocity */, FLING_EXPAND); + mQsController.traceQsJank(true /* startTracing */, false /* wasCancelled */); + mQsController.flingQs(0, FLING_EXPAND); } } @@ -2125,8 +1882,8 @@ public final class NotificationPanelViewController implements Dumpable { if (mSplitShadeEnabled && (isShadeFullyOpen() || isExpanding())) { return; } - if (isQsExpanded()) { - flingSettings(0 /* velocity */, FLING_COLLAPSE); + if (mQsController.getExpanded()) { + mQsController.flingQs(0, FLING_COLLAPSE); } else { expand(true /* animate */); } @@ -2143,8 +1900,7 @@ public final class NotificationPanelViewController implements Dumpable { @VisibleForTesting void flingToHeight(float vel, boolean expand, float target, float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { - mLastFlingWasExpanding = expand; - mShadeLog.logLastFlingWasExpanding(expand); + mQsController.setLastShadeFlingWasExpanding(expand); mHeadsUpTouchHelper.notifyFling(!expand); mKeyguardStateController.notifyPanelFlingStart(!expand /* flingingToDismiss */); setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f); @@ -2216,7 +1972,7 @@ public final class NotificationPanelViewController implements Dumpable { @Override public void onAnimationStart(Animator animation) { if (!mStatusBarStateController.isDozing()) { - beginJankMonitoring(); + mQsController.beginJankMonitoring(isFullyCollapsed()); } } @@ -2248,117 +2004,15 @@ public final class NotificationPanelViewController implements Dumpable { setAnimator(null); mKeyguardStateController.notifyPanelFlingEnd(); if (!cancelled) { - endJankMonitoring(); + mQsController.endJankMonitoring(); notifyExpandingFinished(); } else { - cancelJankMonitoring(); + mQsController.cancelJankMonitoring(); } updatePanelExpansionAndVisibility(); mNotificationStackScrollLayoutController.setPanelFlinging(false); } - private boolean onQsIntercept(MotionEvent event) { - debugLog("onQsIntercept"); - int pointerIndex = event.findPointerIndex(mQsTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mQsTrackingPointer = event.getPointerId(pointerIndex); - } - final float x = event.getX(pointerIndex); - final float y = event.getY(pointerIndex); - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mInitialTouchY = y; - mInitialTouchX = x; - initVelocityTracker(); - trackMovement(event); - float qsExpansionFraction = computeQsExpansionFraction(); - // Intercept the touch if QS is between fully collapsed and fully expanded state - if (!mSplitShadeEnabled - && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) { - mShadeLog.logMotionEvent(event, - "onQsIntercept: down action, QS partially expanded/collapsed"); - return true; - } - if (mKeyguardShowing - && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { - // Dragging down on the lockscreen statusbar should prohibit other interactions - // immediately, otherwise we'll wait on the touchslop. This is to allow - // dragging down to expanded quick settings directly on the lockscreen. - mView.getParent().requestDisallowInterceptTouchEvent(true); - } - if (mQsExpansionAnimator != null) { - mInitialHeightOnTouch = mQsExpansionHeight; - mShadeLog.logMotionEvent(event, - "onQsIntercept: down action, QS tracking enabled"); - mQsTracking = true; - traceQsJank(true /* startTracing */, false /* wasCancelled */); - mNotificationStackScrollLayoutController.cancelLongPress(); - } - break; - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mQsTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - mQsTrackingPointer = event.getPointerId(newIndex); - mInitialTouchX = event.getX(newIndex); - mInitialTouchY = event.getY(newIndex); - } - break; - - case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY; - trackMovement(event); - if (mQsTracking) { - - // Already tracking because onOverscrolled was called. We need to update here - // so we don't stop for a frame until the next touch event gets handled in - // onTouchEvent. - setQsExpansionHeight(h + mInitialHeightOnTouch); - trackMovement(event); - return true; - } else { - mShadeLog.logMotionEvent(event, - "onQsIntercept: move ignored because qs tracking disabled"); - } - float touchSlop = getTouchSlop(event); - if ((h > touchSlop || (h < -touchSlop && mQsExpanded)) - && Math.abs(h) > Math.abs(x - mInitialTouchX) - && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { - mView.getParent().requestDisallowInterceptTouchEvent(true); - mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); - mQsTracking = true; - traceQsJank(true /* startTracing */, false /* wasCancelled */); - onQsExpansionStarted(); - notifyExpandingFinished(); - mInitialHeightOnTouch = mQsExpansionHeight; - mInitialTouchY = y; - mInitialTouchX = x; - mNotificationStackScrollLayoutController.cancelLongPress(); - return true; - } else { - mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop, mQsExpanded, - mCollapsedOnDown, mKeyguardShowing, isQsExpansionEnabled()); - } - break; - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - trackMovement(event); - mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled"); - mQsTracking = false; - break; - } - return false; - } - - @VisibleForTesting - boolean isQsTracking() { - return mQsTracking; - } - private boolean isInContentBounds(float x, float y) { float stackScrollerX = mNotificationStackScrollLayoutController.getX(); return !mNotificationStackScrollLayoutController @@ -2367,29 +2021,14 @@ public final class NotificationPanelViewController implements Dumpable { && x < stackScrollerX + mNotificationStackScrollLayoutController.getWidth(); } - private void traceQsJank(boolean startTracing, boolean wasCancelled) { - if (mInteractionJankMonitor == null) { - return; - } - if (startTracing) { - mInteractionJankMonitor.begin(mView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); - } else { - if (wasCancelled) { - mInteractionJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); - } else { - mInteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); - } - } - } - private void initDownStates(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mQsTouchAboveFalsingThreshold = mQsFullyExpanded; mDozingOnDown = mDozing; mDownX = event.getX(); mDownY = event.getY(); mCollapsedOnDown = isFullyCollapsed(); - mIsPanelCollapseOnQQS = canPanelCollapseOnQQS(mDownX, mDownY); + mQsController.setCollapsedOnDown(mCollapsedOnDown); + mIsPanelCollapseOnQQS = mQsController.canPanelCollapseOnQQS(mDownX, mDownY); mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp(); mAllowExpandForSmallExpansion = mExpectingSynthesizedDown; mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown; @@ -2399,7 +2038,7 @@ public final class NotificationPanelViewController implements Dumpable { event.getEventTime(), mDownX, mDownY, - mQsTouchAboveFalsingThreshold, + mQsController.updateAndGetTouchAboveFalsingThreshold(), mDozingOnDown, mCollapsedOnDown, mIsPanelCollapseOnQQS, @@ -2414,84 +2053,14 @@ public final class NotificationPanelViewController implements Dumpable { } } - /** - * Can the panel collapse in this motion because it was started on QQS? - * - * @param downX the x location where the touch started - * @param downY the y location where the touch started - * @return true if the panel could be collapsed because it stared on QQS - */ - private boolean canPanelCollapseOnQQS(float downX, float downY) { - if (mCollapsedOnDown || mKeyguardShowing || mQsExpanded) { - return false; - } - View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader(); - return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth() - && downY <= header.getBottom(); - - } - - private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) { - float vel = getCurrentQSVelocity(); - boolean expandsQs = flingExpandsQs(vel); - if (expandsQs) { - if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) { - expandsQs = false; - } else { - logQsSwipeDown(y); - } - } else if (vel < 0) { - mFalsingManager.isFalseTouch(QS_COLLAPSE); - } - - int flingType; - if (expandsQs && !isCancelMotionEvent) { - flingType = FLING_EXPAND; - } else if (mSplitShadeEnabled) { - flingType = FLING_HIDE; - } else { - flingType = FLING_COLLAPSE; - } - flingSettings(vel, flingType); - } - - private void logQsSwipeDown(float y) { - float vel = getCurrentQSVelocity(); - final int - gesture = - mBarState == KEYGUARD ? MetricsEvent.ACTION_LS_QS - : MetricsEvent.ACTION_SHADE_QS_PULL; - mLockscreenGestureLogger.write(gesture, - (int) ((y - mInitialTouchY) / mCentralSurfaces.getDisplayDensity()), - (int) (vel / mCentralSurfaces.getDisplayDensity())); - } - - private boolean flingExpandsQs(float vel) { + boolean flingExpandsQs(float vel) { if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - return computeQsExpansionFraction() > 0.5f; + return mQsController.computeExpansionFraction() > 0.5f; } else { return vel > 0; } } - private boolean isFalseTouch() { - if (mFalsingManager.isClassifierEnabled()) { - return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS); - } - return !mQsTouchAboveFalsingThreshold; - } - - private float computeQsExpansionFraction() { - if (mQSAnimatingHiddenFromCollapsed) { - // When hiding QS from collapsed state, the expansion can sometimes temporarily - // be larger than 0 because of the timing, leading to flickers. - return 0.0f; - } - return Math.min( - 1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight - - mQsMinExpansionHeight)); - } - private boolean shouldExpandWhenNotFlinging() { if (getExpandedFraction() > 0.5f) { return true; @@ -2509,121 +2078,13 @@ public final class NotificationPanelViewController implements Dumpable { return mNotificationStackScrollLayoutController.getOpeningHeight(); } - - private boolean handleQsTouch(MotionEvent event) { - if (isSplitShadeAndTouchXOutsideQs(event.getX())) { - return false; - } - final int action = event.getActionMasked(); - boolean collapsedQs = !mQsExpanded && !mSplitShadeEnabled; - boolean expandedShadeCollapsedQs = getExpandedFraction() == 1f && mBarState != KEYGUARD - && collapsedQs && isQsExpansionEnabled(); - if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) { - // Down in the empty area while fully expanded - go to QS. - mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled"); - mQsTracking = true; - traceQsJank(true /* startTracing */, false /* wasCancelled */); - mConflictingQsExpansionGesture = true; - onQsExpansionStarted(); - mInitialHeightOnTouch = mQsExpansionHeight; - mInitialTouchY = event.getY(); - mInitialTouchX = event.getX(); - } - if (!isFullyCollapsed() && !isShadeOrQsHeightAnimationRunning()) { - handleQsDown(event); - } - // defer touches on QQS to shade while shade is collapsing. Added margin for error - // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS. - if (!mSplitShadeEnabled && !mLastFlingWasExpanding - && computeQsExpansionFraction() <= 0.01 && getExpandedFraction() < 1.0) { - mShadeLog.logMotionEvent(event, - "handleQsTouch: shade touched while collapsing, QS tracking disabled"); - mQsTracking = false; - } - if (!mQsExpandImmediate && mQsTracking) { - onQsTouch(event); - if (!mConflictingQsExpansionGesture && !mSplitShadeEnabled) { - mShadeLog.logMotionEvent(event, - "handleQsTouch: not immediate expand or conflicting gesture"); - return true; - } - } - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mConflictingQsExpansionGesture = false; - } - if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && isQsExpansionEnabled()) { - mTwoFingerQsExpandPossible = true; - } - if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex()) - < mStatusBarMinHeight) { - mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1); - setQsExpandImmediate(true); - setShowShelfOnly(true); - updateExpandedHeightToMaxHeight(); - - // Normally, we start listening when the panel is expanded, but here we need to start - // earlier so the state is already up to date when dragging down. - setListening(true); - } - return false; + float getDisplayDensity() { + return mCentralSurfaces.getDisplayDensity(); } - /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */ - private boolean isSplitShadeAndTouchXOutsideQs(float touchX) { - return mSplitShadeEnabled && (touchX < mQsFrame.getX() - || touchX > mQsFrame.getX() + mQsFrame.getWidth()); - } - - private boolean isInQsArea(float x, float y) { - if (isSplitShadeAndTouchXOutsideQs(x)) { - return false; - } - // Let's reject anything at the very bottom around the home handle in gesture nav - if (mIsGestureNavigation && y > mView.getHeight() - mNavigationBarBottomHeight) { - return false; - } - return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom() - || y <= mQs.getView().getY() + mQs.getView().getHeight(); - } - - private boolean isOpenQsEvent(MotionEvent event) { - final int pointerCount = event.getPointerCount(); - final int action = event.getActionMasked(); - - final boolean - twoFingerDrag = - action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2; - - final boolean - stylusButtonClickDrag = - action == MotionEvent.ACTION_DOWN && (event.isButtonPressed( - MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed( - MotionEvent.BUTTON_STYLUS_SECONDARY)); - - final boolean - mouseButtonClickDrag = - action == MotionEvent.ACTION_DOWN && (event.isButtonPressed( - MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed( - MotionEvent.BUTTON_TERTIARY)); - - return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; - } - - private void handleQsDown(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept( - event.getX(), event.getY(), -1)) { - debugLog("handleQsDown"); - mFalsingCollector.onQsDown(); - mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled"); - mQsTracking = true; - onQsExpansionStarted(); - mInitialHeightOnTouch = mQsExpansionHeight; - mInitialTouchY = event.getY(); - mInitialTouchX = event.getX(); - - // If we interrupt an expansion gesture here, make sure to update the state correctly. - notifyExpandingFinished(); - } + /** Return whether a touch is near the gesture handle at the bottom of screen */ + public boolean isInGestureNavHomeHandleArea(float x, float y) { + return mIsGestureNavigation && y > mView.getHeight() - mNavigationBarBottomHeight; } /** Input focus transfer is about to happen. */ @@ -2680,7 +2141,7 @@ public final class NotificationPanelViewController implements Dumpable { } // If we are already running a QS expansion, make sure that we keep the panel open. - if (mQsExpansionAnimator != null) { + if (mQsController.isExpansionAnimating()) { expands = true; } return expands; @@ -2694,124 +2155,9 @@ public final class NotificationPanelViewController implements Dumpable { return isFullyCollapsed() || mBarState != StatusBarState.SHADE; } - private void onQsTouch(MotionEvent event) { - int pointerIndex = event.findPointerIndex(mQsTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mQsTrackingPointer = event.getPointerId(pointerIndex); - } - final float y = event.getY(pointerIndex); - final float x = event.getX(pointerIndex); - final float h = y - mInitialTouchY; - - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled"); - mQsTracking = true; - traceQsJank(true /* startTracing */, false /* wasCancelled */); - mInitialTouchY = y; - mInitialTouchX = x; - onQsExpansionStarted(); - mInitialHeightOnTouch = mQsExpansionHeight; - initVelocityTracker(); - trackMovement(event); - break; - - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mQsTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - final float newY = event.getY(newIndex); - final float newX = event.getX(newIndex); - mQsTrackingPointer = event.getPointerId(newIndex); - mInitialHeightOnTouch = mQsExpansionHeight; - mInitialTouchY = newY; - mInitialTouchX = newX; - } - break; - - case MotionEvent.ACTION_MOVE: - debugLog("onQSTouch move"); - mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion"); - setQsExpansionHeight(h + mInitialHeightOnTouch); - if (h >= getFalsingThreshold()) { - mQsTouchAboveFalsingThreshold = true; - } - trackMovement(event); - break; - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mShadeLog.logMotionEvent(event, - "onQsTouch: up/cancel action, QS tracking disabled"); - mQsTracking = false; - mQsTrackingPointer = -1; - trackMovement(event); - float fraction = computeQsExpansionFraction(); - if (fraction != 0f || y >= mInitialTouchY) { - flingQsWithCurrentVelocity(y, - event.getActionMasked() == MotionEvent.ACTION_CANCEL); - } else { - traceQsJank(false /* startTracing */, - event.getActionMasked() == MotionEvent.ACTION_CANCEL); - } - if (mQsVelocityTracker != null) { - mQsVelocityTracker.recycle(); - mQsVelocityTracker = null; - } - break; - } - } - - private int getFalsingThreshold() { + int getFalsingThreshold() { float factor = mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f; - return (int) (mQsFalsingThreshold * factor); - } - - private void setOverScrolling(boolean overscrolling) { - mStackScrollerOverscrolling = overscrolling; - if (mQs == null) return; - mQs.setOverscrolling(overscrolling); - } - - private void onQsExpansionStarted() { - cancelQsAnimation(); - cancelHeightAnimator(); - - // Reset scroll position and apply that position to the expanded height. - float height = mQsExpansionHeight; - setQsExpansionHeight(height); - mNotificationStackScrollLayoutController.checkSnoozeLeavebehind(); - - // When expanding QS, let's authenticate the user if possible, - // this will speed up notification actions. - if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) { - mUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED); - } - } - - @VisibleForTesting - void setQsExpanded(boolean expanded) { - boolean changed = mQsExpanded != expanded; - if (changed) { - mQsExpanded = expanded; - updateQsState(); - updateExpandedHeightToMaxHeight(); - setStatusAccessibilityImportance(expanded - ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - updateSystemUiStateFlags(); - NavigationBarView navigationBarView = - mNavigationBarController.getNavigationBarView(mDisplayId); - if (navigationBarView != null) { - navigationBarView.onStatusBarPanelStateChanged(); - } - mShadeExpansionStateManager.onQsExpansionChanged(expanded); - mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded, - mQsMinExpansionHeight, mQsMaxExpansionHeight, mStackScrollerOverscrolling, - mDozing, mQsAnimatorExpand, mAnimatingQS); - } + return (int) (mQsController.getFalsingThreshold() * factor); } private void maybeAnimateBottomAreaAlpha() { @@ -2843,400 +2189,21 @@ public final class NotificationPanelViewController implements Dumpable { } } - private void updateQsState() { - boolean qsFullScreen = mQsExpanded && !mSplitShadeEnabled; - mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen); - mNotificationStackScrollLayoutController.setScrollingEnabled( - mBarState != KEYGUARD && (!qsFullScreen || mQsExpansionFromOverscroll)); - - if (mKeyguardUserSwitcherController != null && mQsExpanded - && !mStackScrollerOverscrolling) { - mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true); - } - if (mQs == null) return; - mQs.setExpanded(mQsExpanded); - } - - void setQsExpansionHeight(float height) { - height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); - mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0; - boolean qsAnimatingAway = !mQsAnimatorExpand && mAnimatingQS; - if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling - && !mDozing && !qsAnimatingAway) { - setQsExpanded(true); - } else if (height <= mQsMinExpansionHeight && mQsExpanded) { - setQsExpanded(false); - } - mQsExpansionHeight = height; - updateQsExpansion(); - requestScrollerTopPaddingUpdate(false /* animate */); - mKeyguardStatusBarViewController.updateViewState(); - if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == KEYGUARD) { - updateKeyguardBottomAreaAlpha(); - positionClockAndNotifications(); - } - - if (mAccessibilityManager.isEnabled()) { - mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); - } - - if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded - && mFalsingCollector.shouldEnforceBouncer()) { - mCentralSurfaces.executeRunnableDismissingKeyguard(null, null /* cancelAction */, - false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */); - } - if (DEBUG_DRAWABLE) { - mView.invalidate(); - } - } - - private void updateQsExpansion() { - if (mQs == null) return; - final float squishiness; - if ((mQsExpandImmediate || mQsExpanded) && !mSplitShadeEnabled) { - squishiness = 1; - } else if (mTransitioningToFullShadeProgress > 0.0f) { - squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction(); - } else { - squishiness = mNotificationStackScrollLayoutController - .getNotificationSquishinessFraction(); - } - final float qsExpansionFraction = computeQsExpansionFraction(); - final float adjustedExpansionFraction = mSplitShadeEnabled - ? 1f : computeQsExpansionFraction(); - mQs.setQsExpansion(adjustedExpansionFraction, getExpandedFraction(), getHeaderTranslation(), - squishiness); - mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); - int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); - mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); - setQSClippingBounds(); - - if (mSplitShadeEnabled) { - // In split shade we want to pretend that QS are always collapsed so their behaviour and - // interactions don't influence notifications as they do in portrait. But we want to set - // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1. - mNotificationStackScrollLayoutController.setQsExpansionFraction(0); - } else { - mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); - } - - mDepthController.setQsPanelExpansion(qsExpansionFraction); - mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction); - - float shadeExpandedFraction = isOnKeyguard() - ? getLockscreenShadeDragProgress() - : getExpandedFraction(); - mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); - mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); - mLargeScreenShadeHeaderController.setQsVisible(mQsVisible); - } - - private float getLockscreenShadeDragProgress() { + /** */ + public float getLockscreenShadeDragProgress() { // mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade // transition. If that's not the case we should follow QS expansion fraction for when // user is pulling from the same top to go directly to expanded QS - return mTransitioningToFullShadeProgress > 0 + return mQsController.getTransitioningToFullShadeProgress() > 0 ? mLockscreenShadeTransitionController.getQSDragProgress() - : computeQsExpansionFraction(); + : mQsController.computeExpansionFraction(); } - private void onStackYChanged(boolean shouldAnimate) { - if (mQs != null) { - if (shouldAnimate) { - animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD, - 0 /* delay */); - mNotificationBoundsAnimationDelay = 0; - } - setQSClippingBounds(); - } - } - - private void onNotificationScrolled(int newScrollPosition) { - updateQSExpansionEnabledAmbient(); - } - - private void updateQSExpansionEnabledAmbient() { - final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight; - mQsExpansionEnabledAmbient = mSplitShadeEnabled - || (mAmbientState.getScrollY() <= scrollRangeToTop); - setQsExpansionEnabled(); - } - - /** - * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping - * as well based on the bounds of the shade and QS state. - */ - private void setQSClippingBounds() { - float qsExpansionFraction = computeQsExpansionFraction(); - final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction); - final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0); - checkCorrectScrimVisibility(qsExpansionFraction); - - int top = calculateTopQsClippingBound(qsPanelBottomY); - int bottom = calculateBottomQsClippingBound(top); - int left = calculateLeftQsClippingBound(); - int right = calculateRightQsClippingBound(); - // top should never be lower than bottom, otherwise it will be invisible. - top = Math.min(top, bottom); - applyQSClippingBounds(left, top, right, bottom, qsVisible); - } - - private void checkCorrectScrimVisibility(float expansionFraction) { - // issues with scrims visible on keyguard occur only in split shade - if (mSplitShadeEnabled) { - boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1; - // expansionFraction == 1 means scrims are fully visible as their size/visibility depend - // on QS expansion - if (expansionFraction == 1 && keyguardViewsVisible) { - Log.wtf(TAG, - "Incorrect state, scrim is visible at the same time when clock is visible"); - } - } - } - - private int calculateTopQsClippingBound(int qsPanelBottomY) { - int top; - if (mSplitShadeEnabled) { - top = Math.min(qsPanelBottomY, mLargeScreenShadeHeaderHeight); - } else { - if (mTransitioningToFullShadeProgress > 0.0f) { - // If we're transitioning, let's use the actual value. The else case - // can be wrong during transitions when waiting for the keyguard to unlock - top = mTransitionToFullShadeQSPosition; - } else { - final float notificationTop = getQSEdgePosition(); - if (isOnKeyguard()) { - if (mKeyguardBypassController.getBypassEnabled()) { - // When bypassing on the keyguard, let's use the panel bottom. - // this should go away once we unify the stackY position and don't have - // to do this min anymore below. - top = qsPanelBottomY; - } else { - top = (int) Math.min(qsPanelBottomY, notificationTop); - } - } else { - top = (int) notificationTop; - } - } - top += mOverStretchAmount; - // Correction for instant expansion caused by HUN pull down/ - if (mMinFraction > 0f && mMinFraction < 1f) { - float realFraction = - (getExpandedFraction() - mMinFraction) / (1f - mMinFraction); - top *= MathUtils.saturate(realFraction / mMinFraction); - } - } - return top; - } - - private int calculateBottomQsClippingBound(int top) { - if (mSplitShadeEnabled) { - return top + mNotificationStackScrollLayoutController.getHeight() - + mSplitShadeNotificationsScrimMarginBottom; - } else { - return mView.getBottom(); - } - } - - private int calculateLeftQsClippingBound() { - if (mIsFullWidth) { - // left bounds can ignore insets, it should always reach the edge of the screen - return 0; - } else { - return mNotificationStackScrollLayoutController.getLeft() + mDisplayLeftInset; - } - } - - private int calculateRightQsClippingBound() { - if (mIsFullWidth) { - return mView.getRight() + mDisplayRightInset; - } else { - return mNotificationStackScrollLayoutController.getRight() + mDisplayLeftInset; - } - } - - /** - * Applies clipping to quick settings, notifications layout and - * updates bounds of the notifications background (notifications scrim). - * - * The parameters are bounds of the notifications area rectangle, this function - * calculates bounds for the QS clipping based on the notifications bounds. - */ - private void applyQSClippingBounds(int left, int top, int right, int bottom, - boolean qsVisible) { - if (!mAnimateNextNotificationBounds || mLastQsClipBounds.isEmpty()) { - if (mQsClippingAnimation != null) { - // update the end position of the animator - mQsClippingAnimationEndBounds.set(left, top, right, bottom); - } else { - applyQSClippingImmediately(left, top, right, bottom, qsVisible); - } - } else { - mQsClippingAnimationEndBounds.set(left, top, right, bottom); - final int startLeft = mLastQsClipBounds.left; - final int startTop = mLastQsClipBounds.top; - final int startRight = mLastQsClipBounds.right; - final int startBottom = mLastQsClipBounds.bottom; - if (mQsClippingAnimation != null) { - mQsClippingAnimation.cancel(); - } - mQsClippingAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); - mQsClippingAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mQsClippingAnimation.setDuration(mNotificationBoundsAnimationDuration); - mQsClippingAnimation.setStartDelay(mNotificationBoundsAnimationDelay); - mQsClippingAnimation.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - int animLeft = (int) MathUtils.lerp(startLeft, - mQsClippingAnimationEndBounds.left, fraction); - int animTop = (int) MathUtils.lerp(startTop, - mQsClippingAnimationEndBounds.top, fraction); - int animRight = (int) MathUtils.lerp(startRight, - mQsClippingAnimationEndBounds.right, fraction); - int animBottom = (int) MathUtils.lerp(startBottom, - mQsClippingAnimationEndBounds.bottom, fraction); - applyQSClippingImmediately(animLeft, animTop, animRight, animBottom, - qsVisible /* qsVisible */); - }); - mQsClippingAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mQsClippingAnimation = null; - mIsQsTranslationResetAnimator = false; - mIsPulseExpansionResetAnimator = false; - } - }); - mQsClippingAnimation.start(); - } - mAnimateNextNotificationBounds = false; - mNotificationBoundsAnimationDelay = 0; - } - - private void applyQSClippingImmediately(int left, int top, int right, int bottom, - boolean qsVisible) { - int radius = mScrimCornerRadius; - boolean clipStatusView = false; - mLastQsClipBounds.set(left, top, right, bottom); - if (mIsFullWidth) { - clipStatusView = qsVisible; - float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius; - radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, - Math.min(top / (float) mScrimCornerRadius, 1f)); - } - if (mQs != null) { - float qsTranslation = 0; - boolean pulseExpanding = mPulseExpansionHandler.isExpanding(); - if (mTransitioningToFullShadeProgress > 0.0f || pulseExpanding - || (mQsClippingAnimation != null - && (mIsQsTranslationResetAnimator || mIsPulseExpansionResetAnimator))) { - if (pulseExpanding || mIsPulseExpansionResetAnimator) { - // qsTranslation should only be positive during pulse expansion because it's - // already translating in from the top - qsTranslation = Math.max(0, (top - mQs.getHeader().getHeight()) / 2.0f); - } else if (!mSplitShadeEnabled) { - qsTranslation = (top - mQs.getHeader().getHeight()) * QS_PARALLAX_AMOUNT; - } - } - mQsTranslationForFullShadeTransition = qsTranslation; - updateQsFrameTranslation(); - float currentTranslation = mQsFrame.getTranslationY(); - mQsClipTop = mEnableQsClipping - ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0; - mQsClipBottom = mEnableQsClipping - ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0; - mQsVisible = qsVisible; - mQs.setQsVisible(mQsVisible); - mQs.setFancyClipping( - mQsClipTop, - mQsClipBottom, - radius, - qsVisible && !mSplitShadeEnabled); - mKeyguardInteractor.setQuickSettingsVisible(mQsVisible); - } - // The padding on this area is large enough that we can use a cheaper clipping strategy - mKeyguardStatusViewController.setClipBounds(clipStatusView ? mLastQsClipBounds : null); - // Increase the height of the notifications scrim when not in split shade - // (e.g. portrait tablet) so the rounded corners are not visible at the bottom, - // in this case they are rendered off-screen - final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius; - mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom); - - if (mSplitShadeEnabled) { - mKeyguardStatusBarViewController.setNoTopClipping(); - } else { - mKeyguardStatusBarViewController.updateTopClipping(top); - } - - mScrimController.setScrimCornerRadius(radius); - - // Convert global clipping coordinates to local ones, - // relative to NotificationStackScrollLayout - int nsslLeft = calculateNsslLeft(left); - int nsslRight = calculateNsslRight(right); - int nsslTop = getNotificationsClippingTopBounds(top); - int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop(); - int bottomRadius = mSplitShadeEnabled ? radius : 0; - int topRadius = mSplitShadeEnabled && mExpandingFromHeadsUp ? 0 : radius; - mNotificationStackScrollLayoutController.setRoundedClippingBounds( - nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius); - } - - private int calculateNsslLeft(int nsslLeftAbsolute) { - int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft(); - if (mIsFullWidth) { - return left; - } - return left - mDisplayLeftInset; - } - - private int calculateNsslRight(int nsslRightAbsolute) { - int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft(); - if (mIsFullWidth) { - return right; - } - return right - mDisplayLeftInset; - } - - private int getNotificationsClippingTopBounds(int qsTop) { - if (mSplitShadeEnabled && mExpandingFromHeadsUp) { - // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need - // to set top clipping bound to negative value to allow HUN to go up to the top edge of - // the screen without clipping. - return -mAmbientState.getStackTopMargin(); - } else { - return qsTop - mNotificationStackScrollLayoutController.getTop(); - } - } - - private float getQSEdgePosition() { - // TODO: replace StackY with unified calculation - return Math.max(mQuickQsHeaderHeight * mAmbientState.getExpansionFraction(), - mAmbientState.getStackY() - // need to adjust for extra margin introduced by large screen shade header - + mAmbientState.getStackTopMargin() * mAmbientState.getExpansionFraction() - - mAmbientState.getScrollY()); - } - - private int calculateQsBottomPosition(float qsExpansionFraction) { - if (mTransitioningToFullShadeProgress > 0.0f) { - return mTransitionToFullShadeQSPosition; - } else if (mSplitShadeEnabled) { - // in split shade - outside lockscreen transition handled above - we simply jump between - // two qs expansion values - either shade is closed and qs expansion is 0 or shade is - // open and qs expansion is 1 - int qsBottomTarget = mQs.getDesiredHeight() + mLargeScreenShadeHeaderHeight; - return qsExpansionFraction > 0 ? qsBottomTarget : 0; - } else { - int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight(); - int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0; - int qsBottomYTo = mQs.getDesiredHeight() + expandedTopMargin; - return (int) MathUtils.lerp(qsBottomYFrom, qsBottomYTo, qsExpansionFraction); - } - } - - private String determineAccessibilityPaneTitle() { - if (mQs != null && mQs.isCustomizing()) { + String determineAccessibilityPaneTitle() { + if (mQsController != null && mQsController.isCustomizing()) { return mResources.getString(R.string.accessibility_desc_quick_settings_edit); - } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) { + } else if (mQsController != null && mQsController.getExpansionHeight() != 0.0f + && mQsController.getFullyExpanded()) { // Upon initialisation when we are not layouted yet we don't want to announce that we // are fully expanded, hence the != 0.0f check. return mResources.getString(R.string.accessibility_desc_quick_settings); @@ -3247,42 +2214,27 @@ public final class NotificationPanelViewController implements Dumpable { } } - float calculateNotificationsTopPadding() { - if (mSplitShadeEnabled) { - return mKeyguardShowing ? getKeyguardNotificationStaticPadding() : 0; + /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */ + public int getKeyguardNotificationStaticPadding() { + if (!getKeyguardShowing()) { + return 0; } - if (mKeyguardShowing && (mQsExpandImmediate - || mIsExpanding && mQsExpandedWhenExpandingStarted)) { - - // Either QS pushes the notifications down when fully expanded, or QS is fully above the - // notifications (mostly on tablets). maxNotificationPadding denotes the normal top - // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings - // panel. We need to take the maximum and linearly interpolate with the panel expansion - // for a nice motion. - int maxNotificationPadding = getKeyguardNotificationStaticPadding(); - int maxQsPadding = mQsMaxExpansionHeight; - int max = mBarState == KEYGUARD ? Math.max( - maxNotificationPadding, maxQsPadding) : maxQsPadding; - return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max, - getExpandedFraction()); - } else if (mQsSizeChangeAnimator != null) { - return Math.max( - (int) mQsSizeChangeAnimator.getAnimatedValue(), - getKeyguardNotificationStaticPadding()); - } else if (mKeyguardShowing) { - // We can only do the smoother transition on Keyguard when we also are not collapsing - // from a scrolled quick settings. - return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(), - (float) (mQsMaxExpansionHeight), - computeQsExpansionFraction()); + if (!mKeyguardBypassController.getBypassEnabled()) { + return mClockPositionResult.stackScrollerPadding; + } + int collapsedPosition = mHeadsUpInset; + if (!mNotificationStackScrollLayoutController.isPulseExpanding()) { + return collapsedPosition; } else { - return mQsFrameTranslateController.getNotificationsTopPadding(mQsExpansionHeight, - mNotificationStackScrollLayoutController); + int expandedPosition = + mClockPositionResult.stackScrollerPadding; + return (int) MathUtils.lerp(collapsedPosition, expandedPosition, + mNotificationStackScrollLayoutController.calculateAppearFractionBypass()); } } public boolean getKeyguardShowing() { - return mKeyguardShowing; + return mBarState == KEYGUARD; } public float getKeyguardNotificationTopPadding() { @@ -3293,93 +2245,17 @@ public final class NotificationPanelViewController implements Dumpable { return mKeyguardNotificationBottomPadding; } - /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */ - private int getKeyguardNotificationStaticPadding() { - if (!mKeyguardShowing) { - return 0; - } - if (!mKeyguardBypassController.getBypassEnabled()) { - return mClockPositionResult.stackScrollerPadding; - } - int collapsedPosition = mHeadsUpInset; - if (!mNotificationStackScrollLayoutController.isPulseExpanding()) { - return collapsedPosition; - } else { - int expandedPosition = mClockPositionResult.stackScrollerPadding; - return (int) MathUtils.lerp(collapsedPosition, expandedPosition, - mNotificationStackScrollLayoutController.calculateAppearFractionBypass()); - } - } - - private void requestScrollerTopPaddingUpdate(boolean animate) { + void requestScrollerTopPaddingUpdate(boolean animate) { mNotificationStackScrollLayoutController.updateTopPadding( - calculateNotificationsTopPadding(), animate); - if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) { + mQsController.calculateNotificationsTopPadding(mIsExpanding, + getKeyguardNotificationStaticPadding(), mExpandedFraction), animate); + if (getKeyguardShowing() + && mKeyguardBypassController.getBypassEnabled()) { // update the position of the header - updateQsExpansion(); + mQsController.updateExpansion(); } } - /** - * Set the amount of pixels we have currently dragged down if we're transitioning to the full - * shade. 0.0f means we're not transitioning yet. - */ - public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) { - if (animate && mIsFullWidth) { - animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, - delay); - mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f; - } - float endPosition = 0; - if (pxAmount > 0.0f) { - if (mSplitShadeEnabled) { - float qsHeight = MathUtils.lerp(mQsMinExpansionHeight, mQsMaxExpansionHeight, - mLockscreenShadeTransitionController.getQSDragProgress()); - setQsExpansionHeight(qsHeight); - } - if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0 - && !mMediaDataManager.hasActiveMediaOrRecommendation()) { - // No notifications are visible, let's animate to the height of qs instead - if (mQs != null) { - // Let's interpolate to the header height instead of the top padding, - // because the toppadding is way too low because of the large clock. - // we still want to take into account the edgePosition though as that nicely - // overshoots in the stackscroller - endPosition = getQSEdgePosition() - - mNotificationStackScrollLayoutController.getTopPadding() - + mQs.getHeader().getHeight(); - } - } else { - // Interpolating to the new bottom edge position! - endPosition = getQSEdgePosition() - + mNotificationStackScrollLayoutController.getFullShadeTransitionInset(); - if (isOnKeyguard()) { - endPosition -= mLockscreenNotificationQSPadding; - } - } - } - - // Calculate the overshoot amount such that we're reaching the target after our desired - // distance, but only reach it fully once we drag a full shade length. - mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( - MathUtils.saturate(pxAmount / mDistanceForQSFullShadeTransition)); - - int position = (int) MathUtils.lerp((float) 0, endPosition, - mTransitioningToFullShadeProgress); - if (mTransitioningToFullShadeProgress > 0.0f) { - // we want at least 1 pixel otherwise the panel won't be clipped - position = Math.max(1, position); - } - mTransitionToFullShadeQSPosition = position; - updateQsExpansion(); - } - - /** Called when pulse expansion has finished and this is going to the full shade. */ - public void onPulseExpansionFinished() { - animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0); - mIsPulseExpansionResetAnimator = true; - } - /** * Set the alpha and translationY of the keyguard elements which only show on the lockscreen, * but not in shade locked / shade. This is used when dragging down to the full shade. @@ -3404,154 +2280,14 @@ public final class NotificationPanelViewController implements Dumpable { mKeyguardStatusBarViewController.setAlpha(alpha); } - private void trackMovement(MotionEvent event) { - if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event); - } - - private void initVelocityTracker() { - if (mQsVelocityTracker != null) { - mQsVelocityTracker.recycle(); - } - mQsVelocityTracker = VelocityTracker.obtain(); - } - - private float getCurrentQSVelocity() { - if (mQsVelocityTracker == null) { - return 0; - } - mQsVelocityTracker.computeCurrentVelocity(1000); - return mQsVelocityTracker.getYVelocity(); - } - - private void cancelQsAnimation() { - if (mQsExpansionAnimator != null) { - mQsExpansionAnimator.cancel(); - } - } - - /** @see #flingSettings(float, int, Runnable, boolean) */ - public void flingSettings(float vel, int type) { - flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */); - } - - /** - * Animates QS or QQS as if the user had swiped up or down. - * - * @param vel Finger velocity or 0 when not initiated by touch events. - * @param type Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link - * #FLING_HIDE}. - * @param onFinishRunnable Runnable to be executed at the end of animation. - * @param isClick If originated by click (different interpolator and duration.) - */ - private void flingSettings(float vel, int type, final Runnable onFinishRunnable, - boolean isClick) { - float target; - switch (type) { - case FLING_EXPAND: - target = mQsMaxExpansionHeight; - break; - case FLING_COLLAPSE: - target = mQsMinExpansionHeight; - break; - case FLING_HIDE: - default: - if (mQs != null) { - mQs.closeDetail(); - } - target = 0; - } - if (target == mQsExpansionHeight) { - if (onFinishRunnable != null) { - onFinishRunnable.run(); - } - traceQsJank(false /* startTracing */, type != FLING_EXPAND /* wasCancelled */); - return; - } - - // If we move in the opposite direction, reset velocity and use a different duration. - boolean oppositeDirection = false; - boolean expanding = type == FLING_EXPAND; - if (vel > 0 && !expanding || vel < 0 && expanding) { - vel = 0; - oppositeDirection = true; - } - ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); - if (isClick) { - animator.setInterpolator(Interpolators.TOUCH_RESPONSE); - animator.setDuration(368); - } else { - mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); - } - if (oppositeDirection) { - animator.setDuration(350); - } - animator.addUpdateListener( - animation -> setQsExpansionHeight((Float) animation.getAnimatedValue())); - animator.addListener(new AnimatorListenerAdapter() { - private boolean mIsCanceled; - - @Override - public void onAnimationStart(Animator animation) { - notifyExpandingStarted(); - } - - @Override - public void onAnimationCancel(Animator animation) { - mIsCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - mQSAnimatingHiddenFromCollapsed = false; - mAnimatingQS = false; - notifyExpandingFinished(); - mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind(); - mQsExpansionAnimator = null; - if (onFinishRunnable != null) { - onFinishRunnable.run(); - } - traceQsJank(false /* startTracing */, mIsCanceled /* wasCancelled */); - } - }); - // Let's note that we're animating QS. Moving the animator here will cancel it immediately, - // so we need a separate flag. - mAnimatingQS = true; - animator.start(); - mQsExpansionAnimator = animator; - mQsAnimatorExpand = expanding; - mQSAnimatingHiddenFromCollapsed = computeQsExpansionFraction() == 0.0f && target == 0; - } - - /** - * @return Whether we should intercept a gesture to open Quick Settings. - */ - private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { - if (!isQsExpansionEnabled() || mCollapsedOnDown - || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) - || mSplitShadeEnabled) { - return false; - } - View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader(); - int frameTop = mKeyguardShowing || mQs == null ? 0 : mQsFrame.getTop(); - mQsInterceptRegion.set( - /* left= */ (int) mQsFrame.getX(), - /* top= */ header.getTop() + frameTop, - /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(), - /* bottom= */ header.getBottom() + frameTop); - // Also allow QS to intercept if the touch is near the notch. - mStatusBarTouchableRegionManager.updateRegionForNotch(mQsInterceptRegion); - final boolean onHeader = mQsInterceptRegion.contains((int) x, (int) y); - - if (mQsExpanded) { - return onHeader || (yDiff < 0 && isInQsArea(x, y)); - } else { - return onHeader; - } + /** */ + public float getKeyguardOnlyContentAlpha() { + return mKeyguardOnlyContentAlpha; } @VisibleForTesting boolean canCollapsePanelOnTouch() { - if (!isInSettings() && mBarState == KEYGUARD) { + if (!mQsController.getExpanded() && mBarState == KEYGUARD) { return true; } @@ -3559,20 +2295,22 @@ public final class NotificationPanelViewController implements Dumpable { return true; } - return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS); + return !mSplitShadeEnabled && (mQsController.getExpanded() || mIsPanelCollapseOnQQS); } int getMaxPanelHeight() { int min = mStatusBarMinHeight; if (!(mBarState == KEYGUARD) && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) { - int minHeight = mQsMinExpansionHeight; + int minHeight = mQsController.getMinExpansionHeight(); min = Math.max(min, minHeight); } int maxHeight; - if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted + if (mQsController.isExpandImmediate() || mQsController.getExpanded() + || mIsExpanding && mQsController.getExpandedWhenExpandingStarted() || mPulsing || mSplitShadeEnabled) { - maxHeight = calculatePanelHeightQsExpanded(); + maxHeight = mQsController.calculatePanelHeightExpanded( + mClockPositionResult.stackScrollerPadding); } else { maxHeight = calculatePanelHeightShade(); } @@ -3580,17 +2318,15 @@ public final class NotificationPanelViewController implements Dumpable { if (maxHeight == 0) { Log.wtf(TAG, "maxPanelHeight is invalid. mOverExpansion: " + mOverExpansion + ", calculatePanelHeightQsExpanded: " - + calculatePanelHeightQsExpanded() + ", calculatePanelHeightShade: " - + calculatePanelHeightShade() + ", mStatusBarMinHeight = " - + mStatusBarMinHeight + ", mQsMinExpansionHeight = " + mQsMinExpansionHeight); + + mQsController.calculatePanelHeightExpanded( + mClockPositionResult.stackScrollerPadding) + + ", calculatePanelHeightShade: " + calculatePanelHeightShade() + + ", mStatusBarMinHeight = " + mStatusBarMinHeight + + ", mQsMinExpansionHeight = " + mQsController.getMinExpansionHeight()); } return maxHeight; } - public boolean isInSettings() { - return mQsExpanded; - } - public boolean isExpanding() { return mIsExpanding; } @@ -3603,7 +2339,8 @@ public final class NotificationPanelViewController implements Dumpable { mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.", mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); } - if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { + if (!mQsController.getExpanded() || mQsController.isExpandImmediate() + || mIsExpanding && mQsController.getExpandedWhenExpandingStarted()) { // Updating the clock position will set the top padding which might // trigger a new panel height and re-position the clock. // This is a circular dependency and should be avoided, otherwise we'll have @@ -3614,14 +2351,8 @@ public final class NotificationPanelViewController implements Dumpable { positionClockAndNotifications(); } } - // Below is true when QS are expanded and we swipe up from the same bottom of panel to - // close the whole shade with one motion. Also this will be always true when closing - // split shade as there QS are always expanded so every collapsing motion is motion from - // expanded QS to closed panel - boolean collapsingShadeFromExpandedQs = mQsExpanded && !mQsTracking - && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll; boolean goingBetweenClosedShadeAndExpandedQs = - mQsExpandImmediate || collapsingShadeFromExpandedQs; + mQsController.isGoingBetweenClosedShadeAndExpandedQs(); // in split shade we react when HUN is visible only if shade height is over HUN start // height - which means user is swiping down. Otherwise shade QS will either not show at all // with HUN movement or it will blink when touching HUN initially @@ -3631,7 +2362,7 @@ public final class NotificationPanelViewController implements Dumpable { float qsExpansionFraction; if (mSplitShadeEnabled) { qsExpansionFraction = 1; - } else if (mKeyguardShowing) { + } else if (getKeyguardShowing()) { // On Keyguard, interpolate the QS expansion linearly to the panel expansion qsExpansionFraction = expandedHeight / (getMaxPanelHeight()); } else { @@ -3640,13 +2371,15 @@ public final class NotificationPanelViewController implements Dumpable { float panelHeightQsCollapsed = mNotificationStackScrollLayoutController.getIntrinsicPadding() + mNotificationStackScrollLayoutController.getLayoutMinHeight(); - float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); + float panelHeightQsExpanded = mQsController.calculatePanelHeightExpanded( + mClockPositionResult.stackScrollerPadding); qsExpansionFraction = (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded - panelHeightQsCollapsed); } - float targetHeight = mQsMinExpansionHeight - + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight); - setQsExpansionHeight(targetHeight); + float targetHeight = mQsController.getMinExpansionHeight() + qsExpansionFraction + * (mQsController.getMaxExpansionHeight() + - mQsController.getMinExpansionHeight()); + mQsController.setExpansionHeight(targetHeight); } updateExpandedHeight(expandedHeight); updateHeader(); @@ -3663,8 +2396,8 @@ public final class NotificationPanelViewController implements Dumpable { if (mPanelExpanded != isExpanded) { mPanelExpanded = isExpanded; mShadeExpansionStateManager.onShadeExpansionFullyChanged(isExpanded); - if (!isExpanded && mQs != null && mQs.isCustomizing()) { - mQs.closeCustomizer(); + if (!isExpanded) { + mQsController.closeQsCustomizer(); } } } @@ -3686,40 +2419,6 @@ public final class NotificationPanelViewController implements Dumpable { } } - int calculatePanelHeightQsExpanded() { - float - notificationHeight = - mNotificationStackScrollLayoutController.getHeight() - - mNotificationStackScrollLayoutController.getEmptyBottomMargin() - - mNotificationStackScrollLayoutController.getTopPadding(); - - // When only empty shade view is visible in QS collapsed state, simulate that we would have - // it in expanded QS state as well so we don't run into troubles when fading the view in/out - // and expanding/collapsing the whole panel from/to quick settings. - if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0 - && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) { - notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight(); - } - int maxQsHeight = mQsMaxExpansionHeight; - - // If an animation is changing the size of the QS panel, take the animated value. - if (mQsSizeChangeAnimator != null) { - maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); - } - float totalHeight = Math.max(maxQsHeight, - mBarState == KEYGUARD ? mClockPositionResult.stackScrollerPadding - : 0) + notificationHeight - + mNotificationStackScrollLayoutController.getTopPaddingOverflow(); - if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) { - float - fullyCollapsedHeight = - maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight(); - totalHeight = Math.max(fullyCollapsedHeight, - mNotificationStackScrollLayoutController.getHeight()); - } - return (int) totalHeight; - } - private void updateNotificationTranslucency() { if (mIsOcclusionTransitionRunning) { return; @@ -3738,10 +2437,10 @@ public final class NotificationPanelViewController implements Dumpable { private float getFadeoutAlpha() { float alpha; - if (mQsMinExpansionHeight == 0) { + if (mQsController.getMinExpansionHeight() == 0) { return 1.0f; } - alpha = getExpandedHeight() / mQsMinExpansionHeight; + alpha = getExpandedHeight() / mQsController.getMinExpansionHeight(); alpha = Math.max(0, Math.min(alpha, 1)); alpha = (float) Math.pow(alpha, 0.75); return alpha; @@ -3752,30 +2451,7 @@ public final class NotificationPanelViewController implements Dumpable { if (mBarState == KEYGUARD) { mKeyguardStatusBarViewController.updateViewState(); } - updateQsExpansion(); - } - - private float getHeaderTranslation() { - if (mSplitShadeEnabled) { - // in split shade QS don't translate, just (un)squish and overshoot - return 0; - } - if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) { - return -mQs.getQsMinExpansionHeight(); - } - float appearAmount = mNotificationStackScrollLayoutController - .calculateAppearFraction(mExpandedHeight); - float startHeight = -mQsExpansionHeight; - if (mBarState == StatusBarState.SHADE) { - // Small parallax as we pull down and clip QS - startHeight = -mQsExpansionHeight * QS_PARALLAX_AMOUNT; - } - if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) { - appearAmount = mNotificationStackScrollLayoutController.calculateAppearFractionBypass(); - startHeight = -mQs.getQsMinExpansionHeight(); - } - float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount)); - return Math.min(0, translation); + mQsController.updateExpansion(); } private void updateKeyguardBottomAreaAlpha() { @@ -3792,28 +2468,12 @@ public final class NotificationPanelViewController implements Dumpable { isUnlockHintRunning() ? 0 : KeyguardBouncerConstants.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, getExpandedFraction()); - float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction()); + float alpha = Math.min(expansionAlpha, 1 - mQsController.computeExpansionFraction()); alpha *= mBottomAreaShadeAlpha; mKeyguardBottomAreaInteractor.setAlpha(alpha); mLockIconViewController.setAlpha(alpha); } - private void onExpandingStarted() { - mNotificationStackScrollLayoutController.onExpansionStarted(); - mIsExpanding = true; - mQsExpandedWhenExpandingStarted = mQsFullyExpanded; - mMediaHierarchyManager.setCollapsingShadeFromQS(mQsExpandedWhenExpandingStarted && - /* We also start expanding when flinging closed Qs. Let's exclude that */ - !mAnimatingQS); - if (mQsExpanded) { - onQsExpansionStarted(); - } - // Since there are QS tiles in the header now, we need to make sure we start listening - // immediately so they can be up to date. - if (mQs == null) return; - mQs.setHeaderListening(true); - } - private void onExpandingFinished() { if (!mUnocclusionTransitionFlagEnabled) { mScrimController.onExpandingFinished(); @@ -3823,7 +2483,7 @@ public final class NotificationPanelViewController implements Dumpable { mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed()); mIsExpanding = false; mMediaHierarchyManager.setCollapsingShadeFromQS(false); - mMediaHierarchyManager.setQsExpanded(mQsExpanded); + mMediaHierarchyManager.setQsExpanded(mQsController.getExpanded()); if (isFullyCollapsed()) { DejankUtils.postAfterTraversal(() -> setListening(false)); @@ -3838,10 +2498,10 @@ public final class NotificationPanelViewController implements Dumpable { if (mBarState != SHADE) { // updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but // on keyguard panel state is always OPEN so we need to have that extra update - setQsExpandImmediate(false); + mQsController.setExpandImmediate(false); } setShowShelfOnly(false); - mTwoFingerQsExpandPossible = false; + mQsController.setTwoFingerExpandPossible(false); updateTrackingHeadsUp(null); mExpandingFromHeadsUp = false; setPanelScrimMinFraction(0.0f); @@ -3864,8 +2524,7 @@ public final class NotificationPanelViewController implements Dumpable { private void setListening(boolean listening) { mKeyguardStatusBarViewController.setBatteryListening(listening); - if (mQs == null) return; - mQs.setListening(listening); + mQsController.setListening(listening); } public void expand(boolean animate) { @@ -3899,7 +2558,7 @@ public final class NotificationPanelViewController implements Dumpable { this); if (mAnimateAfterExpanding) { notifyExpandingStarted(); - beginJankMonitoring(); + mQsController.beginJankMonitoring(isFullyCollapsed()); fling(0 /* expand */); } else { mShadeHeightLogger.logFunctionCall("expand"); @@ -3928,16 +2587,10 @@ public final class NotificationPanelViewController implements Dumpable { mOverExpansion = overExpansion; // Translating the quick settings by half the overexpansion to center it in the background // frame - updateQsFrameTranslation(); + mQsController.updateQsFrameTranslation(); mNotificationStackScrollLayoutController.setOverExpansion(overExpansion); } - private void updateQsFrameTranslation() { - mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs, - mNavigationBarBottomHeight + mAmbientState.getStackTopMargin()); - - } - private void falsingAdditionalTapRequired() { if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { mTapAgainViewController.show(); @@ -3964,8 +2617,8 @@ public final class NotificationPanelViewController implements Dumpable { notifyExpandingStarted(); updatePanelExpansionAndVisibility(); mScrimController.onTrackingStarted(); - if (mQsFullyExpanded) { - setQsExpandImmediate(true); + if (mQsController.getFullyExpanded()) { + mQsController.setExpandImmediate(true); setShowShelfOnly(true); } mNotificationStackScrollLayoutController.onPanelTrackingStarted(); @@ -4042,8 +2695,8 @@ public final class NotificationPanelViewController implements Dumpable { // the required distance to be a specific and constant value, to make sure the expansion // motion has the expected speed. We also only want this on non-lockscreen for now. if (mSplitShadeEnabled && mBarState == SHADE) { - boolean transitionFromHeadsUp = - mHeadsUpManager.isTrackingHeadsUp() || mExpandingFromHeadsUp; + boolean transitionFromHeadsUp = (mHeadsUpManager != null + && mHeadsUpManager.isTrackingHeadsUp()) || mExpandingFromHeadsUp; // heads-up starting height is too close to mSplitShadeFullTransitionDistance and // when dragging HUN transition is already 90% complete. It makes shade become // immediately visible when starting to drag. We want to set distance so that @@ -4061,25 +2714,6 @@ public final class NotificationPanelViewController implements Dumpable { } } - @VisibleForTesting - boolean isTrackingBlocked() { - return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch; - } - - public boolean isQsExpanded() { - return mQsExpanded; - } - - /** Returns whether the QS customizer is currently active. */ - public boolean isQsCustomizing() { - return mQs.isCustomizing(); - } - - /** Close the QS customizer if it is open. */ - public void closeQsCustomizer() { - mQs.closeCustomizer(); - } - public void setIsLaunchAnimationRunning(boolean running) { boolean wasRunning = mIsLaunchAnimationRunning; mIsLaunchAnimationRunning = running; @@ -4104,14 +2738,6 @@ public final class NotificationPanelViewController implements Dumpable { } } - public void setQsScrimEnabled(boolean qsScrimEnabled) { - boolean changed = mQsScrimEnabled != qsScrimEnabled; - mQsScrimEnabled = qsScrimEnabled; - if (changed) { - updateQsState(); - } - } - public void onScreenTurningOn() { mKeyguardStatusViewController.dozeTimeTick(); } @@ -4140,7 +2766,7 @@ public final class NotificationPanelViewController implements Dumpable { } break; case StatusBarState.SHADE_LOCKED: - if (!mQsExpanded) { + if (!mQsController.getExpanded()) { mStatusBarStateController.setState(KEYGUARD); } break; @@ -4289,66 +2915,6 @@ public final class NotificationPanelViewController implements Dumpable { return !mShowIconsWhenExpanded; } - private void onQsPanelScrollChanged(int scrollY) { - mLargeScreenShadeHeaderController.setQsScrollY(scrollY); - if (scrollY > 0 && !mQsFullyExpanded) { - debugLog("Scrolling while not expanded. Forcing expand"); - // If we are scrolling QS, we should be fully expanded. - expandWithQs(); - } - } - - private final class QsFragmentListener implements FragmentListener { - @Override - public void onFragmentViewCreated(String tag, Fragment fragment) { - mQs = (QS) fragment; - mQs.setPanelView(mHeightListener); - mQs.setCollapseExpandAction(mCollapseExpandAction); - mQs.setHeaderClickable(isQsExpansionEnabled()); - mQs.setOverscrolling(mStackScrollerOverscrolling); - mQs.setInSplitShade(mSplitShadeEnabled); - mQs.setIsNotificationPanelFullWidth(mIsFullWidth); - - // recompute internal state when qspanel height changes - mQs.getView().addOnLayoutChangeListener( - (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { - final int height = bottom - top; - final int oldHeight = oldBottom - oldTop; - if (height != oldHeight) { - onQsHeightChanged(); - } - }); - mQs.setCollapsedMediaVisibilityChangedListener((visible) -> { - if (mQs.getHeader().isShown()) { - animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD, - 0 /* delay */); - mNotificationStackScrollLayoutController.animateNextTopPaddingChange(); - } - }); - mLockscreenShadeTransitionController.setQS(mQs); - mShadeTransitionController.setQs(mQs); - mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader()); - mQs.setScrollListener(mQsScrollListener); - updateQsExpansion(); - } - - @Override - public void onFragmentViewDestroyed(String tag, Fragment fragment) { - // Manual handling of fragment lifecycle is only required because this bridges - // non-fragment and fragment code. Once we are using a fragment for the notification - // panel, mQs will not need to be null cause it will be tied to the same lifecycle. - if (fragment == mQs) { - mQs = null; - } - } - } - - private void animateNextNotificationBounds(long duration, long delay) { - mAnimateNextNotificationBounds = true; - mNotificationBoundsAnimationDuration = duration; - mNotificationBoundsAnimationDelay = delay; - } - public void setTouchAndAnimationDisabled(boolean disabled) { mTouchDisabled = disabled; if (mTouchDisabled) { @@ -4371,9 +2937,11 @@ public final class NotificationPanelViewController implements Dumpable { if (dozing == mDozing) return; mView.setDozing(dozing); mDozing = dozing; + // TODO (b/) make listeners for this mNotificationStackScrollLayoutController.setDozing(mDozing, animate); mKeyguardBottomAreaInteractor.setAnimateDozingTransitions(animate); mKeyguardStatusBarViewController.setDozing(mDozing); + mQsController.setDozing(mDozing); if (dozing) { mBottomAreaShadeAlphaAnimator.cancel(); @@ -4564,33 +3132,13 @@ public final class NotificationPanelViewController implements Dumpable { ipw.print("mMaxAllowedKeyguardNotifications="); ipw.println(mMaxAllowedKeyguardNotifications); ipw.print("mAnimateNextPositionUpdate="); ipw.println(mAnimateNextPositionUpdate); - ipw.print("mQuickQsHeaderHeight="); ipw.println(mQuickQsHeaderHeight); - ipw.print("mQsTrackingPointer="); ipw.println(mQsTrackingPointer); - ipw.print("mQsTracking="); ipw.println(mQsTracking); - ipw.print("mConflictingQsExpansionGesture="); ipw.println(mConflictingQsExpansionGesture); ipw.print("mPanelExpanded="); ipw.println(mPanelExpanded); - ipw.print("mQsExpanded="); ipw.println(mQsExpanded); - ipw.print("mQsExpandedWhenExpandingStarted="); ipw.println(mQsExpandedWhenExpandingStarted); - ipw.print("mQsFullyExpanded="); ipw.println(mQsFullyExpanded); - ipw.print("mKeyguardShowing="); ipw.println(mKeyguardShowing); ipw.print("mKeyguardQsUserSwitchEnabled="); ipw.println(mKeyguardQsUserSwitchEnabled); ipw.print("mKeyguardUserSwitcherEnabled="); ipw.println(mKeyguardUserSwitcherEnabled); ipw.print("mDozing="); ipw.println(mDozing); ipw.print("mDozingOnDown="); ipw.println(mDozingOnDown); ipw.print("mBouncerShowing="); ipw.println(mBouncerShowing); ipw.print("mBarState="); ipw.println(mBarState); - ipw.print("mInitialHeightOnTouch="); ipw.println(mInitialHeightOnTouch); - ipw.print("mInitialTouchX="); ipw.println(mInitialTouchX); - ipw.print("mInitialTouchY="); ipw.println(mInitialTouchY); - ipw.print("mQsExpansionHeight="); ipw.println(mQsExpansionHeight); - ipw.print("mQsMinExpansionHeight="); ipw.println(mQsMinExpansionHeight); - ipw.print("mQsMaxExpansionHeight="); ipw.println(mQsMaxExpansionHeight); - ipw.print("mQsPeekHeight="); ipw.println(mQsPeekHeight); - ipw.print("mStackScrollerOverscrolling="); ipw.println(mStackScrollerOverscrolling); - ipw.print("mQsExpansionFromOverscroll="); ipw.println(mQsExpansionFromOverscroll); - ipw.print("mLastOverscroll="); ipw.println(mLastOverscroll); - ipw.print("mQsExpansionEnabledPolicy="); ipw.println(mQsExpansionEnabledPolicy); - ipw.print("mQsExpansionEnabledAmbient="); ipw.println(mQsExpansionEnabledAmbient); ipw.print("mStatusBarMinHeight="); ipw.println(mStatusBarMinHeight); ipw.print("mStatusBarHeaderHeightKeyguard="); ipw.println(mStatusBarHeaderHeightKeyguard); ipw.print("mOverStretchAmount="); ipw.println(mOverStretchAmount); @@ -4599,17 +3147,8 @@ public final class NotificationPanelViewController implements Dumpable { ipw.print("mDisplayTopInset="); ipw.println(mDisplayTopInset); ipw.print("mDisplayRightInset="); ipw.println(mDisplayRightInset); ipw.print("mDisplayLeftInset="); ipw.println(mDisplayLeftInset); - ipw.print("mLargeScreenShadeHeaderHeight="); ipw.println(mLargeScreenShadeHeaderHeight); - ipw.print("mSplitShadeNotificationsScrimMarginBottom="); - ipw.println(mSplitShadeNotificationsScrimMarginBottom); ipw.print("mIsExpanding="); ipw.println(mIsExpanding); - ipw.print("mQsExpandImmediate="); ipw.println(mQsExpandImmediate); - ipw.print("mTwoFingerQsExpandPossible="); ipw.println(mTwoFingerQsExpandPossible); ipw.print("mHeaderDebugInfo="); ipw.println(mHeaderDebugInfo); - ipw.print("mQsAnimatorExpand="); ipw.println(mQsAnimatorExpand); - ipw.print("mQsScrimEnabled="); ipw.println(mQsScrimEnabled); - ipw.print("mQsTouchAboveFalsingThreshold="); ipw.println(mQsTouchAboveFalsingThreshold); - ipw.print("mQsFalsingThreshold="); ipw.println(mQsFalsingThreshold); ipw.print("mHeadsUpStartHeight="); ipw.println(mHeadsUpStartHeight); ipw.print("mListenForHeadsUp="); ipw.println(mListenForHeadsUp); ipw.print("mNavigationBarBottomHeight="); ipw.println(mNavigationBarBottomHeight); @@ -4625,7 +3164,6 @@ public final class NotificationPanelViewController implements Dumpable { ipw.println(mBlockingExpansionForCurrentTouch); ipw.print("mExpectingSynthesizedDown="); ipw.println(mExpectingSynthesizedDown); ipw.print("mLastEventSynthesizedDown="); ipw.println(mLastEventSynthesizedDown); - ipw.print("mLastFlingWasExpanding="); ipw.println(mLastFlingWasExpanding); ipw.print("mInterpolatedDarkAmount="); ipw.println(mInterpolatedDarkAmount); ipw.print("mLinearDarkAmount="); ipw.println(mLinearDarkAmount); ipw.print("mPulsing="); ipw.println(mPulsing); @@ -4636,40 +3174,14 @@ public final class NotificationPanelViewController implements Dumpable { ipw.print("mHeadsUpInset="); ipw.println(mHeadsUpInset); ipw.print("mHeadsUpPinnedMode="); ipw.println(mHeadsUpPinnedMode); ipw.print("mAllowExpandForSmallExpansion="); ipw.println(mAllowExpandForSmallExpansion); - ipw.print("mLockscreenNotificationQSPadding="); - ipw.println(mLockscreenNotificationQSPadding); - ipw.print("mTransitioningToFullShadeProgress="); - ipw.println(mTransitioningToFullShadeProgress); - ipw.print("mTransitionToFullShadeQSPosition="); - ipw.println(mTransitionToFullShadeQSPosition); - ipw.print("mDistanceForQSFullShadeTransition="); - ipw.println(mDistanceForQSFullShadeTransition); - ipw.print("mQsTranslationForFullShadeTransition="); - ipw.println(mQsTranslationForFullShadeTransition); ipw.print("mMaxOverscrollAmountForPulse="); ipw.println(mMaxOverscrollAmountForPulse); - ipw.print("mAnimateNextNotificationBounds="); ipw.println(mAnimateNextNotificationBounds); - ipw.print("mNotificationBoundsAnimationDelay="); - ipw.println(mNotificationBoundsAnimationDelay); - ipw.print("mNotificationBoundsAnimationDuration="); - ipw.println(mNotificationBoundsAnimationDuration); ipw.print("mIsPanelCollapseOnQQS="); ipw.println(mIsPanelCollapseOnQQS); - ipw.print("mAnimatingQS="); ipw.println(mAnimatingQS); - ipw.print("mIsQsTranslationResetAnimator="); ipw.println(mIsQsTranslationResetAnimator); - ipw.print("mIsPulseExpansionResetAnimator="); ipw.println(mIsPulseExpansionResetAnimator); ipw.print("mKeyguardOnlyContentAlpha="); ipw.println(mKeyguardOnlyContentAlpha); ipw.print("mKeyguardOnlyTransitionTranslationY="); ipw.println(mKeyguardOnlyTransitionTranslationY); ipw.print("mUdfpsMaxYBurnInOffset="); ipw.println(mUdfpsMaxYBurnInOffset); ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation); ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection); - ipw.print("mScrimCornerRadius="); ipw.println(mScrimCornerRadius); - ipw.print("mScreenCornerRadius="); ipw.println(mScreenCornerRadius); - ipw.print("mQSAnimatingHiddenFromCollapsed="); ipw.println(mQSAnimatingHiddenFromCollapsed); - ipw.print("mUseLargeScreenShadeHeader="); ipw.println(mUseLargeScreenShadeHeader); - ipw.print("mEnableQsClipping="); ipw.println(mEnableQsClipping); - ipw.print("mQsClipTop="); ipw.println(mQsClipTop); - ipw.print("mQsClipBottom="); ipw.println(mQsClipBottom); - ipw.print("mQsVisible="); ipw.println(mQsVisible); ipw.print("mMinFraction="); ipw.println(mMinFraction); ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered); ipw.print("mSplitShadeFullTransitionDistance="); @@ -4854,12 +3366,12 @@ public final class NotificationPanelViewController implements Dumpable { public void updateSystemUiStateFlags() { if (SysUiState.DEBUG) { Log.d(TAG, "Updating panel sysui state flags: fullyExpanded=" - + isFullyExpanded() + " inQs=" + isInSettings()); + + isFullyExpanded() + " inQs=" + mQsController.getExpanded()); } mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED, - isFullyExpanded() && !isInSettings()) - .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isFullyExpanded() && isInSettings()) - .commitUpdate(mDisplayId); + isFullyExpanded() && !mQsController.getExpanded()) + .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, + isFullyExpanded() && mQsController.getExpanded()).commitUpdate(mDisplayId); } private void debugLog(String fmt, Object... args) { @@ -4872,11 +3384,11 @@ public final class NotificationPanelViewController implements Dumpable { void notifyExpandingStarted() { if (!mExpanding) { mExpanding = true; - onExpandingStarted(); + mIsExpanding = true; + mQsController.onExpandingStarted(mQsController.getFullyExpanded()); } } - @VisibleForTesting void notifyExpandingFinished() { endClosing(); if (mExpanding) { @@ -4885,7 +3397,7 @@ public final class NotificationPanelViewController implements Dumpable { } } - private float getTouchSlop(MotionEvent event) { + float getTouchSlop(MotionEvent event) { // Adjust the touch slop if another gesture may be being performed. return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE ? mTouchSlop * mSlopMultiplier @@ -4959,7 +3471,7 @@ public final class NotificationPanelViewController implements Dumpable { public void startExpandMotion(float newX, float newY, boolean startTracking, float expandedHeight) { if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) { - beginJankMonitoring(); + mQsController.beginJankMonitoring(isFullyCollapsed()); } mInitialOffsetOnTouch = expandedHeight; if (!mTracking || isFullyCollapsed()) { @@ -5121,7 +3633,8 @@ public final class NotificationPanelViewController implements Dumpable { setExpandedHeightInternal(height); } - private void updateExpandedHeightToMaxHeight() { + /** Try to set expanded height to max. */ + void updateExpandedHeightToMaxHeight() { float currentMaxPanelHeight = getMaxPanelHeight(); if (isFullyCollapsed()) { @@ -5132,7 +3645,8 @@ public final class NotificationPanelViewController implements Dumpable { return; } - if (mTracking && !isTrackingBlocked()) { + if (mTracking && !(mBlockingExpansionForCurrentTouch + || mQsController.isTrackingBlocked())) { return; } @@ -5174,6 +3688,7 @@ public final class NotificationPanelViewController implements Dumpable { mHeightAnimator.end(); } } + mQsController.setShadeExpandedHeight(mExpandedHeight); mExpansionDragDownAmountPx = h; mExpandedFraction = Math.min(1f, maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight); @@ -5213,7 +3728,7 @@ public final class NotificationPanelViewController implements Dumpable { return mExpandedHeight; } - private float getExpandedFraction() { + float getExpandedFraction() { return mExpandedFraction; } @@ -5244,7 +3759,7 @@ public final class NotificationPanelViewController implements Dumpable { return true; } else { // case of two finger swipe from the top of keyguard - return computeQsExpansionFraction() == 1; + return mQsController.computeExpansionFraction() == 1; } } @@ -5342,7 +3857,7 @@ public final class NotificationPanelViewController implements Dumpable { } /** Returns whether a shade or QS expansion animation is running */ - private boolean isShadeOrQsHeightAnimationRunning() { + public boolean isShadeOrQsHeightAnimationRunning() { return mHeightAnimator != null && !mHintAnimationRunning && !mIsSpringBackAnimation; } @@ -5477,44 +3992,127 @@ public final class NotificationPanelViewController implements Dumpable { return mView.isEnabled(); } - private void beginJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.Configuration.Builder builder = - InteractionJankMonitor.Configuration.Builder.withView( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, - mView) - .setTag(isFullyCollapsed() ? "Expand" : "Collapse"); - mInteractionJankMonitor.begin(builder); + int getDisplayRightInset() { + return mDisplayRightInset; } - private void endJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.getInstance().end( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + int getDisplayLeftInset() { + return mDisplayLeftInset; } - private void cancelJankMonitoring() { - if (mInteractionJankMonitor == null) { - return; - } - InteractionJankMonitor.getInstance().cancel( - InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + float getOverStretchAmount() { + return mOverStretchAmount; + } + + float getMinFraction() { + return mMinFraction; + } + + boolean getCollapsedOnDown() { + return mCollapsedOnDown; + } + + int getNavigationBarBottomHeight() { + return mNavigationBarBottomHeight; + } + + boolean isExpandingFromHeadsUp() { + return mExpandingFromHeadsUp; + } + + /** TODO: remove need for this delegate (b/254870148) */ + public void closeQs() { + mQsController.closeQs(); + } + + /** TODO: remove need for this delegate (b/254870148) */ + public void setQsScrimEnabled(boolean qsScrimEnabled) { + mQsController.setScrimEnabled(qsScrimEnabled); } private ShadeExpansionStateManager getShadeExpansionStateManager() { return mShadeExpansionStateManager; } + private void onQsExpansionChanged(boolean expanded) { + updateExpandedHeightToMaxHeight(); + setStatusAccessibilityImportance(expanded + ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + updateSystemUiStateFlags(); + NavigationBarView navigationBarView = + mNavigationBarController.getNavigationBarView(mDisplayId); + if (navigationBarView != null) { + navigationBarView.onStatusBarPanelStateChanged(); + } + } + + @VisibleForTesting + void onQsSetExpansionHeightCalled(boolean qsFullyExpanded) { + requestScrollerTopPaddingUpdate(false); + mKeyguardStatusBarViewController.updateViewState(); + int barState = getBarState(); + if (barState == SHADE_LOCKED || barState == KEYGUARD) { + updateKeyguardBottomAreaAlpha(); + positionClockAndNotifications(); + } + + if (mAccessibilityManager.isEnabled()) { + mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); + } + + if (!mFalsingManager.isUnlockingDisabled() && qsFullyExpanded + && mFalsingCollector.shouldEnforceBouncer()) { + mCentralSurfaces.executeRunnableDismissingKeyguard(null, null, + false, true, false); + } + if (DEBUG_DRAWABLE) { + mView.invalidate(); + } + } + + private void onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling) { + if (mKeyguardUserSwitcherController != null && qsExpanded + && !isStackScrollerOverscrolling) { + mKeyguardUserSwitcherController.closeSwitcherIfOpenAndNotSimple(true); + } + } + + private void onQsClippingImmediatelyApplied(boolean clipStatusView, + Rect lastQsClipBounds, int top, boolean qsFragmentCreated, boolean qsVisible) { + if (qsFragmentCreated) { + mKeyguardInteractor.setQuickSettingsVisible(qsVisible); + } + + // The padding on this area is large enough that + // we can use a cheaper clipping strategy + mKeyguardStatusViewController.setClipBounds( + clipStatusView ? lastQsClipBounds : null); + if (mSplitShadeEnabled) { + mKeyguardStatusBarViewController.setNoTopClipping(); + } else { + mKeyguardStatusBarViewController.updateTopClipping(top); + } + } + + private void onFlingQsWithoutClick(ValueAnimator animator, float qsExpansionHeight, + float target, float vel) { + mFlingAnimationUtils.apply(animator, qsExpansionHeight, target, vel); + } + + private void onExpansionHeightSetToMax(boolean requestPaddingUpdate) { + if (requestPaddingUpdate) { + requestScrollerTopPaddingUpdate(false /* animate */); + } + updateExpandedHeightToMaxHeight(); + } + private final class NsslHeightChangedListener implements ExpandableView.OnHeightChangedListener { @Override public void onHeightChanged(ExpandableView view, boolean needsAnimation) { // Block update if we are in QS and just the top padding changed (i.e. view == null). - if (view == null && mQsExpanded) { + if (view == null && mQsController.getExpanded()) { return; } if (needsAnimation && mInterpolatedDarkAmount == 0) { @@ -5530,7 +4128,7 @@ public final class NotificationPanelViewController implements Dumpable { == firstRow))) { requestScrollerTopPaddingUpdate(false /* animate */); } - if (mKeyguardShowing) { + if (getKeyguardShowing()) { updateMaxDisplayedNotifications(true); } updateExpandedHeightToMaxHeight(); @@ -5540,62 +4138,6 @@ public final class NotificationPanelViewController implements Dumpable { public void onReset(ExpandableView view) {} } - private void collapseOrExpand() { - onQsExpansionStarted(); - if (mQsExpanded) { - flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */, - true /* isClick */); - } else if (isQsExpansionEnabled()) { - mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0); - flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */, - true /* isClick */); - } - } - - private final class NsslOverscrollTopChangedListener implements - NotificationStackScrollLayout.OnOverscrollTopChangedListener { - @Override - public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { - // When in split shade, overscroll shouldn't carry through to QS - if (mSplitShadeEnabled) { - return; - } - cancelQsAnimation(); - if (!isQsExpansionEnabled()) { - amount = 0f; - } - float rounded = amount >= 1f ? amount : 0f; - setOverScrolling(rounded != 0f && isRubberbanded); - mQsExpansionFromOverscroll = rounded != 0f; - mLastOverscroll = rounded; - updateQsState(); - setQsExpansionHeight(mQsMinExpansionHeight + rounded); - } - - @Override - public void flingTopOverscroll(float velocity, boolean open) { - // in split shade touches affect QS only when touch happens within QS - if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) { - return; - } - mLastOverscroll = 0f; - mQsExpansionFromOverscroll = false; - if (open) { - // During overscrolling, qsExpansion doesn't actually change that the qs is - // becoming expanded. Any layout could therefore reset the position again. Let's - // make sure we can expand - setOverScrolling(false); - } - setQsExpansionHeight(mQsExpansionHeight); - boolean canExpand = isQsExpansionEnabled(); - flingSettings(!canExpand && open ? 0f : velocity, - open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> { - setOverScrolling(false); - updateQsState(); - }, false /* isClick */); - } - } - private void onDynamicPrivacyChanged() { // Do not request animation when pulsing or waking up, otherwise the clock will be out // of sync with the notification panel. @@ -5645,19 +4187,6 @@ public final class NotificationPanelViewController implements Dumpable { } } - private void onQsHeightChanged() { - mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0; - if (mQsExpanded && mQsFullyExpanded) { - mQsExpansionHeight = mQsMaxExpansionHeight; - requestScrollerTopPaddingUpdate(false /* animate */); - updateExpandedHeightToMaxHeight(); - } - if (mAccessibilityManager.isEnabled()) { - mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); - } - mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight); - } - private final class ConfigurationListener implements ConfigurationController.ConfigurationListener { @Override @@ -5733,8 +4262,9 @@ public final class NotificationPanelViewController implements Dumpable { setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); + // TODO: maybe add a listener for barstate mBarState = statusBarState; - mKeyguardShowing = keyguardShowing; + mQsController.setBarState(statusBarState); boolean fromShadeToKeyguard = statusBarState == KEYGUARD && (oldState == SHADE || oldState == SHADE_LOCKED); @@ -5742,7 +4272,7 @@ public final class NotificationPanelViewController implements Dumpable { // user can go to keyguard from different shade states and closing animation // may not fully run - we always want to make sure we close QS when that happens // as we never need QS open in fresh keyguard state - closeQs(); + mQsController.closeQs(); } if (oldState == KEYGUARD && (goingToFullShade @@ -5758,7 +4288,7 @@ public final class NotificationPanelViewController implements Dumpable { duration = StackStateAnimator.ANIMATION_DURATION_STANDARD; } mKeyguardStatusBarViewController.animateKeyguardStatusBarOut(startDelay, duration); - updateQSMinHeight(); + mQsController.updateMinHeight(); } else if (oldState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { mKeyguardStatusBarViewController.animateKeyguardStatusBarIn(); @@ -5786,9 +4316,7 @@ public final class NotificationPanelViewController implements Dumpable { keyguardShowing ? View.VISIBLE : View.INVISIBLE); } if (keyguardShowing && oldState != mBarState) { - if (mQs != null) { - mQs.hideImmediately(); - } + mQsController.hideQsImmediately(); } } mKeyguardStatusBarViewController.updateForHeadsUp(); @@ -5800,7 +4328,7 @@ public final class NotificationPanelViewController implements Dumpable { // The update needs to happen after the headerSlide in above, otherwise the translation // would reset maybeAnimateBottomAreaAlpha(); - updateQsState(); + mQsController.updateQsState(); } @Override @@ -5877,7 +4405,7 @@ public final class NotificationPanelViewController implements Dumpable { @Override public void onViewAttachedToWindow(View v) { mFragmentService.getFragmentHostManager(mView) - .addTagListener(QS.TAG, mQsFragmentListener); + .addTagListener(QS.TAG, mQsController.getQsFragmentListener()); mStatusBarStateController.addCallback(mStatusBarStateListener); mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState()); mConfigurationController.addCallback(mConfigurationListener); @@ -5894,7 +4422,7 @@ public final class NotificationPanelViewController implements Dumpable { public void onViewDetachedFromWindow(View v) { mContentResolver.unregisterContentObserver(mSettingsChangeObserver); mFragmentService.getFragmentHostManager(mView) - .removeTagListener(QS.TAG, mQsFragmentListener); + .removeTagListener(QS.TAG, mQsController.getQsFragmentListener()); mStatusBarStateController.removeCallback(mStatusBarStateListener); mConfigurationController.removeCallback(mConfigurationListener); mFalsingManager.removeTapListener(mFalsingTapListener); @@ -5919,28 +4447,9 @@ public final class NotificationPanelViewController implements Dumpable { // Update Clock Pivot (used by anti-burnin transformations) mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight()); - // Calculate quick setting heights. - int oldMaxHeight = mQsMaxExpansionHeight; - if (mQs != null) { - updateQSMinHeight(); - mQsMaxExpansionHeight = mQs.getDesiredHeight(); - mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight); - } + int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange(); positionClockAndNotifications(); - if (mQsExpanded && mQsFullyExpanded) { - mQsExpansionHeight = mQsMaxExpansionHeight; - requestScrollerTopPaddingUpdate(false /* animate */); - updateExpandedHeightToMaxHeight(); - - // Size has changed, start an animation. - if (mQsMaxExpansionHeight != oldMaxHeight) { - startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight); - } - } else if (!mQsExpanded && mQsExpansionAnimator == null) { - setQsExpansionHeight(mQsMinExpansionHeight + mLastOverscroll); - } else { - mShadeLog.v("onLayoutChange: qs expansion not set"); - } + mQsController.handleShadeLayoutChanged(oldMaxHeight); updateExpandedHeight(getExpandedHeight()); updateHeader(); @@ -5949,9 +4458,8 @@ public final class NotificationPanelViewController implements Dumpable { // container the desired height so when closing the QS detail, it stays smaller after // the size change animation is finished but the detail view is still being animated // away (this animation takes longer than the size change animation). - if (mQsSizeChangeAnimator == null && mQs != null) { - mQs.setHeightOverride(mQs.getDesiredHeight()); - } + mQsController.setHeightOverrideToDesiredHeight(); + updateMaxHeadsUpTranslation(); updateGestureExclusionRect(); if (mExpandAfterLayoutRunnable != null) { @@ -5962,18 +4470,6 @@ public final class NotificationPanelViewController implements Dumpable { } } - private void updateQSMinHeight() { - float previousMin = mQsMinExpansionHeight; - if (mKeyguardShowing || mSplitShadeEnabled) { - mQsMinExpansionHeight = 0; - } else { - mQsMinExpansionHeight = mQs.getQsMinExpansionHeight(); - } - if (mQsExpansionHeight == previousMin) { - mQsExpansionHeight = mQsMinExpansionHeight; - } - } - @NonNull private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) { // the same types of insets that are handled in NotificationShadeWindowView @@ -5982,6 +4478,7 @@ public final class NotificationPanelViewController implements Dumpable { mDisplayTopInset = combinedInsets.top; mDisplayRightInset = combinedInsets.right; mDisplayLeftInset = combinedInsets.left; + mQsController.setDisplayInsets(mDisplayRightInset, mDisplayLeftInset); mNavigationBarBottomHeight = insets.getStableInsetBottom(); updateMaxHeadsUpTranslation(); @@ -5994,10 +4491,10 @@ public final class NotificationPanelViewController implements Dumpable { } private void onPanelStateChanged(@PanelState int state) { - updateQSExpansionEnabledAmbient(); + mQsController.updateExpansionEnabledAmbient(); if (state == STATE_OPEN && mCurrentPanelState != state) { - setQsExpandImmediate(false); + mQsController.setExpandImmediate(false); mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } if (state == STATE_OPENING) { @@ -6005,12 +4502,12 @@ public final class NotificationPanelViewController implements Dumpable { // to locked will trigger this event and we're not actually in the process of opening // the shade, lockscreen is just always expanded if (mSplitShadeEnabled && !isOnKeyguard()) { - setQsExpandImmediate(true); + mQsController.setExpandImmediate(true); } mOpenCloseListener.onOpenStarted(); } if (state == STATE_CLOSED) { - setQsExpandImmediate(false); + mQsController.setExpandImmediate(false); // Close the status bar in the next frame so we can show the end of the // animation. mView.post(mMaybeHideExpandedRunnable); @@ -6076,7 +4573,7 @@ public final class NotificationPanelViewController implements Dumpable { /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */ public boolean onInterceptTouchEvent(MotionEvent event) { mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent"); - if (mQs.disallowPanelTouches()) { + if (mQsController.disallowTouches()) { mShadeLog.logMotionEvent(event, "NPVC not intercepting touch, panel touches disallowed"); return false; @@ -6098,14 +4595,14 @@ public final class NotificationPanelViewController implements Dumpable { + "HeadsUpTouchHelper"); return true; } - if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) + if (!mQsController.shouldQuickSettingsIntercept(mDownX, mDownY, 0) && mPulseExpansionHandler.onInterceptTouchEvent(event)) { mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + "PulseExpansionHandler"); return true; } - if (!isFullyCollapsed() && onQsIntercept(event)) { + if (!isFullyCollapsed() && mQsController.onIntercept(event)) { debugLog("onQsIntercept true"); mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + "QsIntercept"); @@ -6239,8 +4736,7 @@ public final class NotificationPanelViewController implements Dumpable { mLastTouchDownTime = event.getDownTime(); } - - if (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches()) { + if (mQsController.isFullyExpandedAndTouchesDisallowed()) { mShadeLog.logMotionEvent(event, "onTouch: ignore touch, panel touches disallowed and qs fully expanded"); return false; @@ -6271,7 +4767,7 @@ public final class NotificationPanelViewController implements Dumpable { // If pulse is expanding already, let's give it the touch. There are situations // where the panel starts expanding even though we're also pulsing boolean pulseShouldGetTouch = (!mIsExpanding - && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)) + && !mQsController.shouldQuickSettingsIntercept(mDownX, mDownY, 0)) || mPulseExpansionHandler.isExpanding(); if (pulseShouldGetTouch && mPulseExpansionHandler.onTouchEvent(event)) { // We're expanding all the other ones shouldn't get this anymore @@ -6289,7 +4785,8 @@ public final class NotificationPanelViewController implements Dumpable { } boolean handled = mHeadsUpTouchHelper.onTouchEvent(event); - if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { + if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch( + event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) { mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event"); return true; } @@ -6444,7 +4941,9 @@ public final class NotificationPanelViewController implements Dumpable { mTouchAboveFalsingThreshold = true; mUpwardsWhenThresholdReached = isDirectionUpwards(x, y); } - if ((!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) { + if ((!mGestureWaitForTouchSlop || mTracking) + && !(mBlockingExpansionForCurrentTouch + || mQsController.isTrackingBlocked())) { // Count h==0 as part of swipe-up, // otherwise {@link NotificationStackScrollLayout} // wrongly enables stack height updates at the start of lockscreen swipe-up @@ -6462,9 +4961,9 @@ public final class NotificationPanelViewController implements Dumpable { // mHeightAnimator is null, there is no remaining frame, ends instrumenting. if (mHeightAnimator == null) { if (event.getActionMasked() == MotionEvent.ACTION_UP) { - endJankMonitoring(); + mQsController.endJankMonitoring(); } else { - cancelJankMonitoring(); + mQsController.cancelJankMonitoring(); } } break; diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java new file mode 100644 index 0000000000000..c0ef4c1a872ae --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -0,0 +1,2050 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.android.systemui.shade; + +import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; +import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; +import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS; +import static com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE; +import static com.android.systemui.shade.NotificationPanelViewController.FLING_EXPAND; +import static com.android.systemui.shade.NotificationPanelViewController.FLING_HIDE; +import static com.android.systemui.shade.NotificationPanelViewController.QS_PARALLAX_AMOUNT; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.app.Fragment; +import android.content.res.Resources; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.Log; +import android.util.MathUtils; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; +import android.widget.FrameLayout; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.policy.SystemBarUtils; +import com.android.keyguard.FaceAuthApiRequestReason; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; +import com.android.systemui.classifier.Classifier; +import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.media.controls.pipeline.MediaDataManager; +import com.android.systemui.media.controls.ui.MediaHierarchyManager; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.shade.transition.ShadeTransitionController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.PulseExpansionHandler; +import com.android.systemui.statusbar.QsFrameTranslateController; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.stack.AmbientState; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.StackStateAnimator; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.LargeScreenUtils; + +import javax.inject.Inject; + +import dagger.Lazy; + +/** Handles QuickSettings touch handling, expansion and animation state + * TODO (b/264460656) make this dumpable + */ +@CentralSurfacesComponent.CentralSurfacesScope +public class QuickSettingsController { + public static final String TAG = "QuickSettingsController"; + + private QS mQs; + private final Lazy mPanelViewControllerLazy; + + private final NotificationPanelView mPanelView; + private final KeyguardStatusBarView mKeyguardStatusBar; + private final FrameLayout mQsFrame; + + private final QsFrameTranslateController mQsFrameTranslateController; + private final ShadeTransitionController mShadeTransitionController; + private final PulseExpansionHandler mPulseExpansionHandler; + private final ShadeExpansionStateManager mShadeExpansionStateManager; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; + private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final NotificationShadeDepthController mDepthController; + private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; + private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; + private final KeyguardStateController mKeyguardStateController; + private final KeyguardBypassController mKeyguardBypassController; + private final NotificationRemoteInputManager mRemoteInputManager; + private VelocityTracker mQsVelocityTracker; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final ScrimController mScrimController; + private final MediaDataManager mMediaDataManager; + private final MediaHierarchyManager mMediaHierarchyManager; + private final AmbientState mAmbientState; + private final RecordingController mRecordingController; + private final FalsingCollector mFalsingCollector; + private final LockscreenGestureLogger mLockscreenGestureLogger; + private final ShadeLogger mShadeLog; + private final FeatureFlags mFeatureFlags; + private final InteractionJankMonitor mInteractionJankMonitor; + private final FalsingManager mFalsingManager; + private final AccessibilityManager mAccessibilityManager; + private final MetricsLogger mMetricsLogger; + private final Resources mResources; + + /** Whether the notifications are displayed full width (no margins on the side). */ + private boolean mIsFullWidth; + private int mTouchSlop; + private float mSlopMultiplier; + /** the current {@link StatusBarState} */ + private int mBarState; + private int mStatusBarMinHeight; + private boolean mScrimEnabled = true; + private int mScrimCornerRadius; + private int mScreenCornerRadius; + private boolean mUseLargeScreenShadeHeader; + private int mLargeScreenShadeHeaderHeight; + private int mDisplayRightInset = 0; // in pixels + private int mDisplayLeftInset = 0; // in pixels + private boolean mSplitShadeEnabled; + /** + * The padding between the start of notifications and the qs boundary on the lockscreen. + * On lockscreen, notifications aren't inset this extra amount, but we still want the + * qs boundary to be padded. + */ + private int mLockscreenNotificationPadding; + private int mSplitShadeNotificationsScrimMarginBottom; + private boolean mDozing; + private boolean mEnableClipping; + private int mFalsingThreshold; + /** + * Position of the qs bottom during the full shade transition. This is needed as the toppadding + * can change during state changes, which makes it much harder to do animations + */ + private int mTransitionToFullShadePosition; + private boolean mCollapsedOnDown; + private float mShadeExpandedHeight = 0; + private boolean mLastShadeFlingWasExpanding; + + private float mInitialHeightOnTouch; + private float mInitialTouchX; + private float mInitialTouchY; + /** whether current touch Y delta is above falsing threshold */ + private boolean mTouchAboveFalsingThreshold; + /** whether we are tracking a touch on QS container */ + private boolean mTracking; + /** pointerId of the pointer we're currently tracking */ + private int mTrackingPointer; + + /** + * Indicates that QS is in expanded state which can happen by: + * - single pane shade: expanding shade and then expanding QS + * - split shade: just expanding shade (QS are expanded automatically) + */ + private boolean mExpanded; + /** Indicates QS is at its max height */ + private boolean mFullyExpanded; + /** + * Determines if QS should be already expanded when expanding shade. + * Used for split shade, two finger gesture as well as accessibility shortcut to QS. + * It needs to be set when movement starts as it resets at the end of expansion/collapse. + */ + private boolean mExpandImmediate; + private boolean mExpandedWhenExpandingStarted; + private boolean mAnimatingHiddenFromCollapsed; + private boolean mVisible; + private float mExpansionHeight; + private int mMinExpansionHeight; + private int mMaxExpansionHeight; + /** Expansion fraction of the notification shade */ + private float mShadeExpandedFraction; + private int mPeekHeight; + private float mLastOverscroll; + private boolean mExpansionFromOverscroll; + private boolean mExpansionEnabledPolicy = true; + private boolean mExpansionEnabledAmbient = true; + private float mQuickQsHeaderHeight; + /** + * Determines if QS should be already expanded when expanding shade. + * Used for split shade, two finger gesture as well as accessibility shortcut to QS. + * It needs to be set when movement starts as it resets at the end of expansion/collapse. + */ + private boolean mTwoFingerExpandPossible; + /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */ + private boolean mConflictingExpansionGesture; + /** + * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still + * need to take this into account in our panel height calculation. + */ + private boolean mAnimatorExpand; + + /** + * The amount of progress we are currently in if we're transitioning to the full shade. + * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full + * shade. This value can also go beyond 1.1 when we're overshooting! + */ + private float mTransitioningToFullShadeProgress; + /** Distance a full shade transition takes in order for qs to fully transition to the shade. */ + private int mDistanceForFullShadeTransition; + private boolean mStackScrollerOverscrolling; + /** Indicates QS is animating - set by flingQs */ + private boolean mAnimating; + /** Whether the current animator is resetting the qs translation. */ + private boolean mIsTranslationResettingAnimator; + /** Whether the current animator is resetting the pulse expansion after a drag down. */ + private boolean mIsPulseExpansionResettingAnimator; + /** The translation amount for QS for the full shade transition. */ + private float mTranslationForFullShadeTransition; + /** Should we animate the next bounds update. */ + private boolean mAnimateNextNotificationBounds; + /** The delay for the next bounds animation. */ + private long mNotificationBoundsAnimationDelay; + /** The duration of the notification bounds animation. */ + private long mNotificationBoundsAnimationDuration; + + private final Region mInterceptRegion = new Region(); + /** The end bounds of a clipping animation. */ + private final Rect mClippingAnimationEndBounds = new Rect(); + private final Rect mLastClipBounds = new Rect(); + + /** The animator for the qs clipping bounds. */ + private ValueAnimator mClippingAnimator = null; + /** The main animator for QS expansion */ + private ValueAnimator mExpansionAnimator; + /** The animator for QS size change */ + private ValueAnimator mSizeChangeAnimator; + + private ExpansionHeightListener mExpansionHeightListener; + private QsStateUpdateListener mQsStateUpdateListener; + private ApplyClippingImmediatelyListener mApplyClippingImmediatelyListener; + private FlingQsWithoutClickListener mFlingQsWithoutClickListener; + private ExpansionHeightSetToMaxListener mExpansionHeightSetToMaxListener; + private final QS.HeightListener mQsHeightListener = this::onHeightChanged; + private final Runnable mQsCollapseExpandAction = this::collapseOrExpandQs; + private final QS.ScrollListener mQsScrollListener = this::onScroll; + + @Inject + public QuickSettingsController( + Lazy panelViewControllerLazy, + NotificationPanelView panelView, + QsFrameTranslateController qsFrameTranslateController, + ShadeTransitionController shadeTransitionController, + PulseExpansionHandler pulseExpansionHandler, + NotificationRemoteInputManager remoteInputManager, + ShadeExpansionStateManager shadeExpansionStateManager, + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + NotificationStackScrollLayoutController notificationStackScrollLayoutController, + LockscreenShadeTransitionController lockscreenShadeTransitionController, + NotificationShadeDepthController notificationShadeDepthController, + LargeScreenShadeHeaderController largeScreenShadeHeaderController, + 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 + ) { + mPanelViewControllerLazy = panelViewControllerLazy; + mPanelView = panelView; + mQsFrame = mPanelView.findViewById(R.id.qs_frame); + mKeyguardStatusBar = mPanelView.findViewById(R.id.keyguard_header); + mResources = mPanelView.getResources(); + mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources); + mQsFrameTranslateController = qsFrameTranslateController; + mShadeTransitionController = shadeTransitionController; + mPulseExpansionHandler = pulseExpansionHandler; + pulseExpansionHandler.setPulseExpandAbortListener(() -> { + if (mQs != null) { + mQs.animateHeaderSlidingOut(); + } + }); + mRemoteInputManager = remoteInputManager; + mShadeExpansionStateManager = shadeExpansionStateManager; + mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; + mLockscreenShadeTransitionController = lockscreenShadeTransitionController; + mDepthController = notificationShadeDepthController; + mLargeScreenShadeHeaderController = largeScreenShadeHeaderController; + mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; + mKeyguardStateController = keyguardStateController; + mKeyguardBypassController = keyguardBypassController; + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mScrimController = scrimController; + mMediaDataManager = mediaDataManager; + mMediaHierarchyManager = mediaHierarchyManager; + mAmbientState = ambientState; + mRecordingController = recordingController; + mFalsingManager = falsingManager; + mFalsingCollector = falsingCollector; + mAccessibilityManager = accessibilityManager; + + mLockscreenGestureLogger = lockscreenGestureLogger; + mMetricsLogger = metricsLogger; + mShadeLog = shadeLog; + mFeatureFlags = featureFlags; + mInteractionJankMonitor = interactionJankMonitor; + + mShadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged); + mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback()); + } + + @VisibleForTesting + void setQs(QS qs) { + mQs = qs; + } + + public void setExpansionHeightListener(ExpansionHeightListener listener) { + mExpansionHeightListener = listener; + } + + public void setQsStateUpdateListener(QsStateUpdateListener listener) { + mQsStateUpdateListener = listener; + } + + public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) { + mApplyClippingImmediatelyListener = listener; + } + + public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) { + mFlingQsWithoutClickListener = listener; + } + + public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) { + mExpansionHeightSetToMaxListener = callback; + } + + void loadDimens() { + final ViewConfiguration configuration = ViewConfiguration.get(this.mPanelView.getContext()); + mTouchSlop = configuration.getScaledTouchSlop(); + mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); + mPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height); + mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mPanelView.getContext()); + mScrimCornerRadius = mResources.getDimensionPixelSize( + R.dimen.notification_scrim_corner_radius); + mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius( + mPanelView.getContext()); + mFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold); + mLockscreenNotificationPadding = mResources.getDimensionPixelSize( + R.dimen.notification_side_paddings); + mDistanceForFullShadeTransition = mResources.getDimensionPixelSize( + R.dimen.lockscreen_shade_qs_transition_distance); + } + + void updateResources() { + mSplitShadeEnabled = LargeScreenUtils.shouldUseSplitNotificationShade(mResources); + if (mQs != null) { + mQs.setInSplitShade(mSplitShadeEnabled); + } + mSplitShadeNotificationsScrimMarginBottom = + mResources.getDimensionPixelSize( + R.dimen.split_shade_notifications_scrim_margin_bottom); + + mUseLargeScreenShadeHeader = + LargeScreenUtils.shouldUseLargeScreenShadeHeader(mPanelView.getResources()); + mLargeScreenShadeHeaderHeight = + mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height); + int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : + mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top); + mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader); + mAmbientState.setStackTopMargin(topMargin); + + // TODO: When the flag is eventually removed, it means that we have a single view that is + // the same height in QQS and in Large Screen (large_screen_shade_header_height). Eventually + // the concept of largeScreenHeader or quickQsHeader will disappear outside of the class + // that controls the view as the offset needs to be the same regardless. + if (mUseLargeScreenShadeHeader || mFeatureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)) { + mQuickQsHeaderHeight = mLargeScreenShadeHeaderHeight; + } else { + mQuickQsHeaderHeight = SystemBarUtils.getQuickQsOffsetHeight(mPanelView.getContext()); + } + + mEnableClipping = mResources.getBoolean(R.bool.qs_enable_clipping); + } + + // TODO (b/265054088): move this and others to a CoreStartable + void initNotificationStackScrollLayoutController() { + mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( + new NsslOverscrollTopChangedListener()); + mNotificationStackScrollLayoutController.setOnStackYChanged(this::onStackYChanged); + mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); + } + + private void onStackYChanged(boolean shouldAnimate) { + if (isQsFragmentCreated()) { + if (shouldAnimate) { + setAnimateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_STANDARD, + 0 /* delay */); + } + setClippingBounds(); + } + } + + private void onNotificationScrolled(int newScrollPosition) { + updateExpansionEnabledAmbient(); + } + + int getHeaderHeight() { + return mQs.getHeader().getHeight(); + } + + /** Returns the padding of the stackscroller when unlocked */ + int getUnlockedStackScrollerPadding() { + return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight; + } + + public boolean isExpansionEnabled() { + return mExpansionEnabledPolicy && mExpansionEnabledAmbient + && !mRemoteInputManager.isRemoteInputActive(); + } + + public float getTransitioningToFullShadeProgress() { + return mTransitioningToFullShadeProgress; + } + + /** */ + @VisibleForTesting + boolean isExpandImmediate() { + return mExpandImmediate; + } + + float getInitialTouchY() { + return mInitialTouchY; + } + + /** Returns whether split shade is enabled and an x coordinate is outside of the QS frame. */ + private boolean isSplitShadeAndTouchXOutsideQs(float touchX) { + return mSplitShadeEnabled && touchX < mQsFrame.getX() + || touchX > mQsFrame.getX() + mQsFrame.getWidth(); + } + + /** Returns whether touch is within QS area */ + private boolean isTouchInQsArea(float x, float y) { + if (isSplitShadeAndTouchXOutsideQs(x)) { + return false; + } + // TODO (b/265193930): remove dependency on NPVC + // Let's reject anything at the very bottom around the home handle in gesture nav + if (mPanelViewControllerLazy.get().isInGestureNavHomeHandleArea(x, y)) { + return false; + } + return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom() + || y <= mQs.getView().getY() + mQs.getView().getHeight(); + } + + /** Returns whether or not event should open QS */ + private boolean isOpenQsEvent(MotionEvent event) { + final int pointerCount = event.getPointerCount(); + final int action = event.getActionMasked(); + + final boolean + twoFingerDrag = + action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2; + + final boolean + stylusButtonClickDrag = + action == MotionEvent.ACTION_DOWN && (event.isButtonPressed( + MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed( + MotionEvent.BUTTON_STYLUS_SECONDARY)); + + final boolean + mouseButtonClickDrag = + action == MotionEvent.ACTION_DOWN && (event.isButtonPressed( + MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed( + MotionEvent.BUTTON_TERTIARY)); + + return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; + } + + + public boolean getExpanded() { + return mExpanded; + } + + @VisibleForTesting + boolean isTracking() { + return mTracking; + } + + public boolean getFullyExpanded() { + return mFullyExpanded; + } + + boolean isGoingBetweenClosedShadeAndExpandedQs() { + // Below is true when QS are expanded and we swipe up from the same bottom of panel to + // close the whole shade with one motion. Also this will be always true when closing + // split shade as there QS are always expanded so every collapsing motion is motion from + // expanded QS to closed panel + return mExpandImmediate || (mExpanded + && !mTracking && !isExpansionAnimating() + && !mExpansionFromOverscroll); + } + + private boolean isQsFragmentCreated() { + return mQs != null; + } + + public boolean isCustomizing() { + return isQsFragmentCreated() && mQs.isCustomizing(); + } + + public float getExpansionHeight() { + return mExpansionHeight; + } + + public boolean getExpandedWhenExpandingStarted() { + return mExpandedWhenExpandingStarted; + } + + public int getMinExpansionHeight() { + return mMinExpansionHeight; + } + + public boolean isFullyExpandedAndTouchesDisallowed() { + return isQsFragmentCreated() && getFullyExpanded() && disallowTouches(); + } + + public int getMaxExpansionHeight() { + return mMaxExpansionHeight; + } + + private boolean isQsFalseTouch() { + if (mFalsingManager.isClassifierEnabled()) { + return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS); + } + return !mTouchAboveFalsingThreshold; + } + + public int getFalsingThreshold() { + return mFalsingThreshold; + } + + /** + * Returns Whether we should intercept a gesture to open Quick Settings. + */ + public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { + boolean keyguardShowing = mBarState == KEYGUARD; + if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing + && mKeyguardBypassController.getBypassEnabled()) || mSplitShadeEnabled) { + return false; + } + View header = keyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader(); + int frameTop = keyguardShowing + || mQs == null ? 0 : mQsFrame.getTop(); + mInterceptRegion.set( + /* left= */ (int) mQsFrame.getX(), + /* top= */ header.getTop() + frameTop, + /* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(), + /* bottom= */ header.getBottom() + frameTop); + // Also allow QS to intercept if the touch is near the notch. + mStatusBarTouchableRegionManager.updateRegionForNotch(mInterceptRegion); + final boolean onHeader = mInterceptRegion.contains((int) x, (int) y); + + if (getExpanded()) { + return onHeader || (yDiff < 0 && isTouchInQsArea(x, y)); + } else { + return onHeader; + } + } + + /** Returns amount header should be translated */ + private float getHeaderTranslation() { + if (mSplitShadeEnabled) { + // in split shade QS don't translate, just (un)squish and overshoot + return 0; + } + if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) { + return -mQs.getQsMinExpansionHeight(); + } + float appearAmount = mNotificationStackScrollLayoutController + .calculateAppearFraction(mShadeExpandedHeight); + float startHeight = -getExpansionHeight(); + if (mBarState == StatusBarState.SHADE) { + // Small parallax as we pull down and clip QS + startHeight = -getExpansionHeight() * QS_PARALLAX_AMOUNT; + } + if (mKeyguardBypassController.getBypassEnabled() && mBarState == KEYGUARD) { + appearAmount = mNotificationStackScrollLayoutController.calculateAppearFractionBypass(); + startHeight = -mQs.getQsMinExpansionHeight(); + } + float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount)); + return Math.min(0, translation); + } + + /** + * Can the panel collapse in this motion because it was started on QQS? + * + * @param downX the x location where the touch started + * @param downY the y location where the touch started + * Returns true if the panel could be collapsed because it stared on QQS + */ + public boolean canPanelCollapseOnQQS(float downX, float downY) { + if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) { + return false; + } + View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader(); + return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth() + && downY <= header.getBottom(); + } + + /** Closes the Qs customizer. */ + public void closeQsCustomizer() { + mQs.closeCustomizer(); + } + + /** Returns whether touches from the notification panel should be disallowed */ + public boolean disallowTouches() { + return mQs.disallowPanelTouches(); + } + + void setListening(boolean listening) { + if (mQs != null) { + mQs.setListening(listening); + } + } + + void hideQsImmediately() { + if (mQs != null) { + mQs.hideImmediately(); + } + } + + public void setDozing(boolean dozing) { + mDozing = dozing; + } + + /** set QS state to closed */ + public void closeQs() { + cancelExpansionAnimation(); + setExpansionHeight(getMinExpansionHeight()); + // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the + // middle of animation - we need to make sure that value is always false when shade if + // fully collapsed or expanded + setExpandImmediate(false); + } + + @VisibleForTesting + void setExpanded(boolean expanded) { + boolean changed = mExpanded != expanded; + if (changed) { + mExpanded = expanded; + updateQsState(); + mShadeExpansionStateManager.onQsExpansionChanged(expanded); + mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded, + getMinExpansionHeight(), getMaxExpansionHeight(), + mStackScrollerOverscrolling, mAnimatorExpand, mAnimating); + } + } + + void setLastShadeFlingWasExpanding(boolean expanding) { + mLastShadeFlingWasExpanding = expanding; + mShadeLog.logLastFlingWasExpanding(expanding); + } + + /** update Qs height state */ + public void setExpansionHeight(float height) { + int maxHeight = getMaxExpansionHeight(); + height = Math.min(Math.max( + height, getMinExpansionHeight()), maxHeight); + mFullyExpanded = height == maxHeight && maxHeight != 0; + boolean qsAnimatingAway = !mAnimatorExpand && mAnimating; + if (height > getMinExpansionHeight() && !getExpanded() + && !mStackScrollerOverscrolling + && !mDozing && !qsAnimatingAway) { + setExpanded(true); + } else if (height <= getMinExpansionHeight() + && getExpanded()) { + setExpanded(false); + } + mExpansionHeight = height; + updateExpansion(); + + if (mExpansionHeightListener != null) { + mExpansionHeightListener.onQsSetExpansionHeightCalled(getFullyExpanded()); + } + } + + /** */ + public void setHeightOverrideToDesiredHeight() { + if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) { + mQs.setHeightOverride(mQs.getDesiredHeight()); + } + } + + /** Updates quick setting heights and returns old max height. */ + int updateHeightsOnShadeLayoutChange() { + int oldMaxHeight = getMaxExpansionHeight(); + if (isQsFragmentCreated()) { + updateMinHeight(); + mMaxExpansionHeight = mQs.getDesiredHeight(); + mNotificationStackScrollLayoutController.setMaxTopPadding( + getMaxExpansionHeight()); + } + return oldMaxHeight; + } + + /** Called when Shade view layout changed. Updates QS expansion or + * starts size change animation if height has changed. */ + void handleShadeLayoutChanged(int oldMaxHeight) { + if (mExpanded && mFullyExpanded) { + mExpansionHeight = mMaxExpansionHeight; + if (mExpansionHeightSetToMaxListener != null) { + mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true); + } + + // Size has changed, start an animation. + if (getMaxExpansionHeight() != oldMaxHeight) { + startSizeChangeAnimation(oldMaxHeight, + getMaxExpansionHeight()); + } + } else if (!getExpanded() + && !isExpansionAnimating()) { + setExpansionHeight(getMinExpansionHeight() + mLastOverscroll); + } else { + mShadeLog.v("onLayoutChange: qs expansion not set"); + } + } + + private boolean isSizeChangeAnimationRunning() { + return mSizeChangeAnimator != null; + } + + private void startSizeChangeAnimation(int oldHeight, final int newHeight) { + if (mSizeChangeAnimator != null) { + oldHeight = (int) mSizeChangeAnimator.getAnimatedValue(); + mSizeChangeAnimator.cancel(); + } + mSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight); + mSizeChangeAnimator.setDuration(300); + mSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mSizeChangeAnimator.addUpdateListener(animation -> { + if (mExpansionHeightSetToMaxListener != null) { + mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true); + } + + int height = (int) mSizeChangeAnimator.getAnimatedValue(); + mQs.setHeightOverride(height); + }); + mSizeChangeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mSizeChangeAnimator = null; + } + }); + mSizeChangeAnimator.start(); + } + + void setNotificationPanelFullWidth(boolean isFullWidth) { + mIsFullWidth = isFullWidth; + if (mQs != null) { + mQs.setIsNotificationPanelFullWidth(isFullWidth); + } + } + + void setBarState(int barState) { + mBarState = barState; + } + + /** */ + public void setExpansionEnabledPolicy(boolean expansionEnabledPolicy) { + mExpansionEnabledPolicy = expansionEnabledPolicy; + if (mQs != null) { + mQs.setHeaderClickable(isExpansionEnabled()); + } + } + + private void setOverScrolling(boolean overscrolling) { + mStackScrollerOverscrolling = overscrolling; + if (mQs != null) { + mQs.setOverscrolling(overscrolling); + } + } + + /** Sets Qs ScrimEnabled and updates QS state. */ + public void setScrimEnabled(boolean scrimEnabled) { + boolean changed = mScrimEnabled != scrimEnabled; + mScrimEnabled = scrimEnabled; + if (changed) { + updateQsState(); + } + } + + void setCollapsedOnDown(boolean collapsedOnDown) { + mCollapsedOnDown = collapsedOnDown; + } + + void setShadeExpandedHeight(float shadeExpandedHeight) { + mShadeExpandedHeight = shadeExpandedHeight; + } + + @VisibleForTesting + float getShadeExpandedHeight() { + return mShadeExpandedHeight; + } + + @VisibleForTesting + void setExpandImmediate(boolean expandImmediate) { + if (expandImmediate != mExpandImmediate) { + mExpandImmediate = expandImmediate; + mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate); + } + } + + void setTwoFingerExpandPossible(boolean expandPossible) { + mTwoFingerExpandPossible = expandPossible; + } + + /** Called when Qs starts expanding */ + private void onExpansionStarted() { + cancelExpansionAnimation(); + // TODO (b/265193930): remove dependency on NPVC + mPanelViewControllerLazy.get().cancelHeightAnimator(); + // end + + // Reset scroll position and apply that position to the expanded height. + float height = mExpansionHeight; + setExpansionHeight(height); + mNotificationStackScrollLayoutController.checkSnoozeLeavebehind(); + + // When expanding QS, let's authenticate the user if possible, + // this will speed up notification actions. + if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) { + mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED); + } + } + + void updateQsState() { + boolean qsFullScreen = mExpanded && !mSplitShadeEnabled; + mNotificationStackScrollLayoutController.setQsFullScreen(qsFullScreen); + mNotificationStackScrollLayoutController.setScrollingEnabled( + mBarState != KEYGUARD && (!qsFullScreen || mExpansionFromOverscroll)); + + if (mQsStateUpdateListener != null) { + mQsStateUpdateListener.onQsStateUpdated(mExpanded, mStackScrollerOverscrolling); + } + + if (mQs == null) return; + mQs.setExpanded(mExpanded); + } + + /** update expanded state of QS */ + public void updateExpansion() { + if (mQs == null) return; + final float squishiness; + if ((mExpandImmediate || mExpanded) && !mSplitShadeEnabled) { + squishiness = 1; + } else if (mTransitioningToFullShadeProgress > 0.0f) { + squishiness = mLockscreenShadeTransitionController.getQsSquishTransitionFraction(); + } else { + squishiness = mNotificationStackScrollLayoutController + .getNotificationSquishinessFraction(); + } + final float qsExpansionFraction = computeExpansionFraction(); + final float adjustedExpansionFraction = mSplitShadeEnabled + ? 1f : computeExpansionFraction(); + mQs.setQsExpansion( + adjustedExpansionFraction, + mShadeExpandedFraction, + getHeaderTranslation(), + squishiness + ); + mMediaHierarchyManager.setQsExpansion(qsExpansionFraction); + int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction); + mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY); + setClippingBounds(); + + if (mSplitShadeEnabled) { + // In split shade we want to pretend that QS are always collapsed so their behaviour and + // interactions don't influence notifications as they do in portrait. But we want to set + // 0 explicitly in case we're rotating from non-split shade with QS expansion of 1. + mNotificationStackScrollLayoutController.setQsExpansionFraction(0); + } else { + mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction); + } + + mDepthController.setQsPanelExpansion(qsExpansionFraction); + mStatusBarKeyguardViewManager.setQsExpansion(qsExpansionFraction); + + // TODO (b/265193930): remove dependency on NPVC + float shadeExpandedFraction = mBarState == KEYGUARD + ? mPanelViewControllerLazy.get().getLockscreenShadeDragProgress() + : mShadeExpandedFraction; + mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction); + mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction); + mLargeScreenShadeHeaderController.setQsVisible(mVisible); + } + + /** */ + public void updateExpansionEnabledAmbient() { + final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight; + mExpansionEnabledAmbient = mSplitShadeEnabled + || (mAmbientState.getScrollY() <= scrollRangeToTop); + if (mQs != null) { + mQs.setHeaderClickable(isExpansionEnabled()); + } + } + + /** Calculate y value of bottom of QS */ + private int calculateBottomPosition(float qsExpansionFraction) { + if (mTransitioningToFullShadeProgress > 0.0f) { + return mTransitionToFullShadePosition; + } else { + int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight(); + int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0; + int qsBottomYTo = mQs.getDesiredHeight() + expandedTopMargin; + return (int) MathUtils.lerp(qsBottomYFrom, qsBottomYTo, qsExpansionFraction); + } + } + + /** Calculate fraction of current QS expansion state */ + public float computeExpansionFraction() { + if (mAnimatingHiddenFromCollapsed) { + // When hiding QS from collapsed state, the expansion can sometimes temporarily + // be larger than 0 because of the timing, leading to flickers. + return 0.0f; + } + return Math.min( + 1f, (mExpansionHeight - mMinExpansionHeight) / (mMaxExpansionHeight + - mMinExpansionHeight)); + } + + void updateMinHeight() { + float previousMin = mMinExpansionHeight; + if (mBarState == KEYGUARD || mSplitShadeEnabled) { + mMinExpansionHeight = 0; + } else { + mMinExpansionHeight = mQs.getQsMinExpansionHeight(); + } + if (mExpansionHeight == previousMin) { + mExpansionHeight = mMinExpansionHeight; + } + } + + void updateQsFrameTranslation() { + // TODO (b/265193930): remove dependency on NPVC + mQsFrameTranslateController.translateQsFrame(mQsFrame, mQs, + mPanelViewControllerLazy.get().getNavigationBarBottomHeight() + + mAmbientState.getStackTopMargin()); + } + + /** Called when shade starts expanding. */ + public void onExpandingStarted(boolean qsFullyExpanded) { + mNotificationStackScrollLayoutController.onExpansionStarted(); + mExpandedWhenExpandingStarted = qsFullyExpanded; + mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted + /* We also start expanding when flinging closed Qs. Let's exclude that */ + && !mAnimating); + if (mExpanded) { + onExpansionStarted(); + } + // Since there are QS tiles in the header now, we need to make sure we start listening + // immediately so they can be up to date. + if (mQs == null) return; + mQs.setHeaderListening(true); + } + + /** Set animate next notification bounds. */ + private void setAnimateNextNotificationBounds(long duration, long delay) { + mAnimateNextNotificationBounds = true; + mNotificationBoundsAnimationDuration = duration; + mNotificationBoundsAnimationDelay = delay; + } + + /** + * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping + * as well based on the bounds of the shade and QS state. + */ + private void setClippingBounds() { + float qsExpansionFraction = computeExpansionFraction(); + final int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction); + final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0); + checkCorrectScrimVisibility(qsExpansionFraction); + + int top = calculateTopClippingBound(qsPanelBottomY); + int bottom = calculateBottomClippingBound(top); + int left = calculateLeftClippingBound(); + int right = calculateRightClippingBound(); + // top should never be lower than bottom, otherwise it will be invisible. + top = Math.min(top, bottom); + applyClippingBounds(left, top, right, bottom, qsVisible); + } + + /** + * Applies clipping to quick settings, notifications layout and + * updates bounds of the notifications background (notifications scrim). + * + * The parameters are bounds of the notifications area rectangle, this function + * calculates bounds for the QS clipping based on the notifications bounds. + */ + private void applyClippingBounds(int left, int top, int right, int bottom, + boolean qsVisible) { + if (!mAnimateNextNotificationBounds || mLastClipBounds.isEmpty()) { + if (mClippingAnimator != null) { + // update the end position of the animator + mClippingAnimationEndBounds.set(left, top, right, bottom); + } else { + applyClippingImmediately(left, top, right, bottom, qsVisible); + } + } else { + mClippingAnimationEndBounds.set(left, top, right, bottom); + final int startLeft = mLastClipBounds.left; + final int startTop = mLastClipBounds.top; + final int startRight = mLastClipBounds.right; + final int startBottom = mLastClipBounds.bottom; + if (mClippingAnimator != null) { + mClippingAnimator.cancel(); + } + mClippingAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); + mClippingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); + mClippingAnimator.setDuration(mNotificationBoundsAnimationDuration); + mClippingAnimator.setStartDelay(mNotificationBoundsAnimationDelay); + mClippingAnimator.addUpdateListener(animation -> { + float fraction = animation.getAnimatedFraction(); + int animLeft = (int) MathUtils.lerp(startLeft, + mClippingAnimationEndBounds.left, fraction); + int animTop = (int) MathUtils.lerp(startTop, + mClippingAnimationEndBounds.top, fraction); + int animRight = (int) MathUtils.lerp(startRight, + mClippingAnimationEndBounds.right, fraction); + int animBottom = (int) MathUtils.lerp(startBottom, + mClippingAnimationEndBounds.bottom, fraction); + applyClippingImmediately(animLeft, animTop, animRight, animBottom, + qsVisible /* qsVisible */); + }); + mClippingAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mClippingAnimator = null; + mIsTranslationResettingAnimator = false; + mIsPulseExpansionResettingAnimator = false; + } + }); + mClippingAnimator.start(); + } + mAnimateNextNotificationBounds = false; + mNotificationBoundsAnimationDelay = 0; + } + + private void applyClippingImmediately(int left, int top, int right, int bottom, + boolean qsVisible) { + int radius = mScrimCornerRadius; + boolean clipStatusView = false; + mLastClipBounds.set(left, top, right, bottom); + if (mIsFullWidth) { + clipStatusView = qsVisible; + float screenCornerRadius = mRecordingController.isRecording() ? 0 : mScreenCornerRadius; + radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, + Math.min(top / (float) mScrimCornerRadius, 1f)); + } + if (isQsFragmentCreated()) { + float qsTranslation = 0; + boolean pulseExpanding = mPulseExpansionHandler.isExpanding(); + if (mTransitioningToFullShadeProgress > 0.0f + || pulseExpanding || (mClippingAnimator != null + && (mIsTranslationResettingAnimator || mIsPulseExpansionResettingAnimator))) { + if (pulseExpanding || mIsPulseExpansionResettingAnimator) { + // qsTranslation should only be positive during pulse expansion because it's + // already translating in from the top + qsTranslation = Math.max(0, (top - getHeaderHeight()) / 2.0f); + } else if (!mSplitShadeEnabled) { + qsTranslation = (top - getHeaderHeight()) * QS_PARALLAX_AMOUNT; + } + } + mTranslationForFullShadeTransition = qsTranslation; + updateQsFrameTranslation(); + float currentTranslation = mQsFrame.getTranslationY(); + int clipTop = mEnableClipping + ? (int) (top - currentTranslation - mQsFrame.getTop()) : 0; + int clipBottom = mEnableClipping + ? (int) (bottom - currentTranslation - mQsFrame.getTop()) : 0; + mVisible = qsVisible; + mQs.setQsVisible(qsVisible); + mQs.setFancyClipping( + clipTop, + clipBottom, + radius, + qsVisible && !mSplitShadeEnabled); + + } + + // Increase the height of the notifications scrim when not in split shade + // (e.g. portrait tablet) so the rounded corners are not visible at the bottom, + // in this case they are rendered off-screen + final int notificationsScrimBottom = mSplitShadeEnabled ? bottom : bottom + radius; + mScrimController.setNotificationsBounds(left, top, right, notificationsScrimBottom); + + if (mApplyClippingImmediatelyListener != null) { + mApplyClippingImmediatelyListener.onQsClippingImmediatelyApplied(clipStatusView, + mLastClipBounds, top, isQsFragmentCreated(), mVisible); + } + + mScrimController.setScrimCornerRadius(radius); + + // Convert global clipping coordinates to local ones, + // relative to NotificationStackScrollLayout + int nsslLeft = calculateNsslLeft(left); + int nsslRight = calculateNsslRight(right); + int nsslTop = getNotificationsClippingTopBounds(top); + int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop(); + int bottomRadius = mSplitShadeEnabled ? radius : 0; + // TODO (b/265193930): remove dependency on NPVC + int topRadius = mSplitShadeEnabled + && mPanelViewControllerLazy.get().isExpandingFromHeadsUp() ? 0 : radius; + mNotificationStackScrollLayoutController.setRoundedClippingBounds( + nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius); + } + + void setDisplayInsets(int leftInset, int rightInset) { + mDisplayLeftInset = leftInset; + mDisplayRightInset = rightInset; + } + + private int calculateNsslLeft(int nsslLeftAbsolute) { + int left = nsslLeftAbsolute - mNotificationStackScrollLayoutController.getLeft(); + if (mIsFullWidth) { + return left; + } + return left - mDisplayLeftInset; + } + + private int calculateNsslRight(int nsslRightAbsolute) { + int right = nsslRightAbsolute - mNotificationStackScrollLayoutController.getLeft(); + if (mIsFullWidth) { + return right; + } + return right - mDisplayLeftInset; + } + + private int getNotificationsClippingTopBounds(int qsTop) { + // TODO (b/265193930): remove dependency on NPVC + if (mSplitShadeEnabled && mPanelViewControllerLazy.get().isExpandingFromHeadsUp()) { + // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need + // to set top clipping bound to negative value to allow HUN to go up to the top edge of + // the screen without clipping. + return -mAmbientState.getStackTopMargin(); + } else { + return qsTop - mNotificationStackScrollLayoutController.getTop(); + } + } + + private void checkCorrectScrimVisibility(float expansionFraction) { + // issues with scrims visible on keyguard occur only in split shade + if (mSplitShadeEnabled) { + // TODO (b/265193930): remove dependency on NPVC + boolean keyguardViewsVisible = mBarState == KEYGUARD + && mPanelViewControllerLazy.get().getKeyguardOnlyContentAlpha() == 1; + // expansionFraction == 1 means scrims are fully visible as their size/visibility depend + // on QS expansion + if (expansionFraction == 1 && keyguardViewsVisible) { + Log.wtf(TAG, + "Incorrect state, scrim is visible at the same time when clock is visible"); + } + } + } + + /** Calculate top padding for notifications */ + public float calculateNotificationsTopPadding(boolean isShadeExpanding, + int keyguardNotificationStaticPadding, float expandedFraction) { + boolean keyguardShowing = mBarState == KEYGUARD; + if (mSplitShadeEnabled) { + return keyguardShowing + ? keyguardNotificationStaticPadding : 0; + } + if (keyguardShowing && (isExpandImmediate() + || isShadeExpanding && getExpandedWhenExpandingStarted())) { + + // Either QS pushes the notifications down when fully expanded, or QS is fully above the + // notifications (mostly on tablets). maxNotificationPadding denotes the normal top + // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings + // panel. We need to take the maximum and linearly interpolate with the panel expansion + // for a nice motion. + int maxQsPadding = getMaxExpansionHeight(); + int max = keyguardShowing ? Math.max( + keyguardNotificationStaticPadding, maxQsPadding) : maxQsPadding; + return (int) MathUtils.lerp((float) getMinExpansionHeight(), + (float) max, expandedFraction); + } else if (isSizeChangeAnimationRunning()) { + return Math.max((int) mSizeChangeAnimator.getAnimatedValue(), + keyguardNotificationStaticPadding); + } else if (keyguardShowing) { + // We can only do the smoother transition on Keyguard when we also are not collapsing + // from a scrolled quick settings. + return MathUtils.lerp((float) keyguardNotificationStaticPadding, + (float) (getMaxExpansionHeight()), computeExpansionFraction()); + } else { + return mQsFrameTranslateController.getNotificationsTopPadding( + mExpansionHeight, mNotificationStackScrollLayoutController); + } + } + + /** Calculate height of QS panel */ + public int calculatePanelHeightExpanded(int stackScrollerPadding) { + float + notificationHeight = + mNotificationStackScrollLayoutController.getHeight() + - mNotificationStackScrollLayoutController.getEmptyBottomMargin() + - mNotificationStackScrollLayoutController.getTopPadding(); + + // When only empty shade view is visible in QS collapsed state, simulate that we would have + // it in expanded QS state as well so we don't run into troubles when fading the view in/out + // and expanding/collapsing the whole panel from/to quick settings. + if (mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0 + && mNotificationStackScrollLayoutController.isShowingEmptyShadeView()) { + notificationHeight = mNotificationStackScrollLayoutController.getEmptyShadeViewHeight(); + } + int maxQsHeight = mMaxExpansionHeight; + + // If an animation is changing the size of the QS panel, take the animated value. + if (mSizeChangeAnimator != null) { + maxQsHeight = (int) mSizeChangeAnimator.getAnimatedValue(); + } + float totalHeight = Math.max(maxQsHeight, mBarState == KEYGUARD ? stackScrollerPadding : 0) + + notificationHeight + + mNotificationStackScrollLayoutController.getTopPaddingOverflow(); + if (totalHeight > mNotificationStackScrollLayoutController.getHeight()) { + float + fullyCollapsedHeight = + maxQsHeight + mNotificationStackScrollLayoutController.getLayoutMinHeight(); + totalHeight = Math.max(fullyCollapsedHeight, + mNotificationStackScrollLayoutController.getHeight()); + } + return (int) totalHeight; + } + + private float getEdgePosition() { + // TODO: replace StackY with unified calculation + return Math.max(mQuickQsHeaderHeight * mAmbientState.getExpansionFraction(), + mAmbientState.getStackY() + // need to adjust for extra margin introduced by large screen shade header + + mAmbientState.getStackTopMargin() * mAmbientState.getExpansionFraction() + - mAmbientState.getScrollY()); + } + + private int calculateTopClippingBound(int qsPanelBottomY) { + int top; + if (mSplitShadeEnabled) { + top = Math.min(qsPanelBottomY, mLargeScreenShadeHeaderHeight); + } else { + if (mTransitioningToFullShadeProgress > 0.0f) { + // If we're transitioning, let's use the actual value. The else case + // can be wrong during transitions when waiting for the keyguard to unlock + top = mTransitionToFullShadePosition; + } else { + final float notificationTop = getEdgePosition(); + if (mBarState == KEYGUARD) { + if (mKeyguardBypassController.getBypassEnabled()) { + // When bypassing on the keyguard, let's use the panel bottom. + // this should go away once we unify the stackY position and don't have + // to do this min anymore below. + top = qsPanelBottomY; + } else { + top = (int) Math.min(qsPanelBottomY, notificationTop); + } + } else { + top = (int) notificationTop; + } + } + // TODO (b/265193930): remove dependency on NPVC + top += mPanelViewControllerLazy.get().getOverStretchAmount(); + // Correction for instant expansion caused by HUN pull down/ + float minFraction = mPanelViewControllerLazy.get().getMinFraction(); + if (minFraction > 0f && minFraction < 1f) { + float realFraction = (mShadeExpandedFraction + - minFraction) / (1f - minFraction); + top *= MathUtils.saturate(realFraction / minFraction); + } + } + return top; + } + + private int calculateBottomClippingBound(int top) { + if (mSplitShadeEnabled) { + return top + mNotificationStackScrollLayoutController.getHeight() + + mSplitShadeNotificationsScrimMarginBottom; + } else { + return mPanelView.getBottom(); + } + } + + private int calculateLeftClippingBound() { + if (mIsFullWidth) { + // left bounds can ignore insets, it should always reach the edge of the screen + return 0; + } else { + return mNotificationStackScrollLayoutController.getLeft() + + mDisplayLeftInset; + } + } + + private int calculateRightClippingBound() { + if (mIsFullWidth) { + return mPanelView.getRight() + + mDisplayRightInset; + } else { + return mNotificationStackScrollLayoutController.getRight() + + mDisplayLeftInset; + } + } + + private void trackMovement(MotionEvent event) { + if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event); + } + + private void initVelocityTracker() { + if (mQsVelocityTracker != null) { + mQsVelocityTracker.recycle(); + } + mQsVelocityTracker = VelocityTracker.obtain(); + } + + private float getCurrentVelocity() { + if (mQsVelocityTracker == null) { + return 0; + } + mQsVelocityTracker.computeCurrentVelocity(1000); + return mQsVelocityTracker.getYVelocity(); + } + + boolean updateAndGetTouchAboveFalsingThreshold() { + mTouchAboveFalsingThreshold = mFullyExpanded; + return mTouchAboveFalsingThreshold; + } + + private void onHeightChanged() { + mMaxExpansionHeight = isQsFragmentCreated() ? mQs.getDesiredHeight() : 0; + if (mExpanded && mFullyExpanded) { + mExpansionHeight = mMaxExpansionHeight; + if (mExpansionHeightSetToMaxListener != null) { + mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(true); + } + } + if (mAccessibilityManager.isEnabled()) { + // TODO (b/265193930): remove dependency on NPVC + mPanelView.setAccessibilityPaneTitle( + mPanelViewControllerLazy.get().determineAccessibilityPaneTitle()); + } + mNotificationStackScrollLayoutController.setMaxTopPadding(mMaxExpansionHeight); + } + + private void collapseOrExpandQs() { + onExpansionStarted(); + if (getExpanded()) { + flingQs(0, FLING_COLLAPSE, null, true); + } else if (isExpansionEnabled()) { + mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0); + flingQs(0, FLING_EXPAND, null, true); + } + } + + private void onScroll(int scrollY) { + mLargeScreenShadeHeaderController.setQsScrollY(scrollY); + if (scrollY > 0 && !mFullyExpanded) { + // TODO (b/265193930): remove dependency on NPVC + // If we are scrolling QS, we should be fully expanded. + mPanelViewControllerLazy.get().expandWithQs(); + } + } + + @VisibleForTesting + boolean isTrackingBlocked() { + return mConflictingExpansionGesture && getExpanded(); + } + + boolean isExpansionAnimating() { + return mExpansionAnimator != null; + } + + @VisibleForTesting + boolean isConflictingExpansionGesture() { + return mConflictingExpansionGesture; + } + + /** handles touches in Qs panel area */ + public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed, + boolean isShadeOrQsHeightAnimationRunning) { + if (isSplitShadeAndTouchXOutsideQs(event.getX())) { + return false; + } + final int action = event.getActionMasked(); + boolean collapsedQs = !getExpanded() && !mSplitShadeEnabled; + boolean expandedShadeCollapsedQs = mShadeExpandedFraction == 1f + && mBarState != KEYGUARD && collapsedQs && isExpansionEnabled(); + if (action == MotionEvent.ACTION_DOWN && expandedShadeCollapsedQs) { + // Down in the empty area while fully expanded - go to QS. + mShadeLog.logMotionEvent(event, "handleQsTouch: down action, QS tracking enabled"); + mTracking = true; + traceQsJank(true, false); + mConflictingExpansionGesture = true; + onExpansionStarted(); + mInitialHeightOnTouch = mExpansionHeight; + mInitialTouchY = event.getY(); + mInitialTouchX = event.getX(); + } + if (!isFullyCollapsed && !isShadeOrQsHeightAnimationRunning) { + handleDown(event); + } + // defer touches on QQS to shade while shade is collapsing. Added margin for error + // as sometimes the qsExpansionFraction can be a tiny value instead of 0 when in QQS. + if (!mSplitShadeEnabled && !mLastShadeFlingWasExpanding + && computeExpansionFraction() <= 0.01 && mShadeExpandedFraction < 1.0) { + mShadeLog.logMotionEvent(event, + "handleQsTouch: shade touched while shade collapsing, QS tracking disabled"); + mTracking = false; + } + if (!isExpandImmediate() && mTracking) { + onTouch(event); + if (!mConflictingExpansionGesture && !mSplitShadeEnabled) { + mShadeLog.logMotionEvent(event, + "handleQsTouch: not immediate expand or conflicting gesture"); + return true; + } + } + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mConflictingExpansionGesture = false; + } + if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed && isExpansionEnabled()) { + mTwoFingerExpandPossible = true; + } + if (mTwoFingerExpandPossible && isOpenQsEvent(event) + && event.getY(event.getActionIndex()) + < mStatusBarMinHeight) { + mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1); + setExpandImmediate(true); + mNotificationStackScrollLayoutController.setShouldShowShelfOnly(!mSplitShadeEnabled); + if (mExpansionHeightSetToMaxListener != null) { + mExpansionHeightSetToMaxListener.onExpansionHeightSetToMax(false); + } + + // Normally, we start listening when the panel is expanded, but here we need to start + // earlier so the state is already up to date when dragging down. + setListening(true); + } + return false; + } + + private void handleDown(MotionEvent event) { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN + && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { + mFalsingCollector.onQsDown(); + mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled"); + mTracking = true; + onExpansionStarted(); + mInitialHeightOnTouch = mExpansionHeight; + mInitialTouchY = event.getY(); + mInitialTouchX = event.getX(); + // TODO (b/265193930): remove dependency on NPVC + // If we interrupt an expansion gesture here, make sure to update the state correctly. + mPanelViewControllerLazy.get().notifyExpandingFinished(); + } + } + + private void onTouch(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float y = event.getY(pointerIndex); + final float x = event.getX(pointerIndex); + final float h = y - mInitialTouchY; + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mShadeLog.logMotionEvent(event, "onQsTouch: down action, QS tracking enabled"); + mTracking = true; + traceQsJank(true, false); + mInitialTouchY = y; + mInitialTouchX = x; + onExpansionStarted(); + mInitialHeightOnTouch = mExpansionHeight; + initVelocityTracker(); + trackMovement(event); + break; + + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mInitialHeightOnTouch = mExpansionHeight; + mInitialTouchY = newY; + mInitialTouchX = newX; + } + break; + + case MotionEvent.ACTION_MOVE: + mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion"); + setExpansionHeight(h + mInitialHeightOnTouch); + // TODO (b/265193930): remove dependency on NPVC + if (h >= mPanelViewControllerLazy.get().getFalsingThreshold()) { + mTouchAboveFalsingThreshold = true; + } + trackMovement(event); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mShadeLog.logMotionEvent(event, + "onQsTouch: up/cancel action, QS tracking disabled"); + mTracking = false; + mTrackingPointer = -1; + trackMovement(event); + float fraction = computeExpansionFraction(); + if (fraction != 0f || y >= mInitialTouchY) { + flingQsWithCurrentVelocity(y, + event.getActionMasked() == MotionEvent.ACTION_CANCEL); + } else { + traceQsJank(false, + event.getActionMasked() == MotionEvent.ACTION_CANCEL); + } + if (mQsVelocityTracker != null) { + mQsVelocityTracker.recycle(); + mQsVelocityTracker = null; + } + break; + } + } + + /** intercepts touches on Qs panel area. */ + public boolean onIntercept(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mInitialTouchY = y; + mInitialTouchX = x; + initVelocityTracker(); + trackMovement(event); + float qsExpansionFraction = computeExpansionFraction(); + // Intercept the touch if QS is between fully collapsed and fully expanded state + if (!mSplitShadeEnabled + && qsExpansionFraction > 0.0 && qsExpansionFraction < 1.0) { + mShadeLog.logMotionEvent(event, + "onQsIntercept: down action, QS partially expanded/collapsed"); + return true; + } + // TODO (b/265193930): remove dependency on NPVC + if (mPanelViewControllerLazy.get().getKeyguardShowing() + && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { + // Dragging down on the lockscreen statusbar should prohibit other interactions + // immediately, otherwise we'll wait on the touchslop. This is to allow + // dragging down to expanded quick settings directly on the lockscreen. + mPanelView.getParent().requestDisallowInterceptTouchEvent(true); + } + if (mExpansionAnimator != null) { + mInitialHeightOnTouch = mExpansionHeight; + mShadeLog.logMotionEvent(event, + "onQsIntercept: down action, QS tracking enabled"); + mTracking = true; + traceQsJank(true, false); + mNotificationStackScrollLayoutController.cancelLongPress(); + } + break; + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + mTrackingPointer = event.getPointerId(newIndex); + mInitialTouchX = event.getX(newIndex); + mInitialTouchY = event.getY(newIndex); + } + break; + + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialTouchY; + trackMovement(event); + if (mTracking) { + + // Already tracking because onOverscrolled was called. We need to update here + // so we don't stop for a frame until the next touch event gets handled in + // onTouchEvent. + setExpansionHeight(h + mInitialHeightOnTouch); + trackMovement(event); + return true; + } else { + mShadeLog.logMotionEvent(event, + "onQsIntercept: move ignored because qs tracking disabled"); + } + // TODO (b/265193930): remove dependency on NPVC + float touchSlop = event.getClassification() + == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mTouchSlop * mSlopMultiplier + : mTouchSlop; + if ((h > touchSlop || (h < -touchSlop && getExpanded())) + && Math.abs(h) > Math.abs(x - mInitialTouchX) + && shouldQuickSettingsIntercept( + mInitialTouchX, mInitialTouchY, h)) { + mPanelView.getParent().requestDisallowInterceptTouchEvent(true); + mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); + mTracking = true; + traceQsJank(true, false); + onExpansionStarted(); + mPanelViewControllerLazy.get().notifyExpandingFinished(); + mInitialHeightOnTouch = mExpansionHeight; + mInitialTouchY = y; + mInitialTouchX = x; + mNotificationStackScrollLayoutController.cancelLongPress(); + return true; + } else { + mShadeLog.logQsTrackingNotStarted(mInitialTouchY, y, h, touchSlop, + getExpanded(), mPanelViewControllerLazy.get().getKeyguardShowing(), + isExpansionEnabled()); + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + trackMovement(event); + mShadeLog.logMotionEvent(event, "onQsIntercept: up action, QS tracking disabled"); + mTracking = false; + break; + } + return false; + } + + private void onPanelExpansionChanged(ShadeExpansionChangeEvent event) { + mShadeExpandedFraction = event.getFraction(); + } + + /** + * Animate QS closing by flinging it. + * If QS is expanded, it will collapse into QQS and stop. + * If in split shade, it will collapse the whole shade. + * + * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore. + */ + public void animateCloseQs(boolean animateAway) { + if (mExpansionAnimator != null) { + if (!mAnimatorExpand) { + return; + } + float height = mExpansionHeight; + mExpansionAnimator.cancel(); + setExpansionHeight(height); + } + flingQs(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE); + } + + private void cancelExpansionAnimation() { + if (mExpansionAnimator != null) { + mExpansionAnimator.cancel(); + } + } + + /** @see #flingQs(float, int, Runnable, boolean) */ + public void flingQs(float vel, int type) { + flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */); + } + + /** + * Animates QS or QQS as if the user had swiped up or down. + * + * @param vel Finger velocity or 0 when not initiated by touch events. + * @param type Either FLING_EXPAND, FLING_COLLAPSE or FLING_HIDE. + * @param onFinishRunnable Runnable to be executed at the end of animation. + * @param isClick If originated by click (different interpolator and duration.) + */ + private void flingQs(float vel, int type, final Runnable onFinishRunnable, + boolean isClick) { + float target; + switch (type) { + case FLING_EXPAND: + target = getMaxExpansionHeight(); + break; + case FLING_COLLAPSE: + target = getMinExpansionHeight(); + break; + case FLING_HIDE: + default: + if (isQsFragmentCreated()) { + mQs.closeDetail(); + } + target = 0; + } + if (target == mExpansionHeight) { + if (onFinishRunnable != null) { + onFinishRunnable.run(); + } + traceQsJank(false, type != FLING_EXPAND); + return; + } + + // If we move in the opposite direction, reset velocity and use a different duration. + boolean oppositeDirection = false; + boolean expanding = type == FLING_EXPAND; + if (vel > 0 && !expanding || vel < 0 && expanding) { + vel = 0; + oppositeDirection = true; + } + ValueAnimator animator = ValueAnimator.ofFloat( + mExpansionHeight, target); + if (isClick) { + animator.setInterpolator(Interpolators.TOUCH_RESPONSE); + animator.setDuration(368); + } else { + if (mFlingQsWithoutClickListener != null) { + mFlingQsWithoutClickListener.onFlingQsWithoutClick(animator, mExpansionHeight, + target, vel); + } + } + if (oppositeDirection) { + animator.setDuration(350); + } + animator.addUpdateListener( + animation -> setExpansionHeight((Float) animation.getAnimatedValue())); + animator.addListener(new AnimatorListenerAdapter() { + private boolean mIsCanceled; + + @Override + public void onAnimationStart(Animator animation) { + mPanelViewControllerLazy.get().notifyExpandingStarted(); + } + + @Override + public void onAnimationCancel(Animator animation) { + mIsCanceled = true; + } + + @Override + public void onAnimationEnd(Animator animation) { + mAnimatingHiddenFromCollapsed = false; + mAnimating = false; + mPanelViewControllerLazy.get().notifyExpandingFinished(); + mNotificationStackScrollLayoutController.resetCheckSnoozeLeavebehind(); + mExpansionAnimator = null; + if (onFinishRunnable != null) { + onFinishRunnable.run(); + } + traceQsJank(false, mIsCanceled); + } + }); + // Let's note that we're animating QS. Moving the animator here will cancel it immediately, + // so we need a separate flag. + mAnimating = true; + animator.start(); + mExpansionAnimator = animator; + mAnimatorExpand = expanding; + mAnimatingHiddenFromCollapsed = + computeExpansionFraction() == 0.0f && target == 0; + } + + private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) { + float vel = getCurrentVelocity(); + // TODO (b/265193930): remove dependency on NPVC + boolean expandsQs = mPanelViewControllerLazy.get().flingExpandsQs(vel); + if (expandsQs) { + if (mFalsingManager.isUnlockingDisabled() || isQsFalseTouch()) { + expandsQs = false; + } else { + logQsSwipeDown(y); + } + } else if (vel < 0) { + mFalsingManager.isFalseTouch(QS_COLLAPSE); + } + + int flingType; + if (expandsQs && !isCancelMotionEvent) { + flingType = FLING_EXPAND; + } else if (mSplitShadeEnabled) { + flingType = FLING_HIDE; + } else { + flingType = FLING_COLLAPSE; + } + flingQs(vel, flingType); + } + + private void logQsSwipeDown(float y) { + float vel = getCurrentVelocity(); + final int gesture = mBarState == KEYGUARD ? MetricsProto.MetricsEvent.ACTION_LS_QS + : MetricsProto.MetricsEvent.ACTION_SHADE_QS_PULL; + // TODO (b/265193930): remove dependency on NPVC + float displayDensity = mPanelViewControllerLazy.get().getDisplayDensity(); + mLockscreenGestureLogger.write(gesture, + (int) ((y - getInitialTouchY()) / displayDensity), (int) (vel / displayDensity)); + } + + /** */ + public FragmentHostManager.FragmentListener getQsFragmentListener() { + return new QsFragmentListener(); + } + + /** */ + public final class QsFragmentListener implements FragmentHostManager.FragmentListener { + /** */ + @Override + public void onFragmentViewCreated(String tag, Fragment fragment) { + mQs = (QS) fragment; + mQs.setPanelView(mQsHeightListener); + mQs.setCollapseExpandAction(mQsCollapseExpandAction); + mQs.setHeaderClickable(isExpansionEnabled()); + mQs.setOverscrolling(mStackScrollerOverscrolling); + mQs.setInSplitShade(mSplitShadeEnabled); + mQs.setIsNotificationPanelFullWidth(mIsFullWidth); + + // recompute internal state when qspanel height changes + mQs.getView().addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + final int height = bottom - top; + final int oldHeight = oldBottom - oldTop; + if (height != oldHeight) { + onHeightChanged(); + } + }); + mQs.setCollapsedMediaVisibilityChangedListener((visible) -> { + if (mQs.getHeader().isShown()) { + setAnimateNextNotificationBounds( + StackStateAnimator.ANIMATION_DURATION_STANDARD, 0); + mNotificationStackScrollLayoutController.animateNextTopPaddingChange(); + } + }); + mLockscreenShadeTransitionController.setQS(mQs); + mShadeTransitionController.setQs(mQs); + mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader()); + mQs.setScrollListener(mQsScrollListener); + updateExpansion(); + } + + /** */ + @Override + public void onFragmentViewDestroyed(String tag, Fragment fragment) { + // Manual handling of fragment lifecycle is only required because this bridges + // non-fragment and fragment code. Once we are using a fragment for the notification + // panel, mQs will not need to be null cause it will be tied to the same lifecycle. + if (fragment == mQs) { + mQs = null; + } + } + } + + private final class LockscreenShadeTransitionCallback + implements LockscreenShadeTransitionController.Callback { + /** Called when pulse expansion has finished and this is going to the full shade. */ + @Override + public void onPulseExpansionFinished() { + setAnimateNextNotificationBounds( + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0); + mIsPulseExpansionResettingAnimator = true; + } + + @Override + public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) { + if (animate && mIsFullWidth) { + setAnimateNextNotificationBounds( + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, delay); + mIsTranslationResettingAnimator = mTranslationForFullShadeTransition > 0.0f; + } + float endPosition = 0; + if (pxAmount > 0.0f) { + if (mSplitShadeEnabled) { + float qsHeight = MathUtils.lerp(getMinExpansionHeight(), + getMaxExpansionHeight(), + mLockscreenShadeTransitionController.getQSDragProgress()); + setExpansionHeight(qsHeight); + } + if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0 + && !mMediaDataManager.hasActiveMediaOrRecommendation()) { + // No notifications are visible, let's animate to the height of qs instead + if (isQsFragmentCreated()) { + // Let's interpolate to the header height instead of the top padding, + // because the toppadding is way too low because of the large clock. + // we still want to take into account the edgePosition though as that nicely + // overshoots in the stackscroller + endPosition = getEdgePosition() + - mNotificationStackScrollLayoutController.getTopPadding() + + getHeaderHeight(); + } + } else { + // Interpolating to the new bottom edge position! + endPosition = getEdgePosition() + mNotificationStackScrollLayoutController + .getFullShadeTransitionInset(); + if (mBarState == KEYGUARD) { + endPosition -= mLockscreenNotificationPadding; + } + } + } + + // Calculate the overshoot amount such that we're reaching the target after our desired + // distance, but only reach it fully once we drag a full shade length. + mTransitioningToFullShadeProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( + MathUtils.saturate(pxAmount / mDistanceForFullShadeTransition)); + + int position = (int) MathUtils.lerp((float) 0, endPosition, + mTransitioningToFullShadeProgress); + if (mTransitioningToFullShadeProgress > 0.0f) { + // we want at least 1 pixel otherwise the panel won't be clipped + position = Math.max(1, position); + } + mTransitionToFullShadePosition = position; + updateExpansion(); + } + } + + private final class NsslOverscrollTopChangedListener implements + NotificationStackScrollLayout.OnOverscrollTopChangedListener { + @Override + public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { + // When in split shade, overscroll shouldn't carry through to QS + if (mSplitShadeEnabled) { + return; + } + cancelExpansionAnimation(); + if (!isExpansionEnabled()) { + amount = 0f; + } + float rounded = amount >= 1f ? amount : 0f; + setOverScrolling(rounded != 0f && isRubberbanded); + mExpansionFromOverscroll = rounded != 0f; + mLastOverscroll = rounded; + updateQsState(); + setExpansionHeight(getMinExpansionHeight() + rounded); + } + + @Override + public void flingTopOverscroll(float velocity, boolean open) { + // in split shade mode we want to expand/collapse QS only when touch happens within QS + if (isSplitShadeAndTouchXOutsideQs(mInitialTouchX)) { + return; + } + mLastOverscroll = 0f; + mExpansionFromOverscroll = false; + if (open) { + // During overscrolling, qsExpansion doesn't actually change that the qs is + // becoming expanded. Any layout could therefore reset the position again. Let's + // make sure we can expand + setOverScrolling(false); + } + setExpansionHeight(getExpansionHeight()); + boolean canExpand = isExpansionEnabled(); + flingQs(!canExpand && open ? 0f : velocity, + open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> { + setOverScrolling(false); + updateQsState(); + }, false); + } + } + + void beginJankMonitoring(boolean isFullyCollapsed) { + if (mInteractionJankMonitor == null) { + return; + } + // TODO (b/265193930): remove dependency on NPVC + InteractionJankMonitor.Configuration.Builder builder = + InteractionJankMonitor.Configuration.Builder.withView( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, + mPanelView).setTag(isFullyCollapsed ? "Expand" : "Collapse"); + mInteractionJankMonitor.begin(builder); + } + + void endJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.getInstance().end( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } + + void cancelJankMonitoring() { + if (mInteractionJankMonitor == null) { + return; + } + InteractionJankMonitor.getInstance().cancel( + InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE); + } + + void traceQsJank(boolean startTracing, boolean wasCancelled) { + if (mInteractionJankMonitor == null) { + return; + } + if (startTracing) { + mInteractionJankMonitor.begin(mPanelView, CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + } else { + if (wasCancelled) { + mInteractionJankMonitor.cancel(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + } else { + mInteractionJankMonitor.end(CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE); + } + } + } + + interface ExpansionHeightSetToMaxListener { + void onExpansionHeightSetToMax(boolean requestPaddingUpdate); + } + + interface ExpansionHeightListener { + void onQsSetExpansionHeightCalled(boolean qsFullyExpanded); + } + + interface QsStateUpdateListener { + void onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling); + } + + interface ApplyClippingImmediatelyListener { + void onQsClippingImmediatelyApplied(boolean clipStatusView, Rect lastQsClipBounds, + int top, boolean qsFragmentCreated, boolean qsVisible); + } + + interface FlingQsWithoutClickListener { + void onFlingQsWithoutClick(ValueAnimator animator, float qsExpansionHeight, + float target, float vel); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 26c839de08a17..b28509e8fbf5b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -50,7 +50,6 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { h: Float, touchSlop: Float, qsExpanded: Boolean, - collapsedOnDown: Boolean, keyguardShowing: Boolean, qsExpansionEnabled: Boolean ) { @@ -63,13 +62,12 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { long1 = h.toLong() double1 = touchSlop.toDouble() bool1 = qsExpanded - bool2 = collapsedOnDown - bool3 = keyguardShowing - bool4 = qsExpansionEnabled + bool2 = keyguardShowing + bool3 = qsExpansionEnabled }, { "QsTrackingNotStarted: initTouchY=$int1,y=$int2,h=$long1,slop=$double1,qsExpanded" + - "=$bool1,collapsedDown=$bool2,keyguardShowing=$bool3,qsExpansion=$bool4" + "=$bool1,keyguardShowing=$bool2,qsExpansion=$bool3" } ) } @@ -158,7 +156,6 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { qsMinExpansionHeight: Int, qsMaxExpansionHeight: Int, stackScrollerOverscrolling: Boolean, - dozing: Boolean, qsAnimatorExpand: Boolean, animatingQs: Boolean ) { @@ -171,14 +168,13 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { int1 = qsMinExpansionHeight int2 = qsMaxExpansionHeight bool2 = stackScrollerOverscrolling - bool3 = dozing - bool4 = qsAnimatorExpand + bool3 = qsAnimatorExpand // 0 = false, 1 = true long1 = animatingQs.compareTo(false).toLong() }, { "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," + - "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," + + "stackScrollerOverscrolling=$bool2,qsAnimatorExpand=$bool3," + "animatingQs=$long1" } ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index f565f3daf2ee6..0067aa8f271b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -187,6 +187,8 @@ class LockscreenShadeTransitionController @Inject constructor( private val qsTransitionController = qsTransitionControllerFactory.create { qS } + private val callbacks = mutableListOf() + /** See [LockscreenShadeQsTransitionController.qsTransitionFraction].*/ @get:FloatRange(from = 0.0, to = 1.0) val qSDragProgress: Float @@ -320,8 +322,8 @@ class LockscreenShadeTransitionController @Inject constructor( true /* drag down is always an open */) } notificationPanelController.animateToFullShade(delay) - notificationPanelController.setTransitionToFullShadeAmount(0f, - true /* animated */, delay) + callbacks.forEach { it.setTransitionToFullShadeAmount(0f, + true /* animated */, delay) } // Let's reset ourselves, ready for the next animation @@ -425,8 +427,8 @@ class LockscreenShadeTransitionController @Inject constructor( qsTransitionController.dragDownAmount = value - notificationPanelController.setTransitionToFullShadeAmount(field, - false /* animate */, 0 /* delay */) + callbacks.forEach { it.setTransitionToFullShadeAmount(field, + false /* animate */, 0 /* delay */) } mediaHierarchyManager.setTransitionToFullShadeAmount(field) scrimTransitionController.dragDownAmount = value @@ -689,7 +691,7 @@ class LockscreenShadeTransitionController @Inject constructor( if (cancelled) { setPulseHeight(0f, animate = true) } else { - notificationPanelController.onPulseExpansionFinished() + callbacks.forEach { it.onPulseExpansionFinished() } setPulseHeight(0f, animate = false) } } @@ -721,6 +723,27 @@ class LockscreenShadeTransitionController @Inject constructor( "${animationHandlerOnKeyguardDismiss != null}") } } + + + fun addCallback(callback: Callback) { + if (!callbacks.contains(callback)) { + callbacks.add(callback) + } + } + + /** + * Callback for authentication events. + */ + interface Callback { + /** TODO: comment here */ + fun onPulseExpansionFinished() {} + + /** + * Sets the amount of pixels we have currently dragged down if we're transitioning + * to the full shade. 0.0f means we're not transitioning yet. + */ + fun setTransitionToFullShadeAmount(pxAmount: Float, animate: Boolean, delay: Long) {} + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index ccde3c21798ea..b8ab956b32d37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -57,6 +57,7 @@ import com.android.systemui.qs.QSPanelController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; @@ -104,6 +105,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final VibrationEffect mCameraLaunchGestureVibrationEffect; private final SystemBarAttributesListener mSystemBarAttributesListener; private final Lazy mCameraLauncherLazy; + private final QuickSettingsController mQsController; private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); @@ -111,6 +113,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba @Inject CentralSurfacesCommandQueueCallbacks( CentralSurfaces centralSurfaces, + QuickSettingsController quickSettingsController, Context context, @Main Resources resources, ShadeController shadeController, @@ -137,6 +140,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba Lazy cameraLauncherLazy, UserTracker userTracker) { mCentralSurfaces = centralSurfaces; + mQsController = quickSettingsController; mContext = context; mShadeController = shadeController; mCommandQueue = commandQueue; @@ -334,9 +338,9 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mNotificationStackScrollLayoutController.setWillExpand(true); mHeadsUpManager.unpinAll(true /* userUnpinned */); mMetricsLogger.count("panel_open", 1); - } else if (!mNotificationPanelViewController.isInSettings() + } else if (!mQsController.getExpanded() && !mNotificationPanelViewController.isExpanding()) { - mNotificationPanelViewController.flingSettings(0 /* velocity */, + mQsController.flingQs(0 /* velocity */, NotificationPanelViewController.FLING_EXPAND); mMetricsLogger.count("panel_open_qs", 1); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 85399ca22d968..378b74a62024b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -185,6 +185,7 @@ import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; +import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -488,6 +489,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // settings private QSPanelController mQSPanelController; + @VisibleForTesting + QuickSettingsController mQsController; KeyguardIndicationController mKeyguardIndicationController; @@ -1419,7 +1422,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { || isOccluded() || !mKeyguardStateController.canDismissLockScreen() || mKeyguardViewMediator.isAnySimPinSecure() - || (mNotificationPanelViewController.isQsExpanded() && trackingTouch) + || (mQsController.getExpanded() && trackingTouch) || mNotificationPanelViewController.getBarState() == StatusBarState.SHADE_LOCKED) { return; } @@ -1580,6 +1583,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mCentralSurfacesComponent.getLockIconViewController().init(); mStackScrollerController = mCentralSurfacesComponent.getNotificationStackScrollLayoutController(); + mQsController = mCentralSurfacesComponent.getQuickSettingsController(); mStackScroller = mStackScrollerController.getView(); mNotifListContainer = mCentralSurfacesComponent.getNotificationListContainer(); mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); @@ -1699,7 +1703,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { && !isShadeDisabled() && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0) && !mDozing; - mNotificationPanelViewController.setQsExpansionEnabledPolicy(expandEnabled); + mQsController.setExpansionEnabledPolicy(expandEnabled); Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled); } @@ -3234,12 +3238,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mStatusBarKeyguardViewManager.onBackPressed(); return true; } - if (mNotificationPanelViewController.isQsCustomizing()) { - mNotificationPanelViewController.closeQsCustomizer(); + if (mQsController.isCustomizing()) { + mQsController.closeQsCustomizer(); return true; } - if (mNotificationPanelViewController.isQsExpanded()) { - mNotificationPanelViewController.animateCloseQs(false /* animateAway */); + if (mQsController.getExpanded()) { + mNotificationPanelViewController.animateCloseQs(false); return true; } if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) { @@ -3600,7 +3604,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFalsingCollector.onScreenOff(); mScrimController.onScreenTurnedOff(); if (mCloseQsBeforeScreenOff) { - mNotificationPanelViewController.closeQs(); + mQsController.closeQs(); mCloseQsBeforeScreenOff = false; } updateIsKeyguard(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index da1c361bced76..4eed48739b404 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -40,6 +40,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -102,6 +103,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, private final IStatusBarService mBarService; private final DynamicPrivacyController mDynamicPrivacyController; private final NotificationListContainer mNotifListContainer; + private final QuickSettingsController mQsController; protected boolean mVrMode; @@ -109,6 +111,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, StatusBarNotificationPresenter( Context context, NotificationPanelViewController panel, + QuickSettingsController quickSettingsController, HeadsUpManagerPhone headsUp, NotificationShadeWindowView statusBarWindow, ActivityStarter activityStarter, @@ -136,6 +139,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, mActivityStarter = activityStarter; mKeyguardStateController = keyguardStateController; mNotificationPanel = panel; + mQsController = quickSettingsController; mHeadsUpManager = headsUp; mDynamicPrivacyController = dynamicPrivacyController; mKeyguardIndicationController = keyguardIndicationController; @@ -191,7 +195,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, private void maybeClosePanelForShadeEmptied() { if (CLOSE_PANEL_WHEN_EMPTIED && !mNotificationPanel.isTracking() - && !mNotificationPanel.isQsExpanded() + && !mQsController.getExpanded() && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED && !isCollapsing()) { mStatusBarStateController.setState(StatusBarState.KEYGUARD); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index 64b04e93e69ce..aec196fc90f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -26,6 +26,7 @@ import com.android.systemui.shade.LargeScreenShadeHeaderController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; +import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.core.StatusBarInitializer; @@ -113,6 +114,9 @@ public interface CentralSurfacesComponent { */ NotificationPanelViewController getNotificationPanelViewController(); + /** Creates a QuickSettingsController. */ + QuickSettingsController getQuickSettingsController(); + /** * Creates a LockIconViewController. Must be init after creation. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 28f7edf4ffb9b..996d9fb5bfb8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -196,6 +196,7 @@ import org.mockito.stubbing.Answer; import java.util.List; import java.util.Optional; +import dagger.Lazy; import kotlinx.coroutines.CoroutineDispatcher; @SmallTest @@ -270,6 +271,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private KeyguardMediaController mKeyguardMediaController; @Mock private NavigationModeController mNavigationModeController; @Mock private NavigationBarController mNavigationBarController; + @Mock private QuickSettingsController mQsController; @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; @Mock private ContentResolver mContentResolver; @Mock private TapAgainViewController mTapAgainViewController; @@ -329,6 +331,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); private final ShadeExpansionStateManager mShadeExpansionStateManager = new ShadeExpansionStateManager(); + + private QuickSettingsController mQuickSettingsController; + @Mock private Lazy mNotificationPanelViewControllerLazy; + private FragmentHostManager.FragmentListener mFragmentListener; @Before @@ -506,6 +512,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mTapAgainViewController, mNavigationModeController, mNavigationBarController, + mQsController, mFragmentService, mContentResolver, mRecordingController, @@ -515,8 +522,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mShadeExpansionStateManager, mNotificationRemoteInputManager, mSysUIUnfoldComponent, - mInteractionJankMonitor, - mQsFrameTranslateController, mSysUiState, () -> mKeyguardBottomAreaViewController, mKeyguardUnlockAnimationController, @@ -572,6 +577,40 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { .setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture()); verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); reset(mKeyguardStatusViewController); + + when(mNotificationPanelViewControllerLazy.get()) + .thenReturn(mNotificationPanelViewController); + mQuickSettingsController = new QuickSettingsController( + mNotificationPanelViewControllerLazy, + mView, + mQsFrameTranslateController, + mShadeTransitionController, + expansionHandler, + mNotificationRemoteInputManager, + mShadeExpansionStateManager, + mStatusBarKeyguardViewManager, + mNotificationStackScrollLayoutController, + mLockscreenShadeTransitionController, + mNotificationShadeDepthController, + mLargeScreenShadeHeaderController, + mStatusBarTouchableRegionManager, + mKeyguardStateController, + mKeyguardBypassController, + mUpdateMonitor, + mScrimController, + mMediaDataManager, + mMediaHierarchyManager, + mAmbientState, + mRecordingController, + mFalsingManager, + new FalsingCollectorFake(), + mAccessibilityManager, + mLockscreenGestureLogger, + mMetricsLogger, + mFeatureFlags, + mInteractionJankMonitor, + mShadeLog + ); } @After @@ -755,27 +794,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testOnTouchEvent_expansionCanBeBlocked() { - onTouchEvent(MotionEvent.obtain(0L /* downTime */, - 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, - 0 /* metaState */)); - onTouchEvent(MotionEvent.obtain(0L /* downTime */, - 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */, - 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 200f, 0)); assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200); - assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse(); mNotificationPanelViewController.blockExpansionForCurrentTouch(); - onTouchEvent(MotionEvent.obtain(0L /* downTime */, - 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */, - 0 /* metaState */)); + onTouchEvent(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 300f, 0)); // Expansion should not have changed because it was blocked assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200); - assertThat(mNotificationPanelViewController.isTrackingBlocked()).isTrue(); - - onTouchEvent(MotionEvent.obtain(0L /* downTime */, - 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */, - 0 /* metaState */)); - assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse(); } @Test @@ -1045,7 +1071,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testCanCollapsePanelOnTouch_trueWhenInSettings() { mStatusBarStateController.setState(SHADE); - mNotificationPanelViewController.setQsExpanded(true); + when(mQsController.getExpanded()).thenReturn(true); assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isTrue(); } @@ -1054,7 +1080,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { public void testCanCollapsePanelOnTouch_falseInDualPaneShade() { mStatusBarStateController.setState(SHADE); enableSplitShade(/* enabled= */ true); - mNotificationPanelViewController.setQsExpanded(true); + when(mQsController.getExpanded()).thenReturn(true); assertThat(mNotificationPanelViewController.canCollapsePanelOnTouch()).isFalse(); } @@ -1125,7 +1151,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() { mStatusBarStateController.setState(KEYGUARD); - mNotificationPanelViewController.setQsExpanded(true); + when(mQsController.getExpanded()).thenReturn(true); enableSplitShade(true); @@ -1136,24 +1162,18 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() { enableSplitShade(true); mStatusBarStateController.setState(SHADE); - mNotificationPanelViewController.setQsExpanded(true); - mStatusBarStateController.setState(KEYGUARD); - assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false); - assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false); + verify(mQsController).closeQs(); } @Test public void testLockedSplitShadeTransitioningToKeyguard_closesQS() { enableSplitShade(true); mStatusBarStateController.setState(SHADE_LOCKED); - mNotificationPanelViewController.setQsExpanded(true); - mStatusBarStateController.setState(KEYGUARD); - assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false); - assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false); + verify(mQsController).closeQs(); } @Test @@ -1165,7 +1185,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true); when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - mNotificationPanelViewController.closeQs(); + triggerPositionClockAndNotifications(); verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true); } @@ -1292,18 +1312,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true); } - @Test - public void testLargeScreenHeaderMadeActiveForLargeScreen() { - mStatusBarStateController.setState(SHADE); - when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true); - mNotificationPanelViewController.updateResources(); - verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true); - - when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false); - mNotificationPanelViewController.updateResources(); - verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false); - } - @Test public void testExpandWithQsMethodIsUsingLockscreenTransitionController() { enableSplitShade(/* enabled= */ true); @@ -1358,12 +1366,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() { enableSplitShade(/* enabled= */ true); + mShadeExpansionStateManager.updateState(STATE_OPEN); + verify(mQsController).setExpandImmediate(false); + mShadeExpansionStateManager.updateState(STATE_CLOSED); - assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); + verify(mQsController, times(2)).setExpandImmediate(false); mShadeExpansionStateManager.updateState(STATE_OPENING); - - assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isTrue(); + verify(mQsController).setExpandImmediate(true); } @Test @@ -1375,33 +1385,28 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { // going to lockscreen would trigger STATE_OPENING mShadeExpansionStateManager.updateState(STATE_OPENING); - assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); + verify(mQsController, never()).setExpandImmediate(true); } @Test public void testQsImmediateResetsWhenPanelOpensOrCloses() { - mNotificationPanelViewController.setQsExpandImmediate(true); mShadeExpansionStateManager.updateState(STATE_OPEN); - assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); - - mNotificationPanelViewController.setQsExpandImmediate(true); mShadeExpansionStateManager.updateState(STATE_CLOSED); - assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); + verify(mQsController, times(2)).setExpandImmediate(false); } @Test public void testQsExpansionChangedToDefaultWhenRotatingFromOrToSplitShade() { // to make sure shade is in expanded state mNotificationPanelViewController.startWaitingForOpenPanelGesture(); - assertThat(mNotificationPanelViewController.isQsExpanded()).isFalse(); // switch to split shade from portrait (default state) enableSplitShade(/* enabled= */ true); - assertThat(mNotificationPanelViewController.isQsExpanded()).isTrue(); + verify(mQsController).setExpanded(true); // switch to portrait from split shade enableSplitShade(/* enabled= */ false); - assertThat(mNotificationPanelViewController.isQsExpanded()).isFalse(); + verify(mQsController).setExpanded(false); } @Test @@ -1413,59 +1418,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { assertThat(mNotificationPanelViewController.isClosing()).isFalse(); mNotificationPanelViewController.animateCloseQs(false); + assertThat(mNotificationPanelViewController.isClosing()).isTrue(); } - @Test - public void testPanelStaysOpenWhenClosingQs() { - mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, - /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0); - mNotificationPanelViewController.setExpandedFraction(1f); - - assertThat(mNotificationPanelViewController.isClosing()).isFalse(); - mNotificationPanelViewController.animateCloseQs(false); - assertThat(mNotificationPanelViewController.isClosing()).isFalse(); - } - - @Test - public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { - mNotificationPanelViewController.setQs(mQs); - when(mQsFrame.getX()).thenReturn(0f); - when(mQsFrame.getWidth()).thenReturn(1000); - when(mQsHeader.getTop()).thenReturn(0); - when(mQsHeader.getBottom()).thenReturn(1000); - NotificationPanelViewController.TouchHandler touchHandler = - mNotificationPanelViewController.createTouchHandler(); - - mNotificationPanelViewController.setExpandedFraction(1f); - touchHandler.onInterceptTouchEvent( - createMotionEvent(/* x= */ 0, /* y= */ 0, MotionEvent.ACTION_DOWN)); - touchHandler.onInterceptTouchEvent( - createMotionEvent(/* x= */ 0, /* y= */ 500, MotionEvent.ACTION_MOVE)); - - assertThat(mNotificationPanelViewController.isQsTracking()).isTrue(); - } - - @Test - public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() { - enableSplitShade(true); - mNotificationPanelViewController.setQs(mQs); - when(mQsFrame.getX()).thenReturn(0f); - when(mQsFrame.getWidth()).thenReturn(1000); - when(mQsHeader.getTop()).thenReturn(0); - when(mQsHeader.getBottom()).thenReturn(1000); - NotificationPanelViewController.TouchHandler touchHandler = - mNotificationPanelViewController.createTouchHandler(); - - mNotificationPanelViewController.setExpandedFraction(1f); - touchHandler.onInterceptTouchEvent( - createMotionEvent(/* x= */ 0, /* y= */ 0, MotionEvent.ACTION_DOWN)); - touchHandler.onInterceptTouchEvent( - createMotionEvent(/* x= */ 0, /* y= */ 500, MotionEvent.ACTION_MOVE)); - - assertThat(mNotificationPanelViewController.isQsTracking()).isFalse(); - } - @Test public void testOnAttachRefreshStatusBarState() { mStatusBarStateController.setState(KEYGUARD); @@ -1494,11 +1450,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { enableSplitShade(true); mNotificationPanelViewController.expandWithQs(); when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true); + when(mQsController.calculatePanelHeightExpanded(anyInt())).thenReturn(10000); mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( SPLIT_SHADE_FULL_TRANSITION_DISTANCE); int maxDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); + // make sure we're ignoring the placeholder value for Qs max height + assertThat(maxDistance).isLessThan(10000); assertThat(maxDistance).isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); } @@ -1525,94 +1484,25 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() { - mNotificationPanelViewController.setQs(mQs); - setIsFullWidth(true); - verify(mQs).setIsNotificationPanelFullWidth(true); + verify(mQsController).setNotificationPanelFullWidth(true); } @Test public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() { - mNotificationPanelViewController.setQs(mQs); - setIsFullWidth(false); - verify(mQs).setIsNotificationPanelFullWidth(false); + verify(mQsController).setNotificationPanelFullWidth(false); } @Test public void onLayoutChange_qsNotSet_doesNotCrash() { - mNotificationPanelViewController.setQs(null); + mQuickSettingsController.setQs(null); triggerLayoutChange(); } - @Test - public void onQsFragmentAttached_fullWidth_setsFullWidthTrueOnQS() { - setIsFullWidth(true); - givenViewAttached(); - mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); - - verify(mQSFragment).setIsNotificationPanelFullWidth(true); - } - - @Test - public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() { - setIsFullWidth(false); - givenViewAttached(); - mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); - - verify(mQSFragment).setIsNotificationPanelFullWidth(false); - } - - @Test - public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() { - float squishinessFraction = 0.456f; - mNotificationPanelViewController.setQs(mQs); - when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) - .thenReturn(squishinessFraction); - when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) - .thenReturn(0.987f); - // Call setTransitionToFullShadeAmount to get into the full shade transition in progress - // state. - mNotificationPanelViewController.setTransitionToFullShadeAmount( - /* pxAmount= */ 234, - /* animate= */ false, - /* delay= */ 0 - ); - - mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123); - - // First for setTransitionToFullShadeAmount and then setQsExpansion - verify(mQs, times(2)).setQsExpansion( - /* expansion= */ anyFloat(), - /* panelExpansionFraction= */ anyFloat(), - /* proposedTranslation= */ anyFloat(), - eq(squishinessFraction) - ); - } - - @Test - public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() { - float lsSquishinessFraction = 0.456f; - float nsslSquishinessFraction = 0.987f; - mNotificationPanelViewController.setQs(mQs); - when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) - .thenReturn(lsSquishinessFraction); - when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) - .thenReturn(nsslSquishinessFraction); - - mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123); - - verify(mQs).setQsExpansion( - /* expansion= */ anyFloat(), - /* panelExpansionFraction= */ anyFloat(), - /* proposedTranslation= */ anyFloat(), - eq(nsslSquishinessFraction) - ); - } - @Test public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() { StatusBarStateController.StateListener statusBarStateListener = @@ -1738,15 +1628,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); mNotificationPanelViewController.setExpandedHeight(transitionDistance); assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isFalse(); - - // set maxQsExpansion in NPVC - int maxQsExpansion = 123; - mNotificationPanelViewController.setQs(mQs); - when(mQs.getDesiredHeight()).thenReturn(maxQsExpansion); - triggerLayoutChange(); - - mNotificationPanelViewController.setQsExpansionHeight(maxQsExpansion); - assertThat(mNotificationPanelViewController.isShadeFullyOpen()).isTrue(); } @Test @@ -1761,7 +1642,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { } private void triggerPositionClockAndNotifications() { - mNotificationPanelViewController.closeQs(); + mNotificationPanelViewController.onQsSetExpansionHeightCalled(false); } private FalsingManager.FalsingTapListener getFalsingTapListener() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java new file mode 100644 index 0000000000000..c2fca6f2120b6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade; + +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.StatusBarState.SHADE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.Resources; +import android.os.Handler; +import android.os.Looper; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.MotionEvent; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; + +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.UiEventLogger; +import com.android.keyguard.KeyguardStatusView; +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.media.controls.pipeline.MediaDataManager; +import com.android.systemui.media.controls.ui.MediaHierarchyManager; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.qs.QSFragment; +import com.android.systemui.screenrecord.RecordingController; +import com.android.systemui.shade.transition.ShadeTransitionController; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShadeDepthController; +import com.android.systemui.statusbar.PulseExpansionHandler; +import com.android.systemui.statusbar.QsFrameTranslateController; +import com.android.systemui.statusbar.StatusBarStateControllerImpl; +import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.notification.stack.AmbientState; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; +import com.android.systemui.statusbar.phone.KeyguardBypassController; +import com.android.systemui.statusbar.phone.KeyguardStatusBarView; +import com.android.systemui.statusbar.phone.LockscreenGestureLogger; +import com.android.systemui.statusbar.phone.ScreenOffAnimationController; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; +import com.android.systemui.statusbar.policy.KeyguardStateController; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import dagger.Lazy; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public class QuickSettingsControllerTest extends SysuiTestCase { + + private static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400; + + private QuickSettingsController mQsController; + + @Mock private Resources mResources; + @Mock private KeyguardBottomAreaView mQsFrame; + @Mock private KeyguardStatusBarView mKeyguardStatusBar; + @Mock private QS mQs; + @Mock private QSFragment mQSFragment; + + @Mock private Lazy mPanelViewControllerLazy; + @Mock private NotificationPanelViewController mNotificationPanelViewController; + @Mock private NotificationPanelView mPanelView; + @Mock private ViewGroup mQsHeader; + @Mock private ViewParent mPanelViewParent; + @Mock private QsFrameTranslateController mQsFrameTranslateController; + @Mock private ShadeTransitionController mShadeTransitionController; + @Mock private PulseExpansionHandler mPulseExpansionHandler; + @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager; + @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; + @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; + @Mock private NotificationShadeDepthController mNotificationShadeDepthController; + @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; + @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; + @Mock private KeyguardStateController mKeyguardStateController; + @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private ScrimController mScrimController; + @Mock private MediaDataManager mMediaDataManager; + @Mock private MediaHierarchyManager mMediaHierarchyManager; + @Mock private AmbientState mAmbientState; + @Mock private RecordingController mRecordingController; + @Mock private FalsingManager mFalsingManager; + @Mock private FalsingCollector mFalsingCollector; + @Mock private AccessibilityManager mAccessibilityManager; + @Mock private LockscreenGestureLogger mLockscreenGestureLogger; + @Mock private MetricsLogger mMetricsLogger; + @Mock private FeatureFlags mFeatureFlags; + @Mock private InteractionJankMonitor mInteractionJankMonitor; + @Mock private ShadeLogger mShadeLogger; + + @Mock private DumpManager mDumpManager; + @Mock private DozeParameters mDozeParameters; + @Mock private ScreenOffAnimationController mScreenOffAnimationController; + @Mock private HeadsUpManagerPhone mHeadsUpManager; + @Mock private UiEventLogger mUiEventLogger; + + private SysuiStatusBarStateController mStatusBarStateController; + + private Handler mMainHandler; + private LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback; + + private final ShadeExpansionStateManager mShadeExpansionStateManager = + new ShadeExpansionStateManager(); + + private FragmentHostManager.FragmentListener mFragmentListener; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController); + mStatusBarStateController = new StatusBarStateControllerImpl(mUiEventLogger, mDumpManager, + mInteractionJankMonitor, mShadeExpansionStateManager); + + KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext); + keyguardStatusView.setId(R.id.keyguard_status_view); + + when(mPanelView.getResources()).thenReturn(mResources); + when(mPanelView.getContext()).thenReturn(getContext()); + when(mPanelView.findViewById(R.id.keyguard_header)).thenReturn(mKeyguardStatusBar); + when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000); + when(mPanelView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame); + when(mPanelView.findViewById(R.id.keyguard_status_view)) + .thenReturn(mock(KeyguardStatusView.class)); + when(mQs.getView()).thenReturn(mPanelView); + when(mQSFragment.getView()).thenReturn(mPanelView); + + when(mNotificationRemoteInputManager.isRemoteInputActive()) + .thenReturn(false); + when(mInteractionJankMonitor.begin(any(), anyInt())) + .thenReturn(true); + when(mInteractionJankMonitor.end(anyInt())) + .thenReturn(true); + + when(mPanelView.getParent()).thenReturn(mPanelViewParent); + when(mQs.getHeader()).thenReturn(mQsHeader); + + doAnswer(invocation -> { + mLockscreenShadeTransitionCallback = invocation.getArgument(0); + return null; + }).when(mLockscreenShadeTransitionController).addCallback(any()); + + + mMainHandler = new Handler(Looper.getMainLooper()); + + mQsController = new QuickSettingsController( + mPanelViewControllerLazy, + mPanelView, + mQsFrameTranslateController, + mShadeTransitionController, + mPulseExpansionHandler, + mNotificationRemoteInputManager, + mShadeExpansionStateManager, + mStatusBarKeyguardViewManager, + mNotificationStackScrollLayoutController, + mLockscreenShadeTransitionController, + mNotificationShadeDepthController, + mLargeScreenShadeHeaderController, + mStatusBarTouchableRegionManager, + mKeyguardStateController, + mKeyguardBypassController, + mKeyguardUpdateMonitor, + mScrimController, + mMediaDataManager, + mMediaHierarchyManager, + mAmbientState, + mRecordingController, + mFalsingManager, + mFalsingCollector, + mAccessibilityManager, + mLockscreenGestureLogger, + mMetricsLogger, + mFeatureFlags, + mInteractionJankMonitor, + mShadeLogger + ); + + mFragmentListener = mQsController.getQsFragmentListener(); + } + + @After + public void tearDown() { + mNotificationPanelViewController.cancelHeightAnimator(); + mMainHandler.removeCallbacksAndMessages(null); + } + + @Test + public void testOnTouchEvent_isConflictingExpansionGestureSet() { + assertThat(mQsController.isConflictingExpansionGesture()).isFalse(); + mShadeExpansionStateManager.onPanelExpansionChanged(1f, true, false, 0f); + mQsController.handleTouch(MotionEvent.obtain(0L /* downTime */, + 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, + 0 /* metaState */), false, false); + assertThat(mQsController.isConflictingExpansionGesture()).isTrue(); + } + + @Test + public void testCloseQsSideEffects() { + enableSplitShade(true); + mQsController.setExpandImmediate(true); + mQsController.setExpanded(true); + mQsController.closeQs(); + + assertThat(mQsController.getExpanded()).isEqualTo(false); + assertThat(mQsController.isExpandImmediate()).isEqualTo(false); + } + + @Test + public void testLargeScreenHeaderMadeActiveForLargeScreen() { + mStatusBarStateController.setState(SHADE); + when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true); + mQsController.updateResources(); + verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true); + + when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false); + mQsController.updateResources(); + verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false); + } + + @Test + public void testPanelStaysOpenWhenClosingQs() { + mShadeExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1, + /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0); + mNotificationPanelViewController.setExpandedFraction(1f); + + float shadeExpandedHeight = mQsController.getShadeExpandedHeight(); + mQsController.animateCloseQs(false); + + assertThat(mQsController.getShadeExpandedHeight()).isEqualTo(shadeExpandedHeight); + } + + @Test + public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { + mQsController.setQs(mQs); + when(mQsFrame.getX()).thenReturn(0f); + when(mQsFrame.getWidth()).thenReturn(1000); + when(mQsHeader.getTop()).thenReturn(0); + when(mQsHeader.getBottom()).thenReturn(1000); + + mQsController.setShadeExpandedHeight(1f); + mQsController.onIntercept( + createMotionEvent(0, 0, MotionEvent.ACTION_DOWN)); + mQsController.onIntercept( + createMotionEvent(0, 500, MotionEvent.ACTION_MOVE)); + + assertThat(mQsController.isTracking()).isTrue(); + } + + @Test + public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() { + enableSplitShade(true); + mQsController.setQs(mQs); + when(mQsFrame.getX()).thenReturn(0f); + when(mQsFrame.getWidth()).thenReturn(1000); + when(mQsHeader.getTop()).thenReturn(0); + when(mQsHeader.getBottom()).thenReturn(1000); + + mQsController.setShadeExpandedHeight(1f); + mQsController.onIntercept( + createMotionEvent(0, 0, MotionEvent.ACTION_DOWN)); + mQsController.onIntercept( + createMotionEvent(0, 500, MotionEvent.ACTION_MOVE)); + + assertThat(mQsController.isTracking()).isFalse(); + } + + @Test + public void onQsFragmentAttached_fullWidth_setsFullWidthTrueOnQS() { + setIsFullWidth(true); + mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); + + verify(mQSFragment).setIsNotificationPanelFullWidth(true); + } + + @Test + public void onQsFragmentAttached_notFullWidth_setsFullWidthFalseOnQS() { + setIsFullWidth(false); + mFragmentListener.onFragmentViewCreated(QS.TAG, mQSFragment); + + verify(mQSFragment).setIsNotificationPanelFullWidth(false); + } + + @Test + public void getMaxPanelTransitionDistance_inSplitShade_withHeadsUp_returnsBiggerValue() { + enableSplitShade(true); + mNotificationPanelViewController.expandWithQs(); + when(mHeadsUpManager.isTrackingHeadsUp()).thenReturn(true); + + mNotificationPanelViewController.setHeadsUpDraggingStartingHeight( + SPLIT_SHADE_FULL_TRANSITION_DISTANCE); + + assertThat(mQsController.calculatePanelHeightExpanded(0)) + .isGreaterThan(SPLIT_SHADE_FULL_TRANSITION_DISTANCE); + } + + @Test + public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() { + float squishinessFraction = 0.456f; + mQsController.setQs(mQs); + when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) + .thenReturn(squishinessFraction); + when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) + .thenReturn(0.987f); + // Call setTransitionToFullShadeAmount to get into the full shade transition in progress + // state. + mLockscreenShadeTransitionCallback.setTransitionToFullShadeAmount(234, false, 0); + + mQsController.setExpansionHeight(123); + + // First for setTransitionToFullShadeAmount and then setQsExpansion + verify(mQs, times(2)).setQsExpansion(anyFloat(), anyFloat(), anyFloat(), + eq(squishinessFraction) + ); + } + + @Test + public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() { + float lsSquishinessFraction = 0.456f; + float nsslSquishinessFraction = 0.987f; + mQsController.setQs(mQs); + when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) + .thenReturn(lsSquishinessFraction); + when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) + .thenReturn(nsslSquishinessFraction); + + mQsController.setExpansionHeight(123); + + verify(mQs).setQsExpansion(anyFloat(), anyFloat(), anyFloat(), eq(nsslSquishinessFraction) + ); + } + + @Test + public void shadeExpanded_onKeyguard() { + mStatusBarStateController.setState(KEYGUARD); + // set maxQsExpansion in NPVC + int maxQsExpansion = 123; + mQsController.setQs(mQs); + when(mQs.getDesiredHeight()).thenReturn(maxQsExpansion); + + int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange(); + mQsController.handleShadeLayoutChanged(oldMaxHeight); + + mQsController.setExpansionHeight(maxQsExpansion); + assertThat(mQsController.computeExpansionFraction()).isEqualTo(1f); + } + + private static MotionEvent createMotionEvent(int x, int y, int action) { + return MotionEvent.obtain( + /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0); + } + + private void enableSplitShade(boolean enabled) { + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(enabled); + mQsController.updateResources(); + } + + private void setIsFullWidth(boolean fullWidth) { + mQsController.setNotificationPanelFullWidth(fullWidth); + triggerLayoutChange(); + } + + private void triggerLayoutChange() { + int oldMaxHeight = mQsController.updateHeightsOnShadeLayoutChange(); + mQsController.handleShadeLayoutChanged(oldMaxHeight); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 702f278746be9..d99cdd514f37e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -79,6 +79,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var singleShadeOverScroller: SingleShadeLockScreenOverScroller @Mock lateinit var splitShadeOverScroller: SplitShadeLockScreenOverScroller @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController + @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback @JvmField @Rule val mockito = MockitoJUnit.rule() private val configurationController = FakeConfigurationController() @@ -124,6 +125,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { }, qsTransitionControllerFactory = { qsTransitionController }, ) + transitionController.addCallback(transitionControllerCallback) whenever(nsslController.view).thenReturn(stackscroller) whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback) transitionController.notificationPanelController = notificationPanelController @@ -258,7 +260,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat()) verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat()) verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat()) - verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(), + verify(transitionControllerCallback, never()).setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong()) verify(qsTransitionController, never()).dragDownAmount = anyFloat() } @@ -269,7 +271,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { verify(nsslController).setTransitionToFullShadeAmount(anyFloat()) verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat()) verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat()) - verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(), + verify(transitionControllerCallback).setTransitionToFullShadeAmount(anyFloat(), anyBoolean(), anyLong()) verify(qsTransitionController).dragDownAmount = 10f verify(depthController).transitionToFullShadeProgress = anyFloat() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index b879cf24fcf12..48573c63d7280 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -46,6 +46,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.CameraLauncher; import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DisableFlagsLogger; @@ -73,6 +74,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private CentralSurfaces mCentralSurfaces; @Mock private ShadeController mShadeController; @Mock private CommandQueue mCommandQueue; + @Mock private QuickSettingsController mQuickSettingsController; @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final MetricsLogger mMetricsLogger = new FakeMetricsLogger(); @@ -101,6 +103,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mSbcqCallbacks = new CentralSurfacesCommandQueueCallbacks( mCentralSurfaces, + mQuickSettingsController, mContext, mContext.getResources(), mShadeController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 06053987202ce..dbf416e9d1aad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -126,6 +126,7 @@ import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; +import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeControllerImpl; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -218,6 +219,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private NotificationPanelViewController mNotificationPanelViewController; @Mock private NotificationPanelView mNotificationPanelView; + @Mock private QuickSettingsController mQuickSettingsController; @Mock private IStatusBarService mBarService; @Mock private IDreamManager mDreamManager; @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel; @@ -546,6 +548,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // initialized automatically and make NPVC private. mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView; mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController; + mCentralSurfaces.mQsController = mQuickSettingsController; mCentralSurfaces.mDozeScrimController = mDozeScrimController; mCentralSurfaces.mPresenter = mNotificationPresenter; mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index 8841521c19f57..5bb25f5425a74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -43,6 +43,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; +import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -112,6 +113,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mStatusBarNotificationPresenter = new StatusBarNotificationPresenter( mContext, mock(NotificationPanelViewController.class), + mock(QuickSettingsController.class), mock(HeadsUpManagerPhone.class), notificationShadeWindowView, mock(ActivityStarter.class),