Merge "Hide notifications on lockscreen when the wallpaper dream is active" into udc-qpr-dev
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.collection.coordinator
|
||||
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.StatusBarState
|
||||
import com.android.systemui.statusbar.SysuiStatusBarStateController
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Filter out notifications on the lockscreen if the lockscreen hosted dream is active. If the user
|
||||
* stops dreaming, pulls the shade down or unlocks the device, then the notifications are unhidden.
|
||||
*/
|
||||
@CoordinatorScope
|
||||
class DreamCoordinator
|
||||
@Inject
|
||||
constructor(
|
||||
private val statusBarStateController: SysuiStatusBarStateController,
|
||||
@Application private val scope: CoroutineScope,
|
||||
private val keyguardRepository: KeyguardRepository,
|
||||
) : Coordinator {
|
||||
private var isOnKeyguard = false
|
||||
private var isLockscreenHostedDream = false
|
||||
|
||||
override fun attach(pipeline: NotifPipeline) {
|
||||
pipeline.addPreGroupFilter(filter)
|
||||
statusBarStateController.addCallback(statusBarStateListener)
|
||||
scope.launch { attachFilterOnDreamingStateChange() }
|
||||
recordStatusBarState(statusBarStateController.state)
|
||||
}
|
||||
|
||||
private val filter =
|
||||
object : NotifFilter("LockscreenHostedDreamFilter") {
|
||||
var isFiltering = false
|
||||
override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean {
|
||||
return isFiltering
|
||||
}
|
||||
inline fun update(msg: () -> String) {
|
||||
val wasFiltering = isFiltering
|
||||
isFiltering = isLockscreenHostedDream && isOnKeyguard
|
||||
if (wasFiltering != isFiltering) {
|
||||
invalidateList(msg())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val statusBarStateListener =
|
||||
object : StatusBarStateController.StateListener {
|
||||
override fun onStateChanged(newState: Int) {
|
||||
recordStatusBarState(newState)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun attachFilterOnDreamingStateChange() {
|
||||
keyguardRepository.isActiveDreamLockscreenHosted.collect { isDreaming ->
|
||||
recordDreamingState(isDreaming)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordStatusBarState(newState: Int) {
|
||||
isOnKeyguard = newState == StatusBarState.KEYGUARD
|
||||
filter.update { "recordStatusBarState: " + StatusBarState.toString(newState) }
|
||||
}
|
||||
|
||||
private fun recordDreamingState(isDreaming: Boolean) {
|
||||
isLockscreenHostedDream = isDreaming
|
||||
filter.update { "recordLockscreenHostedDreamState: $isDreaming" }
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.android.systemui.statusbar.notification.collection.coordinator
|
||||
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.PipelineDumpable
|
||||
import com.android.systemui.statusbar.notification.collection.PipelineDumper
|
||||
@@ -31,32 +33,34 @@ interface NotifCoordinators : Coordinator, PipelineDumpable
|
||||
|
||||
@CoordinatorScope
|
||||
class NotifCoordinatorsImpl @Inject constructor(
|
||||
sectionStyleProvider: SectionStyleProvider,
|
||||
dataStoreCoordinator: DataStoreCoordinator,
|
||||
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
|
||||
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
|
||||
keyguardCoordinator: KeyguardCoordinator,
|
||||
rankingCoordinator: RankingCoordinator,
|
||||
appOpsCoordinator: AppOpsCoordinator,
|
||||
deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
|
||||
bubbleCoordinator: BubbleCoordinator,
|
||||
headsUpCoordinator: HeadsUpCoordinator,
|
||||
gutsCoordinator: GutsCoordinator,
|
||||
conversationCoordinator: ConversationCoordinator,
|
||||
debugModeCoordinator: DebugModeCoordinator,
|
||||
groupCountCoordinator: GroupCountCoordinator,
|
||||
groupWhenCoordinator: GroupWhenCoordinator,
|
||||
mediaCoordinator: MediaCoordinator,
|
||||
preparationCoordinator: PreparationCoordinator,
|
||||
remoteInputCoordinator: RemoteInputCoordinator,
|
||||
rowAppearanceCoordinator: RowAppearanceCoordinator,
|
||||
stackCoordinator: StackCoordinator,
|
||||
shadeEventCoordinator: ShadeEventCoordinator,
|
||||
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
|
||||
viewConfigCoordinator: ViewConfigCoordinator,
|
||||
visualStabilityCoordinator: VisualStabilityCoordinator,
|
||||
sensitiveContentCoordinator: SensitiveContentCoordinator,
|
||||
dismissibilityCoordinator: DismissibilityCoordinator,
|
||||
sectionStyleProvider: SectionStyleProvider,
|
||||
featureFlags: FeatureFlags,
|
||||
dataStoreCoordinator: DataStoreCoordinator,
|
||||
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
|
||||
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
|
||||
keyguardCoordinator: KeyguardCoordinator,
|
||||
rankingCoordinator: RankingCoordinator,
|
||||
appOpsCoordinator: AppOpsCoordinator,
|
||||
deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
|
||||
bubbleCoordinator: BubbleCoordinator,
|
||||
headsUpCoordinator: HeadsUpCoordinator,
|
||||
gutsCoordinator: GutsCoordinator,
|
||||
conversationCoordinator: ConversationCoordinator,
|
||||
debugModeCoordinator: DebugModeCoordinator,
|
||||
groupCountCoordinator: GroupCountCoordinator,
|
||||
groupWhenCoordinator: GroupWhenCoordinator,
|
||||
mediaCoordinator: MediaCoordinator,
|
||||
preparationCoordinator: PreparationCoordinator,
|
||||
remoteInputCoordinator: RemoteInputCoordinator,
|
||||
rowAppearanceCoordinator: RowAppearanceCoordinator,
|
||||
stackCoordinator: StackCoordinator,
|
||||
shadeEventCoordinator: ShadeEventCoordinator,
|
||||
smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
|
||||
viewConfigCoordinator: ViewConfigCoordinator,
|
||||
visualStabilityCoordinator: VisualStabilityCoordinator,
|
||||
sensitiveContentCoordinator: SensitiveContentCoordinator,
|
||||
dismissibilityCoordinator: DismissibilityCoordinator,
|
||||
dreamCoordinator: DreamCoordinator,
|
||||
) : NotifCoordinators {
|
||||
|
||||
private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
|
||||
@@ -96,6 +100,10 @@ class NotifCoordinatorsImpl @Inject constructor(
|
||||
mCoordinators.add(remoteInputCoordinator)
|
||||
mCoordinators.add(dismissibilityCoordinator)
|
||||
|
||||
if (featureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
|
||||
mCoordinators.add(dreamCoordinator)
|
||||
}
|
||||
|
||||
// Manually add Ordered Sections
|
||||
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
|
||||
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.collection.coordinator
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.StatusBarState
|
||||
import com.android.systemui.statusbar.SysuiStatusBarStateController
|
||||
import com.android.systemui.statusbar.notification.collection.NotifPipeline
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
|
||||
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class DreamCoordinatorTest : SysuiTestCase() {
|
||||
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
|
||||
@Mock private lateinit var notifPipeline: NotifPipeline
|
||||
@Mock private lateinit var filterListener: Pluggable.PluggableListener<NotifFilter>
|
||||
|
||||
private val keyguardRepository = FakeKeyguardRepository()
|
||||
private var fakeEntry: NotificationEntry = NotificationEntryBuilder().build()
|
||||
val testDispatcher = UnconfinedTestDispatcher()
|
||||
val testScope = TestScope(testDispatcher)
|
||||
|
||||
private lateinit var filter: NotifFilter
|
||||
private lateinit var statusBarListener: StatusBarStateController.StateListener
|
||||
private lateinit var dreamCoordinator: DreamCoordinator
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
|
||||
|
||||
// Build the coordinator
|
||||
dreamCoordinator =
|
||||
DreamCoordinator(
|
||||
statusBarStateController,
|
||||
testScope.backgroundScope,
|
||||
keyguardRepository
|
||||
)
|
||||
|
||||
// Attach the pipeline and capture the listeners/filters that it registers
|
||||
dreamCoordinator.attach(notifPipeline)
|
||||
|
||||
filter = withArgCaptor { verify(notifPipeline).addPreGroupFilter(capture()) }
|
||||
filter.setInvalidationListener(filterListener)
|
||||
|
||||
statusBarListener = withArgCaptor {
|
||||
verify(statusBarStateController).addCallback(capture())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hideNotifications_whenDreamingAndOnKeyguard() =
|
||||
testScope.runTest {
|
||||
// GIVEN we are on keyguard and not dreaming
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
keyguardRepository.setIsActiveDreamLockscreenHosted(false)
|
||||
runCurrent()
|
||||
|
||||
// THEN notifications are not filtered out
|
||||
verifyPipelinesNotInvalidated()
|
||||
assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
|
||||
|
||||
// WHEN dreaming starts and the active dream is hosted in lockscreen
|
||||
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
|
||||
runCurrent()
|
||||
|
||||
// THEN pipeline is notified and notifications should all be filtered out
|
||||
verifyPipelinesInvalidated()
|
||||
assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showNotifications_whenDreamingAndNotOnKeyguard() =
|
||||
testScope.runTest {
|
||||
// GIVEN we are on the keyguard and active dream is hosted in lockscreen
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
|
||||
runCurrent()
|
||||
|
||||
// THEN pipeline is notified and notifications are all filtered out
|
||||
verifyPipelinesInvalidated()
|
||||
clearPipelineInvocations()
|
||||
assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
|
||||
|
||||
// WHEN we are no longer on the keyguard
|
||||
statusBarListener.onStateChanged(StatusBarState.SHADE)
|
||||
|
||||
// THEN pipeline is notified and notifications are not filtered out
|
||||
verifyPipelinesInvalidated()
|
||||
assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showNotifications_whenOnKeyguardAndNotDreaming() =
|
||||
testScope.runTest {
|
||||
// GIVEN we are on the keyguard and active dream is hosted in lockscreen
|
||||
keyguardRepository.setKeyguardShowing(true)
|
||||
keyguardRepository.setIsActiveDreamLockscreenHosted(true)
|
||||
runCurrent()
|
||||
|
||||
// THEN pipeline is notified and notifications are all filtered out
|
||||
verifyPipelinesInvalidated()
|
||||
clearPipelineInvocations()
|
||||
assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isTrue()
|
||||
|
||||
// WHEN the lockscreen hosted dream stops
|
||||
keyguardRepository.setIsActiveDreamLockscreenHosted(false)
|
||||
runCurrent()
|
||||
|
||||
// THEN pipeline is notified and notifications are not filtered out
|
||||
verifyPipelinesInvalidated()
|
||||
assertThat(filter.shouldFilterOut(fakeEntry, 0L)).isFalse()
|
||||
}
|
||||
|
||||
private fun verifyPipelinesInvalidated() {
|
||||
verify(filterListener).onPluggableInvalidated(eq(filter), any())
|
||||
}
|
||||
|
||||
private fun verifyPipelinesNotInvalidated() {
|
||||
verify(filterListener, never()).onPluggableInvalidated(eq(filter), any())
|
||||
}
|
||||
|
||||
private fun clearPipelineInvocations() {
|
||||
clearInvocations(filterListener)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user