Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d0df55f authored by András Kurucz's avatar András Kurucz
Browse files

Debounce HeadsUpNotificationInteractor.isHeadsUpOrAnimatingAway

When the last HUN is unpinned, and we are starting the animation, we
should keep this flow true the entire time. This prevents flickering.

Bug: 342495652
Bug: 347489544
Test: atest HeadsUpNotificationInteractorTest
Test: systemui-notification-3-jank-suite
Flag: com.android.systemui.notifications_heads_up_refactor

Change-Id: Id9c8c22fbc0da6cec4469fc5ce74cc58ce16dd90
parent ee38b7c0
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -85,6 +85,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Before
@@ -181,6 +182,7 @@ class SceneContainerStartableTest : SysuiTestCase() {


            kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
            kosmos.headsUpNotificationRepository.activeHeadsUpRows.value =
                buildNotificationRows(isPinned = false)
                buildNotificationRows(isPinned = false)
            advanceTimeBy(50L) // account for HeadsUpNotificationInteractor debounce
            assertThat(isVisible).isFalse()
            assertThat(isVisible).isFalse()
        }
        }


+59 −0
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -278,6 +279,64 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
            assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
            assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
        }
        }


    @Test
    fun isHeadsUpOrAnimatingAway_falseOnStart() =
        testScope.runTest {
            val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)

            runCurrent()

            assertThat(isHeadsUpOrAnimatingAway).isFalse()
        }

    @Test
    fun isHeadsUpOrAnimatingAway_hasPinnedRows() =
        testScope.runTest {
            val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)

            // WHEN a row is pinned
            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
            runCurrent()

            assertThat(isHeadsUpOrAnimatingAway).isTrue()
        }

    @Test
    fun isHeadsUpOrAnimatingAway_headsUpAnimatingAway() =
        testScope.runTest {
            val isHeadsUpOrAnimatingAway by collectLastValue(underTest.isHeadsUpOrAnimatingAway)

            // WHEN the last row is animating away
            headsUpRepository.setHeadsUpAnimatingAway(true)
            runCurrent()

            assertThat(isHeadsUpOrAnimatingAway).isTrue()
        }

    @Test
    fun isHeadsUpOrAnimatingAway_headsUpAnimatingAwayDebounced() =
        testScope.runTest {
            val values by collectValues(underTest.isHeadsUpOrAnimatingAway)

            // GIVEN a row is pinned
            headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
            runCurrent()
            assertThat(values.size).isEqualTo(2)
            assertThat(values.first()).isFalse() // initial value
            assertThat(values.last()).isTrue()

            // WHEN the last row is removed
            headsUpRepository.setNotifications(emptyList())
            runCurrent()
            // AND starts to animate away
            headsUpRepository.setHeadsUpAnimatingAway(true)
            runCurrent()

            // THEN isHeadsUpOrAnimatingAway remained true
            assertThat(values.size).isEqualTo(2)
            assertThat(values.last()).isTrue()
        }

    @Test
    @Test
    fun showHeadsUpStatusBar_true() =
    fun showHeadsUpStatusBar_true() =
        testScope.runTest {
        testScope.runTest {
+20 −5
Original line number Original line Diff line number Diff line
@@ -14,7 +14,7 @@
 * limitations under the License.
 * limitations under the License.
 */
 */


@file:OptIn(ExperimentalCoroutinesApi::class)
@file:OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)


package com.android.systemui.statusbar.notification.domain.interactor
package com.android.systemui.statusbar.notification.domain.interactor


@@ -27,11 +27,15 @@ import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRep
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart


class HeadsUpNotificationInteractor
class HeadsUpNotificationInteractor
@Inject
@Inject
@@ -77,6 +81,17 @@ constructor(
                animatingAway ->
                animatingAway ->
                hasPinnedRows || animatingAway
                hasPinnedRows || animatingAway
            }
            }
            .debounce { isHeadsUpOrAnimatingAway ->
                if (isHeadsUpOrAnimatingAway) {
                    0
                } else {
                    // When the last pinned entry is removed from the [HeadsUpRepository],
                    // there might be a delay before the View starts animating.
                    50L
                }
            }
            .onStart { emit(false) } // emit false, so we don't wait for the initial update
            .distinctUntilChanged()


    private val canShowHeadsUp: Flow<Boolean> =
    private val canShowHeadsUp: Flow<Boolean> =
        combine(
        combine(