From 43d826f755abbdc5fec0b1bf1c168bee82271b2a Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Thu, 17 Mar 2022 11:45:59 -0400 Subject: [PATCH] Debounce the inset application When toggling between light/dark (and maybe other config changes), the insets for the nav/gesture bar go to 0 for a short while. Debounce the updating of these values. Fixes: 224888027 Test: atest NotificationQSContainerControllerTest Test: manual, toggle dark mode Change-Id: I2960147df643a8fbdaf0a33668fd4db2392a83d2 --- .../NotificationsQSContainerController.kt | 39 +++++++++++++++---- .../NotificationQSContainerControllerTest.kt | 38 ++++++++++++++++-- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt index 7764d33856ca8..a15feeb9cda84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt @@ -4,6 +4,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowInsets +import androidx.annotation.VisibleForTesting import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END @@ -11,6 +12,7 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.R +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.navigationbar.NavigationModeController @@ -21,14 +23,19 @@ import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.util.Utils import com.android.systemui.util.ViewController +import com.android.systemui.util.concurrency.DelayableExecutor import java.util.function.Consumer import javax.inject.Inject +@VisibleForTesting +internal const val INSET_DEBOUNCE_MILLIS = 500L + class NotificationsQSContainerController @Inject constructor( view: NotificationsQuickSettingsContainer, private val navigationModeController: NavigationModeController, private val overviewProxyService: OverviewProxyService, - private val featureFlags: FeatureFlags + private val featureFlags: FeatureFlags, + @Main private val delayableExecutor: DelayableExecutor ) : ViewController(view), QSContainerController { var qsExpanded = false @@ -60,11 +67,29 @@ class NotificationsQSContainerController @Inject constructor( taskbarVisible = visible } } - private val windowInsetsListener: Consumer = Consumer { insets -> - // when taskbar is visible, stableInsetBottom will include its height - bottomStableInsets = insets.stableInsetBottom - bottomCutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0 - updateBottomSpacing() + + // With certain configuration changes (like light/dark changes), the nav bar will disappear + // for a bit, causing `bottomStableInsets` to be unstable for some time. Debounce the value + // for 500ms. + // All interactions with this object happen in the main thread. + private val delayedInsetSetter = object : Runnable, Consumer { + private var canceller: Runnable? = null + private var stableInsets = 0 + private var cutoutInsets = 0 + + override fun accept(insets: WindowInsets) { + // when taskbar is visible, stableInsetBottom will include its height + stableInsets = insets.stableInsetBottom + cutoutInsets = insets.displayCutout?.safeInsetBottom ?: 0 + canceller?.run() + canceller = delayableExecutor.executeDelayed(this, INSET_DEBOUNCE_MILLIS) + } + + override fun run() { + bottomStableInsets = stableInsets + bottomCutoutInsets = cutoutInsets + updateBottomSpacing() + } } override fun onInit() { @@ -77,7 +102,7 @@ class NotificationsQSContainerController @Inject constructor( public override fun onViewAttached() { updateResources() overviewProxyService.addCallback(taskbarVisibilityListener) - mView.setInsetsChangedListener(windowInsetsListener) + mView.setInsetsChangedListener(delayedInsetSetter) mView.setQSFragmentAttachedListener { qs: QS -> qs.setContainerController(this) } mView.setConfigurationChangedListener { updateResources() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt index 6526fabefe493..9e7b6c514ca34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt @@ -18,6 +18,8 @@ import com.android.systemui.navigationbar.NavigationModeController import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener import com.android.systemui.recents.OverviewProxyService import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -31,6 +33,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.doNothing import org.mockito.Mockito.eq import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.function.Consumer @@ -71,6 +74,8 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { private lateinit var navigationModeCallback: ModeChangedListener private lateinit var taskbarVisibilityCallback: OverviewProxyListener private lateinit var windowInsetsCallback: Consumer + private lateinit var delayableExecutor: FakeExecutor + private lateinit var fakeSystemClock: FakeSystemClock @Before fun setup() { @@ -78,11 +83,14 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { mContext.ensureTestableResources() whenever(notificationsQSContainer.context).thenReturn(mContext) whenever(notificationsQSContainer.resources).thenReturn(mContext.resources) + fakeSystemClock = FakeSystemClock() + delayableExecutor = FakeExecutor(fakeSystemClock) controller = NotificationsQSContainerController( notificationsQSContainer, navigationModeController, overviewProxyService, - featureFlags + featureFlags, + delayableExecutor ) overrideResource(R.dimen.split_shade_notifications_scrim_margin_bottom, SCRIM_MARGIN) @@ -490,13 +498,32 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { container.addView(newViewWithId(1)) container.addView(newViewWithId(View.NO_ID)) val controller = NotificationsQSContainerController(container, navigationModeController, - overviewProxyService, featureFlags) + overviewProxyService, featureFlags, delayableExecutor) controller.updateResources() assertThat(container.getChildAt(0).id).isEqualTo(1) assertThat(container.getChildAt(1).id).isNotEqualTo(View.NO_ID) } + @Test + fun testWindowInsetDebounce() { + disableSplitShade() + useNewFooter(true) + + given(taskbarVisible = false, + navigationMode = GESTURES_NAVIGATION, + insets = emptyInsets(), + applyImmediately = false) + fakeSystemClock.advanceTime(INSET_DEBOUNCE_MILLIS / 2) + windowInsetsCallback.accept(windowInsets().withStableBottom()) + + delayableExecutor.advanceClockToLast() + delayableExecutor.runAllReady() + + verify(notificationsQSContainer, never()).setQSContainerPaddingBottom(0) + verify(notificationsQSContainer).setQSContainerPaddingBottom(STABLE_INSET_BOTTOM) + } + private fun disableSplitShade() { setSplitShadeEnabled(false) } @@ -513,12 +540,17 @@ class NotificationQSContainerControllerTest : SysuiTestCase() { private fun given( taskbarVisible: Boolean, navigationMode: Int, - insets: WindowInsets + insets: WindowInsets, + applyImmediately: Boolean = true ) { Mockito.clearInvocations(notificationsQSContainer) taskbarVisibilityCallback.onTaskbarStatusUpdated(taskbarVisible, false) navigationModeCallback.onNavigationModeChanged(navigationMode) windowInsetsCallback.accept(insets) + if (applyImmediately) { + delayableExecutor.advanceClockToLast() + delayableExecutor.runAllReady() + } } fun then(