[Motion] Split-shade transition on LS: add an overshoot/overscroll effect

- Applies over scroll to QS, Notifications Scrim, and NSSL instead of only NSSL
- Extracts the over scroll logic out of NSSL and into its own class adds tests

Fixes: 222034650
Test: Unit tests + Manually
Change-Id: I7e9363c3003c92bfe850b86cc9b0465b0d33cdc3
This commit is contained in:
Christian Göllner
2022-03-24 10:04:13 +00:00
parent c5b61d9387
commit 752f82ea9d
17 changed files with 528 additions and 38 deletions

View File

@@ -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
*/

View File

@@ -38,6 +38,8 @@
<dimen name="status_bar_header_height_keyguard">42dp</dimen>
<dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
<!-- Distance that the full shade transition takes in order to complete by tapping on a button
like "expand". -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
* Copyright (c) 2006, 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.
*/
-->
<resources>
<integer name="lockscreen_shade_over_scroll_release_duration">500</integer>
</resources>

View File

@@ -26,4 +26,6 @@
<dimen name="status_bar_header_height_keyguard">56dp</dimen>
<dimen name="qs_media_session_height_expanded">251dp</dimen>
<dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen>
</resources>

View File

@@ -1174,9 +1174,8 @@
the shade -->
<dimen name="lockscreen_shade_media_transition_distance">120dp</dimen>
<!-- Maximum overshoot for the topPadding of notifications when transitioning to the full
shade -->
<dimen name="lockscreen_shade_notification_movement">24dp</dimen>
<!-- Maximum over scroll amount for the shade when transition to the full shade. -->
<dimen name="lockscreen_shade_max_over_scroll_amount">24dp</dimen>
<!-- Maximum overshoot for the pulse expansion -->
<dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>

View File

@@ -25,4 +25,7 @@
See com.android.systemui.volume.VolumeDialogImpl.
Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
<integer name="volume_dialog_gravity">21</integer>
<!-- The time it takes for the over scroll release animation to complete, in milli seconds. -->
<integer name="lockscreen_shade_over_scroll_release_duration">0</integer>
</resources>

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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);
}
/** */

View File

@@ -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.
*

View File

@@ -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 <T> 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)
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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();