diff options
13 files changed, 503 insertions, 10 deletions
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4a8fd1b00dde..0a858af31964 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -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> 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 c2750c2d2a6f..2493ccbe5a48 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 @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 0b46f07f8e8b..a01071222199 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -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(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 9e707644782c..6637394e2b2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -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 */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt index e29959290355..ca667dddbe8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelStateListener.kt @@ -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) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt new file mode 100644 index 000000000000..2789db874249 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt @@ -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) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt new file mode 100644 index 000000000000..f1cedeb21e0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt @@ -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) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt new file mode 100644 index 000000000000..2762b9a38e92 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt @@ -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 +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt new file mode 100644 index 000000000000..71050f2e7c67 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt @@ -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 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 6409967aca9b..94a93ad6cf33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index cad603c85bbc..00ac005845f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -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, () -> {}, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt new file mode 100644 index 000000000000..39d33e86925e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt @@ -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 + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt new file mode 100644 index 000000000000..219737d1dfb4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt @@ -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) + } +} |