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

Commit 4c0b540f authored by Lyn Han's avatar Lyn Han Committed by Android (Google) Code Review
Browse files

Merge "[Dual Shade] Fix first HUN not showing over LS QS" into main

parents 83de3874 19711712
Loading
Loading
Loading
Loading
+136 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationContainerInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableSceneContainer
class NotificationContainerInteractorTest : SysuiTestCase() {

    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
    private val underTest
        get() = kosmos.notificationContainerInteractor

    private fun expandShade() {
        kosmos.sceneInteractor.snapToScene(Scenes.Shade, "test")
        kosmos.sceneInteractor.setTransitionState(
            flowOf(ObservableTransitionState.Idle(Scenes.Shade))
        )
    }

    private fun collapseShade() {
        kosmos.sceneInteractor.snapToScene(Scenes.Lockscreen, "test")
        kosmos.sceneInteractor.setTransitionState(
            flowOf(ObservableTransitionState.Idle(Scenes.Lockscreen))
        )
    }

    private fun pinNotif() {
        kosmos.headsUpNotificationRepository.setNotifications(
            listOf(
                UnconfinedFakeHeadsUpRowRepository(
                    key = "key",
                    pinnedStatus = MutableStateFlow(PinnedStatus.PinnedByUser),
                )
            )
        )
    }

    @Test
    fun activate_collapsed_noUnpinAll() =
        kosmos.runTest {
            // GIVEN one pinned notif in COLLAPSED shade
            pinNotif()
            collapseShade()

            // WHEN the interactor is activated
            underTest.activateIn(testScope)

            // THEN unpinAll is NOT called because there was no false->true transition
            assertThat(headsUpNotificationRepository.orderedHeadsUpRows.value[0].pinnedStatus.value)
                .isEqualTo(PinnedStatus.PinnedByUser)
        }

    @Test
    fun activate_expanded_noUnpinAll() =
        kosmos.runTest {
            // GIVEN one pinned notif in EXPANDED shade
            pinNotif()
            expandShade()

            // WHEN the interactor is activated
            underTest.activateIn(testScope)

            // THEN unpinAll is NOT called because there was no false->true transition
            assertThat(headsUpNotificationRepository.orderedHeadsUpRows.value[0].pinnedStatus.value)
                .isEqualTo(PinnedStatus.PinnedByUser)
        }

    @Test
    fun activate_collapsedToExpanded_unpinAll() =
        kosmos.runTest {
            // GIVEN one pinned notif in COLLAPSED shade
            pinNotif()
            collapseShade()
            underTest.activateIn(testScope)

            // WHEN the shade EXPANDS
            expandShade()

            // THEN unpinned IS called
            assertThat(headsUpNotificationRepository.orderedHeadsUpRows.value[0].pinnedStatus.value)
                .isEqualTo(PinnedStatus.NotPinned)
        }

    @Test
    fun activate_expandedToCollapsed_noUnpin() =
        kosmos.runTest {
            // GIVEN one pinned notif in EXPANDED shade
            pinNotif()
            expandShade()
            underTest.activateIn(testScope)

            // WHEN the shade COLLAPSES
            collapseShade()

            // THEN unpinned is NOT called
            assertThat(headsUpNotificationRepository.orderedHeadsUpRows.value[0].pinnedStatus.value)
                .isEqualTo(PinnedStatus.PinnedByUser)
        }
}
+5 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.qs.panels.ui.viewmodel.AnimateQsTilesViewModel
import com.android.systemui.statusbar.notification.domain.interactor.NotificationContainerInteractor
import com.android.systemui.scene.domain.interactor.OnBootTransitionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.logger.SceneLogger
@@ -69,6 +70,7 @@ constructor(
    private val falsingInteractor: FalsingInteractor,
    private val powerInteractor: PowerInteractor,
    private val onBootTransitionInteractor: OnBootTransitionInteractor,
    private val notificationContainerInteractor: NotificationContainerInteractor,
    shadeModeInteractor: ShadeModeInteractor,
    private val remoteInputInteractor: RemoteInputInteractor,
    private val logger: SceneLogger,
@@ -145,6 +147,9 @@ constructor(
            coroutineScope {
                launch { hydrator.activate() }
                launch("SceneContainerHapticsViewModel") { hapticsViewModel.activate() }
                launch("NotificationContainerInteractor") {
                    notificationContainerInteractor.activate()
                }
            }
            awaitCancellation()
        } finally {
+47 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.pairwise
import kotlinx.coroutines.flow.filter
import javax.inject.Inject

/**
 * Manages notification-related logic that needs to persist across scene changes within the scene
 * container.
 */
@SysUISingleton
class NotificationContainerInteractor
@Inject
constructor(
    private val shadeInteractor: ShadeInteractor,
    private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
) : ExclusiveActivatable() {

    override suspend fun onActivated() {
        // Unpin all HUNs only when the shade transitions from closed to open.
        shadeInteractor.isAnyExpanded
            .pairwise()
            .filter { (wasExpanded, isExpanded) -> !wasExpanded && isExpanded }
            .collect {
                headsUpNotificationInteractor.unpinAll(true)
            }
    }
}
+0 −6
Original line number Diff line number Diff line
@@ -115,12 +115,6 @@ constructor(
        coroutineScope {
            launch { hydrator.activate() }

            launch(context = mainContext) {
                shadeInteractor.isAnyExpanded
                    .filter { it }
                    .collect { headsUpNotificationInteractor.unpinAll(true) }
            }

            launch {
                sceneInteractor.transitionState
                    .filter { it is ObservableTransitionState.Idle }
+2 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.systemui.scene.ui.viewmodel.dualShadeEducationalTooltipsViewM
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationContainerInteractor
import com.android.systemui.wallpapers.ui.viewmodel.wallpaperViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import org.mockito.kotlin.mock
@@ -113,6 +114,7 @@ val Kosmos.sceneContainerViewModelFactory by Fixture {
                burnIn = aodBurnInViewModel,
                clock = keyguardClockViewModel,
                onBootTransitionInteractor = onBootTransitionInteractor,
                notificationContainerInteractor = notificationContainerInteractor,
                dualShadeEducationalTooltipsViewModelFactory =
                    dualShadeEducationalTooltipsViewModelFactory,
                animateQsTilesViewModelFactory = animateQsTilesViewModelFactory,
Loading