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

Commit 14c1c515 authored by Lucas Silva's avatar Lucas Silva Committed by Android (Google) Code Review
Browse files

Merge "Properly hide notifications when glanceable hub is showing" into main

parents 78c94616 227ef414
Loading
Loading
Loading
Loading
+9 −4
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn

/** Encapsulates business-logic related to communal mode. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -239,10 +240,14 @@ constructor(
     * This will not be true while transitioning to the hub and will turn false immediately when a
     * swipe to exit the hub starts.
     */
    val isIdleOnCommunal: Flow<Boolean> =
        communalRepository.transitionState.map {
            it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal
        }
    val isIdleOnCommunal: StateFlow<Boolean> =
        communalRepository.transitionState
            .map { it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal }
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                initialValue = false,
            )

    /**
     * Flow that emits a boolean if any portion of the communal UI is visible at all.
+45 −5
Original line number Diff line number Diff line
@@ -23,7 +23,9 @@ import androidx.core.animation.ObjectAnimator
import com.android.app.animation.Interpolators
import com.android.app.animation.InterpolatorsAndroidX
import com.android.systemui.Dumpable
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -47,11 +49,14 @@ import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

@SysUISingleton
class NotificationWakeUpCoordinator
@Inject
constructor(
    @Application applicationScope: CoroutineScope,
    dumpManager: DumpManager,
    private val mHeadsUpManager: HeadsUpManager,
    private val statusBarStateController: StatusBarStateController,
@@ -60,6 +65,7 @@ constructor(
    private val screenOffAnimationController: ScreenOffAnimationController,
    private val logger: NotificationWakeUpCoordinatorLogger,
    private val notifsKeyguardInteractor: NotificationsKeyguardInteractor,
    private val communalInteractor: CommunalInteractor,
) :
    OnHeadsUpChangedListener,
    StatusBarStateController.StateListener,
@@ -201,6 +207,13 @@ constructor(
                }
            }
        )
        applicationScope.launch {
            communalInteractor.isIdleOnCommunal.collect {
                if (!overrideDozeAmountIfCommunalShowing()) {
                    maybeClearHardDozeAmountOverrideHidingNotifs()
                }
            }
        }
    }

    fun setStackScroller(stackScrollerController: NotificationStackScrollLayoutController) {
@@ -302,6 +315,10 @@ constructor(
            return
        }

        if (overrideDozeAmountIfCommunalShowing()) {
            return
        }

        if (clearHardDozeAmountOverride()) {
            return
        }
@@ -311,10 +328,13 @@ constructor(

    private fun setHardDozeAmountOverride(dozing: Boolean, source: String) {
        logger.logSetDozeAmountOverride(dozing = dozing, source = source)
        val previousOverride = hardDozeAmountOverride
        hardDozeAmountOverride = if (dozing) 1f else 0f
        hardDozeAmountOverrideSource = source
        if (previousOverride != hardDozeAmountOverride) {
            updateDozeAmount()
        }
    }

    private fun clearHardDozeAmountOverride(): Boolean {
        if (hardDozeAmountOverride == null) return false
@@ -434,6 +454,11 @@ constructor(
            return
        }

        if (overrideDozeAmountIfCommunalShowing()) {
            this.state = newState
            return
        }

        maybeClearHardDozeAmountOverrideHidingNotifs()

        this.state = newState
@@ -471,6 +496,18 @@ constructor(
        return false
    }

    private fun overrideDozeAmountIfCommunalShowing(): Boolean {
        if (communalInteractor.isIdleOnCommunal.value) {
            if (statusBarStateController.state == StatusBarState.KEYGUARD) {
                setHardDozeAmountOverride(dozing = true, source = "Override: communal (keyguard)")
            } else {
                setHardDozeAmountOverride(dozing = false, source = "Override: communal (shade)")
            }
            return true
        }
        return false
    }

    /**
     * If the last [setDozeAmount] call was an override to hide notifications, then this call will
     * check for the set of states that may have caused that override, and if none of them still
@@ -483,20 +520,23 @@ constructor(
            val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
            val dozing = statusBarStateController.isDozing
            val bypass = bypassController.bypassEnabled
            val idleOnCommunal = communalInteractor.isIdleOnCommunal.value
            val animating =
                screenOffAnimationController.overrideNotificationsFullyDozingOnKeyguard()
            // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff] and
            // [overrideDozeAmountIfBypass] based on 'animating' and 'bypass' respectively, so only
            // clear the override if both those conditions are cleared.  But also require either
            // Overrides are set by [overrideDozeAmountIfAnimatingScreenOff],
            // [overrideDozeAmountIfBypass] and [overrideDozeAmountIfCommunalShowing] based on
            // 'animating', 'bypass' and 'idleOnCommunal' respectively, so only clear the override
            // if all of those conditions are cleared.  But also require either
            // !dozing or !onKeyguard because those conditions should indicate that we intend
            // notifications to be visible, and thus it is safe to unhide them.
            val willRemove = (!onKeyguard || !dozing) && !bypass && !animating
            val willRemove = (!onKeyguard || !dozing) && !bypass && !animating && !idleOnCommunal
            logger.logMaybeClearHardDozeAmountOverrideHidingNotifs(
                willRemove = willRemove,
                onKeyguard = onKeyguard,
                dozing = dozing,
                bypass = bypass,
                animating = animating,
                idleOnCommunal = idleOnCommunal,
            )
            if (willRemove) {
                clearHardDozeAmountOverride()
+2 −1
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
        onKeyguard: Boolean,
        dozing: Boolean,
        bypass: Boolean,
        idleOnCommunal: Boolean,
        animating: Boolean,
    ) {
        buffer.log(
@@ -103,7 +104,7 @@ constructor(@NotificationLockscreenLog private val buffer: LogBuffer) {
            {
                str1 =
                    "willRemove=$willRemove onKeyguard=$onKeyguard dozing=$dozing" +
                        " bypass=$bypass animating=$animating"
                        " bypass=$bypass animating=$animating idleOnCommunal=$idleOnCommunal"
            },
            { "maybeClearHardDozeAmountOverrideHidingNotifs() $str1" }
        )
+3 −1
Original line number Diff line number Diff line
@@ -604,6 +604,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
                new NotificationsKeyguardInteractor(notifsKeyguardViewStateRepository);
        NotificationWakeUpCoordinator coordinator =
                new NotificationWakeUpCoordinator(
                        mKosmos.getTestScope().getBackgroundScope(),
                        mDumpManager,
                        mock(HeadsUpManager.class),
                        new StatusBarStateControllerImpl(
@@ -618,7 +619,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
                        mDozeParameters,
                        mScreenOffAnimationController,
                        new NotificationWakeUpCoordinatorLogger(logcatLogBuffer()),
                        notifsKeyguardInteractor);
                        notifsKeyguardInteractor,
                        mKosmos.getCommunalInteractor());
        mConfigurationController = new ConfigurationControllerImpl(mContext);
        PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
                mContext,
+49 −2
Original line number Diff line number Diff line
@@ -19,10 +19,15 @@ package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dump.DumpManager
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
@@ -34,11 +39,16 @@ import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -49,6 +59,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions

@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -56,7 +67,8 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {

    @get:Rule val animatorTestRule = AnimatorTestRule(this)

    private val kosmos = Kosmos()
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope

    private val dumpManager: DumpManager = mock()
    private val headsUpManager: HeadsUpManager = mock()
@@ -97,6 +109,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
        whenever(statusBarStateController.state).then { statusBarState }
        notificationWakeUpCoordinator =
            NotificationWakeUpCoordinator(
                kosmos.applicationCoroutineScope,
                dumpManager,
                headsUpManager,
                statusBarStateController,
@@ -105,6 +118,7 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
                screenOffAnimationController,
                logger,
                kosmos.notificationsKeyguardInteractor,
                kosmos.communalInteractor,
            )
        statusBarStateCallback = withArgCaptor {
            verify(statusBarStateController).addCallback(capture())
@@ -160,6 +174,39 @@ class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
        assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
    }

    @Test
    fun setDozeToZeroWhenCommunalShowingWillFullyHideNotifications() =
        testScope.runTest {
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                )
            kosmos.communalRepository.setTransitionState(transitionState)
            runCurrent()
            setDozeAmount(0f)
            verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
        }

    @Test
    fun closingCommunalWillShowNotifications() =
        testScope.runTest {
            val transitionState =
                MutableStateFlow<ObservableTransitionState>(
                    ObservableTransitionState.Idle(CommunalScenes.Communal)
                )
            kosmos.communalRepository.setTransitionState(transitionState)
            runCurrent()
            setDozeAmount(0f)
            verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()

            transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Blank)
            runCurrent()
            verifyStackScrollerDozeAndHideAmount(dozeAmount = 0f, hideAmount = 0f)
            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
        }

    @Test
    fun switchingToShadeWithBypassEnabledWillShowNotifications() {
        setDozeToZeroWithBypassWillFullyHideNotifications()