diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 8ad200951eb47..75d95e6d85e30 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -122,6 +122,11 @@ public interface QS extends FragmentBase {
*/
default void setScrollListener(ScrollListener scrollListener) {}
+ /**
+ * Sets the amount of vertical over scroll that should be performed on QS.
+ */
+ default void setOverScrollAmount(int overScrollAmount) {}
+
/**
* Callback for when QSPanel container is scrolled
*/
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 9f14d2f236b5c..76780ffd2f80e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -38,6 +38,8 @@
42dp
+ 32dp
+
200dp
diff --git a/packages/SystemUI/res/values-sw600dp-land/integers.xml b/packages/SystemUI/res/values-sw600dp-land/integers.xml
new file mode 100644
index 0000000000000..919d6055667bc
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-land/integers.xml
@@ -0,0 +1,20 @@
+
+
+
+ 500
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index cff660d26cb1e..0512d3c2c3a48 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -26,4 +26,6 @@
56dp
251dp
+
+ 42dp
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a309cc4ff1f5b..f39b5efdca24e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1174,9 +1174,8 @@
the shade -->
120dp
-
- 24dp
+
+ 24dp
32dp
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index f0f7a196d6940..3164ed1e67513 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -25,4 +25,7 @@
See com.android.systemui.volume.VolumeDialogImpl.
Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
21
+
+
+ 0
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 4e631046063bf..f3d71d1d76298 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -141,6 +141,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
*/
private float mFullShadeProgress;
+ private boolean mOverScrolling;
+
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
QSTileHost qsTileHost,
@@ -497,6 +499,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
isTransitioningToFullShade ? progress : mSquishinessFraction);
}
+ @Override
+ public void setOverScrollAmount(int overScrollAmount) {
+ mOverScrolling = overScrollAmount != 0;
+ getView().setTranslationY(overScrollAmount);
+ }
+
@Override
public int getHeightDiff() {
return mQSPanelScrollView.getBottom() - mHeader.getBottom()
@@ -515,7 +523,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
? 1 : QSAnimator.SHORT_PARALLAX_AMOUNT) * (expansion - 1);
boolean onKeyguard = isKeyguardState();
boolean onKeyguardAndExpanded = onKeyguard && !mShowCollapsedOnKeyguard;
- if (!mHeaderAnimating && !headerWillBeAnimating()) {
+ if (!mHeaderAnimating && !headerWillBeAnimating() && !mOverScrolling) {
getView().setTranslationY(
onKeyguardAndExpanded
? translationScaleY * mHeader.getHeight()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt
new file mode 100644
index 0000000000000..de37a38e969e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt
@@ -0,0 +1,8 @@
+package com.android.systemui.statusbar
+
+/** Represents an over scroller for the transition to full shade on lock screen. */
+interface LockScreenShadeOverScroller {
+
+ /** The amount in pixels that the user has dragged to expand the shade. */
+ var expansionDragDownAmount: Float
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 310eb4f246525..8f78feb979b89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -64,6 +64,8 @@ class LockscreenShadeTransitionController @Inject constructor(
private val scrimController: ScrimController,
private val depthController: NotificationShadeDepthController,
private val context: Context,
+ private val splitShadeOverScrollerFactory: SplitShadeLockScreenOverScroller.Factory,
+ private val singleShadeOverScrollerFactory: SingleShadeLockScreenOverScroller.Factory,
wakefulnessLifecycle: WakefulnessLifecycle,
configurationController: ConfigurationController,
falsingManager: FalsingManager,
@@ -194,6 +196,27 @@ class LockscreenShadeTransitionController @Inject constructor(
*/
val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, context)
+ private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy {
+ splitShadeOverScrollerFactory.create(qS, nsslController)
+ }
+
+ private val phoneShadeOverScroller: SingleShadeLockScreenOverScroller by lazy {
+ singleShadeOverScrollerFactory.create(nsslController)
+ }
+
+ /**
+ * [LockScreenShadeOverScroller] property that delegates to either
+ * [SingleShadeLockScreenOverScroller] or [SplitShadeLockScreenOverScroller].
+ *
+ * There are currently two different implementations, as the over scroll behavior is different
+ * on single shade and split shade.
+ *
+ * On single shade, only notifications are over scrolled, whereas on split shade, everything is
+ * over scrolled.
+ */
+ private val shadeOverScroller: LockScreenShadeOverScroller
+ get() = if (useSplitShade) splitShadeOverScroller else phoneShadeOverScroller
+
init {
updateResources()
configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
@@ -410,7 +433,7 @@ class LockscreenShadeTransitionController @Inject constructor(
if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
val notificationShelfProgress =
MathUtils.saturate(dragDownAmount / notificationShelfTransitionDistance)
- nsslController.setTransitionToFullShadeAmount(field, notificationShelfProgress)
+ nsslController.setTransitionToFullShadeAmount(notificationShelfProgress)
qSDragProgress = MathUtils.saturate(dragDownAmount / qsTransitionDistance)
qS.setTransitionToFullShadeAmount(field, qSDragProgress)
@@ -422,6 +445,7 @@ class LockscreenShadeTransitionController @Inject constructor(
transitionToShadeAmountScrim(field)
transitionToShadeAmountCommon(field)
transitionToShadeAmountKeyguard(field)
+ shadeOverScroller.expansionDragDownAmount = dragDownAmount
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
new file mode 100644
index 0000000000000..575f354c66201
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt
@@ -0,0 +1,74 @@
+package com.android.systemui.statusbar
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.MathUtils
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class SingleShadeLockScreenOverScroller
+@AssistedInject
+constructor(
+ configurationController: ConfigurationController,
+ private val context: Context,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ @Assisted private val nsslController: NotificationStackScrollLayoutController
+) : LockScreenShadeOverScroller {
+
+ private var maxOverScrollAmount = 0
+ private var totalDistanceForFullShadeTransition = 0
+
+ init {
+ updateResources()
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ })
+ }
+
+ private fun updateResources() {
+ val resources = context.resources
+ totalDistanceForFullShadeTransition =
+ resources.getDimensionPixelSize(R.dimen.lockscreen_shade_qs_transition_distance)
+ maxOverScrollAmount =
+ resources.getDimensionPixelSize(R.dimen.lockscreen_shade_max_over_scroll_amount)
+ }
+
+ override var expansionDragDownAmount: Float = 0f
+ set(value) {
+ if (value == field) {
+ return
+ }
+ field = value
+ overScroll()
+ }
+
+ private fun overScroll() {
+ var extraTopInset = 0.0f
+ if (statusBarStateController.state == StatusBarState.KEYGUARD) {
+ val viewHeight = nsslController.height
+ val overallProgress = MathUtils.saturate(expansionDragDownAmount / viewHeight)
+ val transitionProgress =
+ Interpolators.getOvershootInterpolation(
+ overallProgress,
+ 0.6f,
+ totalDistanceForFullShadeTransition.toFloat() / viewHeight.toFloat())
+ extraTopInset = transitionProgress * maxOverScrollAmount
+ }
+ nsslController.setOverScrollAmount(extraTopInset.toInt())
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ nsslController: NotificationStackScrollLayoutController
+ ): SingleShadeLockScreenOverScroller
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
new file mode 100644
index 0000000000000..96ce6b45dc71d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -0,0 +1,129 @@
+package com.android.systemui.statusbar
+
+import android.animation.Animator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.animation.PathInterpolator
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class SplitShadeLockScreenOverScroller
+@AssistedInject
+constructor(
+ configurationController: ConfigurationController,
+ private val context: Context,
+ private val scrimController: ScrimController,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ @Assisted private val qS: QS,
+ @Assisted private val nsslController: NotificationStackScrollLayoutController
+) : LockScreenShadeOverScroller {
+
+ private var releaseOverScrollAnimator: Animator? = null
+ private var transitionToFullShadeDistance = 0
+ private var releaseOverScrollDuration = 0L
+ private var maxOverScrollAmount = 0
+ private var previousOverscrollAmount = 0
+
+ init {
+ updateResources()
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ })
+ }
+
+ private fun updateResources() {
+ val resources = context.resources
+ transitionToFullShadeDistance =
+ resources.getDimensionPixelSize(R.dimen.lockscreen_shade_full_transition_distance)
+ maxOverScrollAmount =
+ resources.getDimensionPixelSize(R.dimen.lockscreen_shade_max_over_scroll_amount)
+ releaseOverScrollDuration =
+ resources.getInteger(R.integer.lockscreen_shade_over_scroll_release_duration).toLong()
+ }
+
+ override var expansionDragDownAmount: Float = 0f
+ set(dragDownAmount) {
+ if (field == dragDownAmount) {
+ return
+ }
+ field = dragDownAmount
+ if (shouldOverscroll()) {
+ overScroll(dragDownAmount)
+ } else if (shouldReleaseOverscroll()) {
+ releaseOverScroll()
+ }
+ }
+
+ private fun shouldOverscroll() = statusBarStateController.state == StatusBarState.KEYGUARD
+
+ private fun shouldReleaseOverscroll() = !shouldOverscroll() && previousOverscrollAmount != 0
+
+ private fun overScroll(dragDownAmount: Float) {
+ val overscrollAmount: Int = calculateOverscrollAmount(dragDownAmount)
+ applyOverscroll(overscrollAmount)
+ previousOverscrollAmount = overscrollAmount
+ }
+
+ private fun applyOverscroll(overscrollAmount: Int) {
+ qS.setOverScrollAmount(overscrollAmount)
+ scrimController.setNotificationsOverScrollAmount(overscrollAmount)
+ nsslController.setOverScrollAmount(overscrollAmount)
+ }
+
+ private fun calculateOverscrollAmount(dragDownAmount: Float): Int {
+ val fullHeight: Int = nsslController.height
+ val fullHeightProgress: Float = MathUtils.saturate(dragDownAmount / fullHeight)
+ val overshootStart: Float = transitionToFullShadeDistance / fullHeight.toFloat()
+ val overShootTransitionProgress: Float =
+ Interpolators.getOvershootInterpolation(
+ fullHeightProgress, OVER_SHOOT_AMOUNT, overshootStart)
+ return (overShootTransitionProgress * maxOverScrollAmount).toInt()
+ }
+
+ private fun releaseOverScroll() {
+ val animator = ValueAnimator.ofInt(previousOverscrollAmount, 0)
+ animator.addUpdateListener {
+ val overScrollAmount = it.animatedValue as Int
+ qS.setOverScrollAmount(overScrollAmount)
+ scrimController.setNotificationsOverScrollAmount(overScrollAmount)
+ nsslController.setOverScrollAmount(overScrollAmount)
+ }
+ animator.interpolator = RELEASE_OVER_SCROLL_INTERPOLATOR
+ animator.duration = releaseOverScrollDuration
+ animator.start()
+ releaseOverScrollAnimator = animator
+ previousOverscrollAmount = 0
+ }
+
+ @VisibleForTesting
+ internal fun finishAnimations() {
+ releaseOverScrollAnimator?.end()
+ releaseOverScrollAnimator = null
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ qS: QS,
+ nsslController: NotificationStackScrollLayoutController
+ ): SplitShadeLockScreenOverScroller
+ }
+
+ companion object {
+ private const val OVER_SHOOT_AMOUNT = 0.6f
+ private val RELEASE_OVER_SCROLL_INTERPOLATOR = PathInterpolator(0.17f, 0f, 0f, 1f)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d98f8a77cc8d8..5bc50ae11fb74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -41,7 +41,6 @@ import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
-import android.util.MathUtils;
import android.util.Pair;
import android.view.Display;
import android.view.LayoutInflater;
@@ -65,7 +64,6 @@ import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
-import com.android.systemui.animation.Interpolators;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -205,18 +203,6 @@ public class NotificationStackScrollLayoutController {
private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
- /**
- * The total distance in pixels that the full shade transition takes to transition entirely to
- * the full shade.
- */
- private int mTotalDistanceForFullShadeTransition;
-
- /**
- * The amount of movement the notifications do when transitioning to the full shade before
- * reaching the overstrech
- */
- private int mNotificationDragDownMovement;
-
@VisibleForTesting
final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
new View.OnAttachStateChangeListener() {
@@ -304,10 +290,6 @@ public class NotificationStackScrollLayoutController {
private NotifStats mNotifStats = NotifStats.getEmpty();
private void updateResources() {
- mNotificationDragDownMovement = mResources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_notification_movement);
- mTotalDistanceForFullShadeTransition = mResources.getDimensionPixelSize(
- R.dimen.lockscreen_shade_qs_transition_distance);
mNotificationStackSizeCalculator.updateResources();
}
@@ -1537,8 +1519,6 @@ public class NotificationStackScrollLayoutController {
}
/**
- * @param amount The amount of pixels we have currently dragged down
- * for the lockscreen to shade transition. 0f for all other states.
* @param fraction The fraction of lockscreen to shade transition.
* 0f for all other states.
*
@@ -1546,18 +1526,15 @@ public class NotificationStackScrollLayoutController {
* LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
* until the next lockscreen-to-shade transition.
*/
- public void setTransitionToFullShadeAmount(float amount, float fraction) {
+ public void setTransitionToFullShadeAmount(float fraction) {
mView.setFractionToShade(fraction);
+ }
- float extraTopInset = 0.0f;
- if (mStatusBarStateController.getState() == KEYGUARD) {
- float overallProgress = MathUtils.saturate(amount / mView.getHeight());
- float transitionProgress = Interpolators.getOvershootInterpolation(overallProgress,
- 0.6f,
- (float) mTotalDistanceForFullShadeTransition / (float) mView.getHeight());
- extraTopInset = transitionProgress * mNotificationDragDownMovement;
- }
- mView.setExtraTopInsetForFullShadeTransition(extraTopInset);
+ /**
+ * Sets the amount of vertical over scroll that should be performed on NSSL.
+ */
+ public void setOverScrollAmount(int overScrollAmount) {
+ mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 029a7a5fcdd53..7885a70d804fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -629,6 +629,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
}
+ /**
+ * Sets the amount of vertical over scroll that should be performed on the notifications scrim.
+ */
+ public void setNotificationsOverScrollAmount(int overScrollAmount) {
+ mNotificationsScrim.setTranslationY(overScrollAmount);
+ }
+
/**
* Current state of the QuickSettings when pulling it from the top.
*
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 1d2a0ca3777a1..52c8a559b7b35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -44,6 +44,7 @@ import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.junit.MockitoJUnit
private fun anyObject(): T {
@@ -75,6 +76,8 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
@Mock lateinit var expandHelperCallback: ExpandHelper.Callback
@Mock lateinit var mCentralSurfaces: CentralSurfaces
@Mock lateinit var qS: QS
+ @Mock lateinit var singleShadeOverScroller: SingleShadeLockScreenOverScroller
+ @Mock lateinit var splitShadeOverScroller: SplitShadeLockScreenOverScroller
@JvmField @Rule val mockito = MockitoJUnit.rule()
private val configurationController = FakeConfigurationController()
@@ -104,7 +107,9 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
context = context,
configurationController = configurationController,
falsingManager = falsingManager,
- dumpManager = dumpManager
+ dumpManager = dumpManager,
+ splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
+ singleShadeOverScrollerFactory = { singleShadeOverScroller }
)
whenever(nsslController.view).thenReturn(stackscroller)
whenever(nsslController.expandHelperCallback).thenReturn(expandHelperCallback)
@@ -229,7 +234,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
fun testDragDownAmountDoesntCallOutInLockedDownShade() {
whenever(nsslController.isInLockedDownShade).thenReturn(true)
transitionController.dragDownAmount = 10f
- verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
+ verify(nsslController, never()).setTransitionToFullShadeAmount(anyFloat())
verify(mediaHierarchyManager, never()).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController, never()).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
verify(notificationPanelController, never()).setTransitionToFullShadeAmount(anyFloat(),
@@ -240,7 +245,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
@Test
fun testDragDownAmountCallsOut() {
transitionController.dragDownAmount = 10f
- verify(nsslController).setTransitionToFullShadeAmount(anyFloat(), anyFloat())
+ verify(nsslController).setTransitionToFullShadeAmount(anyFloat())
verify(mediaHierarchyManager).setTransitionToFullShadeAmount(anyFloat())
verify(scrimController).setTransitionToFullShadeProgress(anyFloat(), anyFloat())
verify(notificationPanelController).setTransitionToFullShadeAmount(anyFloat(),
@@ -388,6 +393,26 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
verify(mediaHierarchyManager).setTransitionToFullShadeAmount(10f)
}
+ @Test
+ fun setDragAmount_notInSplitShade_forwardsToSingleShadeOverScroller() {
+ disableSplitShade()
+
+ transitionController.dragDownAmount = 10f
+
+ verify(singleShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(splitShadeOverScroller)
+ }
+
+ @Test
+ fun setDragAmount_inSplitShade_forwardsToSplitShadeOverScroller() {
+ enableSplitShade()
+
+ transitionController.dragDownAmount = 10f
+
+ verify(splitShadeOverScroller).expansionDragDownAmount = 10f
+ verifyZeroInteractions(singleShadeOverScroller)
+ }
+
private fun enableSplitShade() {
setSplitShadeEnabled(true)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
new file mode 100644
index 0000000000000..2606be5fabad2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt
@@ -0,0 +1,56 @@
+package com.android.systemui.statusbar
+
+import org.mockito.Mockito.`when` as whenever
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.intThat
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SingleShadeLockScreenOverScrollerTest : SysuiTestCase() {
+
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var nsslController: NotificationStackScrollLayoutController
+
+ private lateinit var overScroller: SingleShadeLockScreenOverScroller
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(nsslController.height).thenReturn(1800)
+ overScroller =
+ SingleShadeLockScreenOverScroller(
+ FakeConfigurationController(),
+ context,
+ statusBarStateController,
+ nsslController
+ )
+ }
+
+ @Test
+ fun setDragDownAmount_onKeyguard_overScrolls() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ overScroller.expansionDragDownAmount = 10f
+
+ verify(nsslController).setOverScrollAmount(intThat { it > 0 })
+ }
+
+ @Test
+ fun setDragDownAmount_notOnKeyguard_doesNotOverScroll() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+ overScroller.expansionDragDownAmount = 10f
+
+ verify(nsslController).setOverScrollAmount(0)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
new file mode 100644
index 0000000000000..9d5099cfafd0e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -0,0 +1,124 @@
+package com.android.systemui.statusbar
+
+import org.mockito.Mockito.`when` as whenever
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.intThat
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
+
+ private val configurationController = FakeConfigurationController()
+
+ @Mock private lateinit var scrimController: ScrimController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var qS: QS
+ @Mock private lateinit var nsslController: NotificationStackScrollLayoutController
+
+ private lateinit var overScroller: SplitShadeLockScreenOverScroller
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(nsslController.height).thenReturn(1800)
+
+ overScroller =
+ SplitShadeLockScreenOverScroller(
+ configurationController,
+ context,
+ scrimController,
+ statusBarStateController,
+ qS,
+ nsslController)
+ }
+
+ @Test
+ fun setDragDownAmount_onKeyguard_appliesOverScroll() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+
+ setDragAmount(1000f)
+
+ verifyOverScrollPerformed()
+ }
+
+ @Test
+ fun setDragDownAmount_notOnKeyguard_doesNotApplyOverScroll() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+
+ setDragAmount(1000f)
+
+ verifyZeroInteractions(qS)
+ verifyZeroInteractions(scrimController)
+ verifyZeroInteractions(nsslController)
+ }
+
+ @Test
+ fun setDragAmount_onKeyguard_thenNotOnKeyguard_resetsOverScrollToZero() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ setDragAmount(1000f)
+ verifyOverScrollPerformed()
+ reset(qS, scrimController, nsslController)
+
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ setDragAmount(999f)
+ verifyOverScrollResetToZero()
+ }
+
+ @Test
+ fun setDragAmount_onKeyguard_thenNotOnKeyguard_multipleTimes_resetsOverScrollToZeroOnlyOnce() {
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ setDragAmount(1000f)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ setDragAmount(999f)
+ reset(qS, scrimController, nsslController)
+
+ setDragAmount(998f)
+ setDragAmount(997f)
+ setDragAmount(996f)
+ verifyNoMoreOverScrollChanges()
+ }
+
+ private fun verifyOverScrollPerformed() {
+ verify(qS).setOverScrollAmount(intThat { it > 0 })
+ verify(scrimController).setNotificationsOverScrollAmount(intThat { it > 0 })
+ verify(nsslController).setOverScrollAmount(intThat { it > 0 })
+ }
+
+ private fun verifyOverScrollResetToZero() {
+ // Might be more than once as the animator might have multiple values close to zero that
+ // round down to zero.
+ verify(qS, atLeast(1)).setOverScrollAmount(0)
+ verify(scrimController, atLeast(1)).setNotificationsOverScrollAmount(0)
+ verify(nsslController, atLeast(1)).setOverScrollAmount(0)
+ }
+
+ private fun verifyNoMoreOverScrollChanges() {
+ verifyNoMoreInteractions(qS)
+ verifyNoMoreInteractions(scrimController)
+ verifyNoMoreInteractions(nsslController)
+ }
+
+ private fun setDragAmount(dragDownAmount: Float) {
+ overScroller.expansionDragDownAmount = dragDownAmount
+ overScroller.finishAnimations()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 786a8586ea39f..903bff283f335 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -1292,6 +1292,33 @@ public class ScrimControllerTest extends SysuiTestCase {
assertThat(mNotificationsScrim.getViewAlpha()).isEqualTo(notifProgress);
}
+ @Test
+ public void setNotificationsOverScrollAmount_setsTranslationYOnNotificationsScrim() {
+ int overScrollAmount = 10;
+
+ mScrimController.setNotificationsOverScrollAmount(overScrollAmount);
+
+ assertThat(mNotificationsScrim.getTranslationY()).isEqualTo(overScrollAmount);
+ }
+
+ @Test
+ public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnBehindScrim() {
+ int overScrollAmount = 10;
+
+ mScrimController.setNotificationsOverScrollAmount(overScrollAmount);
+
+ assertThat(mScrimBehind.getTranslationY()).isEqualTo(0);
+ }
+
+ @Test
+ public void setNotificationsOverScrollAmount_doesNotSetTranslationYOnFrontScrim() {
+ int overScrollAmount = 10;
+
+ mScrimController.setNotificationsOverScrollAmount(overScrollAmount);
+
+ assertThat(mScrimInFront.getTranslationY()).isEqualTo(0);
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();