Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +19 −2 Original line number Diff line number Diff line Loading @@ -51,6 +51,8 @@ import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.phone.CentralSurfaces Loading Loading @@ -175,10 +177,12 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) assertThat(isVisible).isFalse() kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = true kosmos.headsUpNotificationRepository.activeHeadsUpRows.value = buildNotificationRows(isPinned = true) assertThat(isVisible).isTrue() kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = false kosmos.headsUpNotificationRepository.activeHeadsUpRows.value = buildNotificationRows(isPinned = false) assertThat(isVisible).isFalse() } Loading Loading @@ -1070,4 +1074,17 @@ class SceneContainerStartableTest : SysuiTestCase() { return transitionStateFlow } private fun buildNotificationRows(isPinned: Boolean = false): Set<HeadsUpRowRepository> = setOf( fakeHeadsUpRowRepository(key = "0", isPinned = isPinned), fakeHeadsUpRowRepository(key = "1", isPinned = isPinned), fakeHeadsUpRowRepository(key = "2", isPinned = isPinned), fakeHeadsUpRowRepository(key = "3", isPinned = isPinned), ) private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean) = FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { this.isPinned.value = isPinned } } packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt 0 → 100644 +269 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ @file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.statusbar.notification.domain.interactor import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) class HeadsUpNotificationInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val repository = kosmos.headsUpNotificationRepository private val underTest = kosmos.headsUpNotificationInteractor @Test fun hasPinnedRows_emptyList_false() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) assertThat(hasPinnedRows).isFalse() } @Test fun hasPinnedRows_noPinnedRows_false() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // WHEN no pinned rows are set repository.setNotifications( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) runCurrent() // THEN hasPinnedRows is false assertThat(hasPinnedRows).isFalse() } @Test fun hasPinnedRows_hasPinnedRows_true() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // WHEN a pinned rows is set repository.setNotifications( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) runCurrent() // THEN hasPinnedRows is true assertThat(hasPinnedRows).isTrue() } @Test fun hasPinnedRows_rowGetsPinned_true() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // GIVEN no rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // WHEN a row gets pinned rows[0].isPinned.value = true runCurrent() // THEN hasPinnedRows updates to true assertThat(hasPinnedRows).isTrue() } @Test fun hasPinnedRows_rowGetsUnPinned_false() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // GIVEN one row is pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // THEN that row gets unpinned rows[0].isPinned.value = false runCurrent() // THEN hasPinnedRows updates to false assertThat(hasPinnedRows).isFalse() } @Test fun pinnedRows_noRows_isEmpty() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) assertThat(pinnedHeadsUpRows).isEmpty() } @Test fun pinnedRows_noPinnedRows_isEmpty() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // WHEN no rows are pinned repository.setNotifications( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) runCurrent() // THEN all rows are filtered assertThat(pinnedHeadsUpRows).isEmpty() } @Test fun pinnedRows_hasPinnedRows_containsPinnedRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // WHEN some rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // THEN the unpinned rows are filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1]) } @Test fun pinnedRows_rowGetsPinned_containsPinnedRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // GIVEN some rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // WHEN all rows gets pinned rows[2].isPinned.value = true runCurrent() // THEN no rows are filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1], rows[2]) } @Test fun pinnedRows_allRowsPinned_containsAllRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // WHEN all rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2", isPinned = true), ) repository.setNotifications(rows) runCurrent() // THEN no rows are filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1], rows[2]) } @Test fun pinnedRows_rowGetsUnPinned_containsPinnedRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // GIVEN all rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2", isPinned = true), ) repository.setNotifications(rows) runCurrent() // WHEN a row gets unpinned rows[0].isPinned.value = false runCurrent() // THEN the unpinned row is filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[1], rows[2]) } @Test fun pinnedRows_rowGetsPinnedAndUnPinned_containsTheSameInstance() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) val rows = arrayListOf( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() rows[0].isPinned.value = true runCurrent() assertThat(pinnedHeadsUpRows).containsExactly(rows[0]) rows[0].isPinned.value = false runCurrent() assertThat(pinnedHeadsUpRows).isEmpty() rows[0].isPinned.value = true runCurrent() assertThat(pinnedHeadsUpRows).containsExactly(rows[0]) } private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { this.isPinned.value = isPinned } } packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +8 −0 Original line number Diff line number Diff line Loading @@ -193,6 +193,7 @@ import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefac import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; Loading Loading @@ -4381,6 +4382,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onHeadsUpPinned(NotificationEntry entry) { if (NotificationsHeadsUpRefactor.isEnabled()) { return; } if (!isKeyguardShowing()) { mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true); } Loading @@ -4388,6 +4393,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onHeadsUpUnPinned(NotificationEntry entry) { if (NotificationsHeadsUpRefactor.isEnabled()) { return; } // When we're unpinning the notification via active edge they remain heads-upped, // we need to make sure that an animation happens in this case, otherwise the Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt +3 −6 Original line number Diff line number Diff line Loading @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.data import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepositoryImpl import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import dagger.Binds import dagger.Module Loading @@ -27,8 +27,5 @@ import dagger.Module ] ) interface NotificationDataLayerModule { @Binds fun bindHeadsUpNotificationRepository( impl: HeadsUpNotificationRepositoryImpl ): HeadsUpNotificationRepository @Binds fun bindHeadsUpNotificationRepository(impl: HeadsUpManagerPhone): HeadsUpRepository } packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationRepository.kt→packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt +41 −0 Original line number Diff line number Diff line Loading @@ -16,44 +16,26 @@ package com.android.systemui.statusbar.notification.data.repository import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow class HeadsUpNotificationRepositoryImpl @Inject constructor( headsUpManager: HeadsUpManager, ) : HeadsUpNotificationRepository { override val hasPinnedHeadsUp: Flow<Boolean> = conflatedCallbackFlow { val listener = object : OnHeadsUpChangedListener { override fun onHeadsUpPinnedModeChanged(inPinnedMode: Boolean) { trySend(headsUpManager.hasPinnedHeadsUp()) } override fun onHeadsUpPinned(entry: NotificationEntry?) { trySend(headsUpManager.hasPinnedHeadsUp()) } /** * A repository of currently displayed heads up notifications. * * This repository serves as a boundary between the * [com.android.systemui.statusbar.policy.HeadsUpManager] and the modern notifications presentation * codebase. */ interface HeadsUpRepository { override fun onHeadsUpUnPinned(entry: NotificationEntry?) { trySend(headsUpManager.hasPinnedHeadsUp()) } /** * True if we are exiting the headsUp pinned mode, and some notifications might still be * animating out. This is used to keep the touchable regions in a reasonable state. */ val headsUpAnimatingAway: Flow<Boolean> override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { trySend(headsUpManager.hasPinnedHeadsUp()) } } trySend(headsUpManager.hasPinnedHeadsUp()) headsUpManager.addListener(listener) awaitClose { headsUpManager.removeListener(listener) } } } /** The heads up row that should be displayed on top. */ val topHeadsUpRow: Flow<HeadsUpRowRepository?> interface HeadsUpNotificationRepository { val hasPinnedHeadsUp: Flow<Boolean> /** Set of currently active top-level heads up rows to be displayed. */ val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>> } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +19 −2 Original line number Diff line number Diff line Loading @@ -51,6 +51,8 @@ import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.phone.CentralSurfaces Loading Loading @@ -175,10 +177,12 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlow.value = ObservableTransitionState.Idle(Scenes.Gone) assertThat(isVisible).isFalse() kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = true kosmos.headsUpNotificationRepository.activeHeadsUpRows.value = buildNotificationRows(isPinned = true) assertThat(isVisible).isTrue() kosmos.headsUpNotificationRepository.hasPinnedHeadsUp.value = false kosmos.headsUpNotificationRepository.activeHeadsUpRows.value = buildNotificationRows(isPinned = false) assertThat(isVisible).isFalse() } Loading Loading @@ -1070,4 +1074,17 @@ class SceneContainerStartableTest : SysuiTestCase() { return transitionStateFlow } private fun buildNotificationRows(isPinned: Boolean = false): Set<HeadsUpRowRepository> = setOf( fakeHeadsUpRowRepository(key = "0", isPinned = isPinned), fakeHeadsUpRowRepository(key = "1", isPinned = isPinned), fakeHeadsUpRowRepository(key = "2", isPinned = isPinned), fakeHeadsUpRowRepository(key = "3", isPinned = isPinned), ) private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean) = FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { this.isPinned.value = isPinned } }
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt 0 → 100644 +269 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ @file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.statusbar.notification.domain.interactor import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) class HeadsUpNotificationInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val repository = kosmos.headsUpNotificationRepository private val underTest = kosmos.headsUpNotificationInteractor @Test fun hasPinnedRows_emptyList_false() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) assertThat(hasPinnedRows).isFalse() } @Test fun hasPinnedRows_noPinnedRows_false() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // WHEN no pinned rows are set repository.setNotifications( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) runCurrent() // THEN hasPinnedRows is false assertThat(hasPinnedRows).isFalse() } @Test fun hasPinnedRows_hasPinnedRows_true() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // WHEN a pinned rows is set repository.setNotifications( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) runCurrent() // THEN hasPinnedRows is true assertThat(hasPinnedRows).isTrue() } @Test fun hasPinnedRows_rowGetsPinned_true() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // GIVEN no rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // WHEN a row gets pinned rows[0].isPinned.value = true runCurrent() // THEN hasPinnedRows updates to true assertThat(hasPinnedRows).isTrue() } @Test fun hasPinnedRows_rowGetsUnPinned_false() = testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // GIVEN one row is pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // THEN that row gets unpinned rows[0].isPinned.value = false runCurrent() // THEN hasPinnedRows updates to false assertThat(hasPinnedRows).isFalse() } @Test fun pinnedRows_noRows_isEmpty() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) assertThat(pinnedHeadsUpRows).isEmpty() } @Test fun pinnedRows_noPinnedRows_isEmpty() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // WHEN no rows are pinned repository.setNotifications( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) runCurrent() // THEN all rows are filtered assertThat(pinnedHeadsUpRows).isEmpty() } @Test fun pinnedRows_hasPinnedRows_containsPinnedRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // WHEN some rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // THEN the unpinned rows are filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1]) } @Test fun pinnedRows_rowGetsPinned_containsPinnedRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // GIVEN some rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() // WHEN all rows gets pinned rows[2].isPinned.value = true runCurrent() // THEN no rows are filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1], rows[2]) } @Test fun pinnedRows_allRowsPinned_containsAllRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // WHEN all rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2", isPinned = true), ) repository.setNotifications(rows) runCurrent() // THEN no rows are filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1], rows[2]) } @Test fun pinnedRows_rowGetsUnPinned_containsPinnedRows() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // GIVEN all rows are pinned val rows = arrayListOf( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2", isPinned = true), ) repository.setNotifications(rows) runCurrent() // WHEN a row gets unpinned rows[0].isPinned.value = false runCurrent() // THEN the unpinned row is filtered assertThat(pinnedHeadsUpRows).containsExactly(rows[1], rows[2]) } @Test fun pinnedRows_rowGetsPinnedAndUnPinned_containsTheSameInstance() = testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) val rows = arrayListOf( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) repository.setNotifications(rows) runCurrent() rows[0].isPinned.value = true runCurrent() assertThat(pinnedHeadsUpRows).containsExactly(rows[0]) rows[0].isPinned.value = false runCurrent() assertThat(pinnedHeadsUpRows).isEmpty() rows[0].isPinned.value = true runCurrent() assertThat(pinnedHeadsUpRows).containsExactly(rows[0]) } private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { this.isPinned.value = isPinned } }
packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +8 −0 Original line number Diff line number Diff line Loading @@ -193,6 +193,7 @@ import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefac import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; Loading Loading @@ -4381,6 +4382,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onHeadsUpPinned(NotificationEntry entry) { if (NotificationsHeadsUpRefactor.isEnabled()) { return; } if (!isKeyguardShowing()) { mNotificationStackScrollLayoutController.generateHeadsUpAnimation(entry, true); } Loading @@ -4388,6 +4393,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void onHeadsUpUnPinned(NotificationEntry entry) { if (NotificationsHeadsUpRefactor.isEnabled()) { return; } // When we're unpinning the notification via active edge they remain heads-upped, // we need to make sure that an animation happens in this case, otherwise the Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt +3 −6 Original line number Diff line number Diff line Loading @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.data import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationRepositoryImpl import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository import com.android.systemui.statusbar.phone.HeadsUpManagerPhone import dagger.Binds import dagger.Module Loading @@ -27,8 +27,5 @@ import dagger.Module ] ) interface NotificationDataLayerModule { @Binds fun bindHeadsUpNotificationRepository( impl: HeadsUpNotificationRepositoryImpl ): HeadsUpNotificationRepository @Binds fun bindHeadsUpNotificationRepository(impl: HeadsUpManagerPhone): HeadsUpRepository }
packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationRepository.kt→packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt +41 −0 Original line number Diff line number Diff line Loading @@ -16,44 +16,26 @@ package com.android.systemui.statusbar.notification.data.repository import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow class HeadsUpNotificationRepositoryImpl @Inject constructor( headsUpManager: HeadsUpManager, ) : HeadsUpNotificationRepository { override val hasPinnedHeadsUp: Flow<Boolean> = conflatedCallbackFlow { val listener = object : OnHeadsUpChangedListener { override fun onHeadsUpPinnedModeChanged(inPinnedMode: Boolean) { trySend(headsUpManager.hasPinnedHeadsUp()) } override fun onHeadsUpPinned(entry: NotificationEntry?) { trySend(headsUpManager.hasPinnedHeadsUp()) } /** * A repository of currently displayed heads up notifications. * * This repository serves as a boundary between the * [com.android.systemui.statusbar.policy.HeadsUpManager] and the modern notifications presentation * codebase. */ interface HeadsUpRepository { override fun onHeadsUpUnPinned(entry: NotificationEntry?) { trySend(headsUpManager.hasPinnedHeadsUp()) } /** * True if we are exiting the headsUp pinned mode, and some notifications might still be * animating out. This is used to keep the touchable regions in a reasonable state. */ val headsUpAnimatingAway: Flow<Boolean> override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { trySend(headsUpManager.hasPinnedHeadsUp()) } } trySend(headsUpManager.hasPinnedHeadsUp()) headsUpManager.addListener(listener) awaitClose { headsUpManager.removeListener(listener) } } } /** The heads up row that should be displayed on top. */ val topHeadsUpRow: Flow<HeadsUpRowRepository?> interface HeadsUpNotificationRepository { val hasPinnedHeadsUp: Flow<Boolean> /** Set of currently active top-level heads up rows to be displayed. */ val activeHeadsUpRows: Flow<Set<HeadsUpRowRepository>> }