[Motion] Split-shade expansion: implement over scroll effect [v1]

Before, there was code to add over scroll on shade expansion, but only for the notification side of the panel.
This CL adds specific split-shade logic for over scroll, which adds over scroll to the entire panel.

Note that this first version still doesn't take into account fling velocity for the overscroll amount.
Also, this isn't a regression from the current state.

Test: Manually
Test: NotificationStackScrollLayoutTest.java
Test: NotificationPanelViewControllerTest.java
Test: ShadeTransitionControllerTest.kt
Test: SplitShadeOverScrollerTest.kt
Fixes: 228022047
Change-Id: Ic6de84d831a4a6e65139c1cdc04a2612b72dcb49
This commit is contained in:
Christian Göllner
2022-04-20 14:09:52 +02:00
committed by Christian Göllner
parent 8731db3349
commit 352c4e44db
13 changed files with 503 additions and 10 deletions

View File

@@ -1179,6 +1179,10 @@
<!-- Maximum over scroll amount for the shade when transition to the full shade. -->
<dimen name="lockscreen_shade_max_over_scroll_amount">24dp</dimen>
<!-- Maximum over scroll amount for the shade when transition to the full shade.
Only used for split-shade. -->
<dimen name="shade_max_over_scroll_amount">@dimen/lockscreen_shade_max_over_scroll_amount</dimen>
<!-- Maximum overshoot for the pulse expansion -->
<dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>

View File

@@ -117,6 +117,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -178,6 +179,7 @@ public class NotificationStackScrollLayoutController {
private final CentralSurfaces mCentralSurfaces;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final ShadeTransitionController mShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
@@ -647,6 +649,7 @@ public class NotificationStackScrollLayoutController {
NotifCollection notifCollection,
NotificationEntryManager notificationEntryManager,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
ShadeTransitionController shadeTransitionController,
IStatusBarService iStatusBarService,
UiEventLogger uiEventLogger,
LayoutInflater layoutInflater,
@@ -675,6 +678,7 @@ public class NotificationStackScrollLayoutController {
mLockscreenUserManager = lockscreenUserManager;
mMetricsLogger = metricsLogger;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mShadeTransitionController = shadeTransitionController;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mResources = resources;
@@ -769,6 +773,7 @@ public class NotificationStackScrollLayoutController {
mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
mLockscreenShadeTransitionController.setStackScroller(this);
mShadeTransitionController.setNotificationStackScrollLayoutController(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);

View File

@@ -179,6 +179,7 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.phone.panelstate.PanelState;
import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -310,6 +311,7 @@ public class NotificationPanelViewController extends PanelViewController {
private final NotificationRemoteInputManager mRemoteInputManager;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final ShadeTransitionController mShadeTransitionController;
private final TapAgainViewController mTapAgainViewController;
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
private final RecordingController mRecordingController;
@@ -745,7 +747,8 @@ public class NotificationPanelViewController extends PanelViewController {
NotificationListContainer notificationListContainer,
PanelEventsEmitter panelEventsEmitter,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController) {
super(view,
falsingManager,
dozeLog,
@@ -826,7 +829,9 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mShadeTransitionController = shadeTransitionController;
lockscreenShadeTransitionController.setNotificationPanelController(this);
shadeTransitionController.setNotificationPanelViewController(this);
DynamicPrivacyControlListener
dynamicPrivacyControlListener =
new DynamicPrivacyControlListener();
@@ -3613,6 +3618,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
});
mLockscreenShadeTransitionController.setQS(mQs);
mShadeTransitionController.setQs(mQs);
mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
mQs.setScrollListener(mScrollListener);
updateQsExpansion();

View File

@@ -108,6 +108,8 @@ public abstract class PanelViewController {
*/
private boolean mIsSpringBackAnimation;
private boolean mInSplitShade;
private void logf(String fmt, Object... args) {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
}
@@ -303,8 +305,9 @@ public abstract class PanelViewController {
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
mUnlockFalsingThreshold = mResources.getDimensionPixelSize(
R.dimen.unlock_falsing_threshold);
mUnlockFalsingThreshold =
mResources.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
}
protected float getTouchSlop(MotionEvent event) {
@@ -600,10 +603,12 @@ public abstract class PanelViewController {
}
mIsFlinging = true;
// we want to perform an overshoot animation when flinging open
final boolean addOverscroll = expand
&& mStatusBarStateController.getState() != StatusBarState.KEYGUARD
&& mOverExpansion == 0.0f
&& vel >= 0;
final boolean addOverscroll =
expand
&& !mInSplitShade // Split shade has its own overscroll logic
&& mStatusBarStateController.getState() != StatusBarState.KEYGUARD
&& mOverExpansion == 0.0f
&& vel >= 0;
final boolean shouldSpringBack = addOverscroll || (mOverExpansion != 0.0f && expand);
float overshootAmount = 0.0f;
if (addOverscroll) {
@@ -777,7 +782,8 @@ public abstract class PanelViewController {
}
float maxPanelHeight = getMaxPanelHeight();
if (mHeightAnimator == null) {
if (mTracking) {
// Split shade has its own overscroll logic
if (mTracking && !mInSplitShade) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
}

View File

@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.phone.panelstate
/** A listener interface to be notified of state change events for the notification panel. */
interface PanelStateListener {
fun interface PanelStateListener {
/** Called when the panel's expansion state has changed. */
fun onPanelStateChanged(@PanelState state: Int)
}

View File

@@ -0,0 +1,14 @@
package com.android.systemui.statusbar.phone.shade.transition
import javax.inject.Inject
/**
* An implementation on [ShadeOverScroller] that does nothing.
*
* At the moment there is only a concrete implementation [ShadeOverScroller] for split-shade, so
* this one is used when we are not in split-shade.
*/
class NoOpOverScroller @Inject constructor() : ShadeOverScroller {
override fun onPanelStateChanged(newPanelState: Int) {}
override fun onDragDownAmountChanged(newDragDownAmount: Float) {}
}

View File

@@ -0,0 +1,11 @@
package com.android.systemui.statusbar.phone.shade.transition
import com.android.systemui.statusbar.phone.panelstate.PanelState
/** Represents an over scroller for the non-lockscreen shade. */
interface ShadeOverScroller {
fun onPanelStateChanged(@PanelState newPanelState: Int)
fun onDragDownAmountChanged(newDragDownAmount: Float)
}

View File

@@ -0,0 +1,73 @@
package com.android.systemui.statusbar.phone.shade.transition
import android.content.Context
import android.content.res.Configuration
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.qs.QS
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.NotificationPanelViewController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.phone.panelstate.PanelState
import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
/** Controls the shade expansion transition on non-lockscreen. */
@SysUISingleton
class ShadeTransitionController
@Inject
constructor(
configurationController: ConfigurationController,
panelExpansionStateManager: PanelExpansionStateManager,
private val context: Context,
private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory,
private val noOpOverScroller: NoOpOverScroller
) {
lateinit var notificationPanelViewController: NotificationPanelViewController
lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController
lateinit var qs: QS
private var inSplitShade = false
private val splitShadeOverScroller by lazy {
splitShadeOverScrollerFactory.create(qs, notificationStackScrollLayoutController)
}
private val shadeOverScroller: ShadeOverScroller
get() =
if (inSplitShade && propertiesInitialized()) {
splitShadeOverScroller
} else {
noOpOverScroller
}
init {
updateResources()
configurationController.addCallback(
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
updateResources()
}
})
panelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
panelExpansionStateManager.addStateListener(this::onPanelStateChanged)
}
private fun updateResources() {
inSplitShade = context.resources.getBoolean(R.bool.config_use_split_notification_shade)
}
private fun onPanelStateChanged(@PanelState state: Int) {
shadeOverScroller.onPanelStateChanged(state)
}
private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount)
}
private fun propertiesInitialized() =
this::qs.isInitialized &&
this::notificationPanelViewController.isInitialized &&
this::notificationStackScrollLayoutController.isInitialized
}

View File

@@ -0,0 +1,142 @@
package com.android.systemui.statusbar.phone.shade.transition
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.util.MathUtils
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
import com.android.systemui.dump.DumpManager
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.phone.panelstate.PanelState
import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.PrintWriter
class SplitShadeOverScroller
@AssistedInject
constructor(
configurationController: ConfigurationController,
dumpManager: DumpManager,
private val context: Context,
private val scrimController: ScrimController,
@Assisted private val qS: QS,
@Assisted private val nsslController: NotificationStackScrollLayoutController
) : ShadeOverScroller {
private var releaseOverScrollDuration = 0L
private var maxOverScrollAmount = 0
private var previousOverscrollAmount = 0
private var dragDownAmount: Float = 0f
@PanelState private var panelState: Int = STATE_CLOSED
private var releaseOverScrollAnimator: Animator? = null
init {
updateResources()
configurationController.addCallback(
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
updateResources()
}
})
dumpManager.registerDumpable(this::dump)
}
private fun updateResources() {
val resources = context.resources
maxOverScrollAmount = resources.getDimensionPixelSize(R.dimen.shade_max_over_scroll_amount)
releaseOverScrollDuration =
resources.getInteger(R.integer.lockscreen_shade_over_scroll_release_duration).toLong()
}
override fun onPanelStateChanged(@PanelState newPanelState: Int) {
if (shouldReleaseOverscroll(previousState = panelState, newState = newPanelState)) {
releaseOverScroll()
}
panelState = newPanelState
}
override fun onDragDownAmountChanged(newDragDownAmount: Float) {
if (dragDownAmount == newDragDownAmount) {
return
}
dragDownAmount = newDragDownAmount
if (shouldOverscroll()) {
overScroll(newDragDownAmount)
}
}
private fun shouldOverscroll() = panelState == STATE_OPENING
private fun shouldReleaseOverscroll(@PanelState previousState: Int, @PanelState newState: Int) =
previousState == STATE_OPENING && newState != STATE_OPENING
private fun overScroll(dragDownAmount: Float) {
val overscrollAmount: Int = calculateOverscrollAmount(dragDownAmount)
applyOverscroll(overscrollAmount)
previousOverscrollAmount = overscrollAmount
}
private fun calculateOverscrollAmount(dragDownAmount: Float): Int {
val fullHeight: Int = nsslController.height
val fullHeightProgress: Float = MathUtils.saturate(dragDownAmount / fullHeight)
return (fullHeightProgress * maxOverScrollAmount).toInt()
}
private fun applyOverscroll(overscrollAmount: Int) {
qS.setOverScrollAmount(overscrollAmount)
scrimController.setNotificationsOverScrollAmount(overscrollAmount)
nsslController.setOverScrollAmount(overscrollAmount)
}
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 = Interpolators.STANDARD
animator.duration = releaseOverScrollDuration
animator.start()
releaseOverScrollAnimator = animator
previousOverscrollAmount = 0
}
@VisibleForTesting
internal fun finishAnimations() {
releaseOverScrollAnimator?.end()
releaseOverScrollAnimator = null
}
private fun dump(pw: PrintWriter, strings: Array<String>) {
pw.println(
"""
SplitShadeOverScroller:
Resources:
releaseOverScrollDuration: $releaseOverScrollDuration
maxOverScrollAmount: $maxOverScrollAmount
State:
previousOverscrollAmount: $previousOverscrollAmount
dragDownAmount: $dragDownAmount
panelState: $panelState
""".trimIndent())
}
@AssistedFactory
fun interface Factory {
fun create(
qS: QS,
nsslController: NotificationStackScrollLayoutController
): SplitShadeOverScroller
}
}

View File

@@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -135,6 +136,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private StackStateLogger mStackLogger;
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private ShadeTransitionController mShadeTransitionController;
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
@@ -179,6 +181,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mNotifCollection,
mEntryManager,
mLockscreenShadeTransitionController,
mShadeTransitionController,
mIStatusBarService,
mUiEventLogger,
mLayoutInflater,

View File

@@ -128,6 +128,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -337,6 +338,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
@Mock
private ShadeTransitionController mShadeTransitionController;
@Mock
private QS mQs;
@Mock
private View mQsHeader;
@@ -527,7 +530,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationListContainer,
mPanelEventsEmitter,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController);
mUnlockedScreenOffAnimationController,
mShadeTransitionController);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},

View File

@@ -0,0 +1,118 @@
package com.android.systemui.statusbar.phone.shade.transition
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
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.NotificationPanelViewController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
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.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ShadeTransitionControllerTest : SysuiTestCase() {
@Mock private lateinit var npvc: NotificationPanelViewController
@Mock private lateinit var nsslController: NotificationStackScrollLayoutController
@Mock private lateinit var qs: QS
@Mock private lateinit var noOpOverScroller: NoOpOverScroller
@Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller
private lateinit var controller: ShadeTransitionController
private val configurationController = FakeConfigurationController()
private val panelExpansionStateManager = PanelExpansionStateManager()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
controller =
ShadeTransitionController(
configurationController,
panelExpansionStateManager,
context,
splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
noOpOverScroller)
// Resetting as they are notified upon initialization.
reset(noOpOverScroller, splitShadeOverScroller)
}
@Test
fun onPanelExpansionChanged_inSplitShade_forwardsToSplitShadeOverScroller() {
initLateProperties()
enableSplitShade()
startPanelExpansion()
verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING)
verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
verifyZeroInteractions(noOpOverScroller)
}
@Test
fun onPanelStateChanged_inSplitShade_propertiesNotInitialized_forwardsToNoOpOverScroller() {
enableSplitShade()
startPanelExpansion()
verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
verifyZeroInteractions(splitShadeOverScroller)
}
@Test
fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() {
initLateProperties()
disableSplitShade()
startPanelExpansion()
verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
verifyZeroInteractions(splitShadeOverScroller)
}
private fun initLateProperties() {
controller.qs = qs
controller.notificationStackScrollLayoutController = nsslController
controller.notificationPanelViewController = npvc
}
private fun disableSplitShade() {
setSplitShadeEnabled(false)
}
private fun enableSplitShade() {
setSplitShadeEnabled(true)
}
private fun setSplitShadeEnabled(enabled: Boolean) {
overrideResource(R.bool.config_use_split_notification_shade, enabled)
configurationController.notifyConfigurationChanged()
}
private fun startPanelExpansion() {
panelExpansionStateManager.onPanelExpansionChanged(
fraction = 0.5f,
expanded = true,
tracking = true,
dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT)
}
companion object {
private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
}
}

View File

@@ -0,0 +1,107 @@
package com.android.systemui.statusbar.phone.shade.transition
import org.mockito.Mockito.`when` as whenever
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
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.phone.panelstate.STATE_CLOSED
import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
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.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class SplitShadeOverScrollerTest : SysuiTestCase() {
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var qs: QS
@Mock private lateinit var nsslController: NotificationStackScrollLayoutController
private val configurationController = FakeConfigurationController()
private lateinit var overScroller: SplitShadeOverScroller
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(nsslController.height).thenReturn(1000)
overScroller =
SplitShadeOverScroller(
configurationController, dumpManager, context, scrimController, qs, nsslController)
}
@Test
fun onDragDownAmountChanged_panelOpening_overScrolls_basedOnHeightAndMaxAmount() {
val maxOverScrollAmount = 50
val dragDownAmount = 100f
overrideResource(R.dimen.shade_max_over_scroll_amount, maxOverScrollAmount)
configurationController.notifyConfigurationChanged()
overScroller.onPanelStateChanged(STATE_OPENING)
overScroller.onDragDownAmountChanged(dragDownAmount)
val expectedOverScrollAmount =
(dragDownAmount / nsslController.height * maxOverScrollAmount).toInt()
verify(qs).setOverScrollAmount(expectedOverScrollAmount)
verify(nsslController).setOverScrollAmount(expectedOverScrollAmount)
verify(scrimController).setNotificationsOverScrollAmount(expectedOverScrollAmount)
}
@Test
fun onDragDownAmountChanged_panelClosed_doesNotOverScroll() {
overScroller.onPanelStateChanged(STATE_CLOSED)
overScroller.onDragDownAmountChanged(100f)
verifyZeroInteractions(qs, scrimController, nsslController)
}
@Test
fun onDragDownAmountChanged_panelOpen_doesNotOverScroll() {
overScroller.onPanelStateChanged(STATE_OPEN)
overScroller.onDragDownAmountChanged(100f)
verifyZeroInteractions(qs, scrimController, nsslController)
}
@Test
fun onPanelStateChanged_opening_thenOpen_releasesOverScroll() {
overScroller.onPanelStateChanged(STATE_OPENING)
overScroller.onDragDownAmountChanged(100f)
overScroller.onPanelStateChanged(STATE_OPEN)
overScroller.finishAnimations()
verify(qs, atLeastOnce()).setOverScrollAmount(0)
verify(scrimController, atLeastOnce()).setNotificationsOverScrollAmount(0)
verify(nsslController, atLeastOnce()).setOverScrollAmount(0)
}
@Test
fun onPanelStateChanged_opening_thenClosed_releasesOverScroll() {
overScroller.onPanelStateChanged(STATE_OPENING)
overScroller.onDragDownAmountChanged(100f)
overScroller.onPanelStateChanged(STATE_CLOSED)
overScroller.finishAnimations()
verify(qs, atLeastOnce()).setOverScrollAmount(0)
verify(scrimController, atLeastOnce()).setNotificationsOverScrollAmount(0)
verify(nsslController, atLeastOnce()).setOverScrollAmount(0)
}
}