Merge "Hide notifications on lockscreen when the wallpaper dream is active" into udc-qpr-dev

This commit is contained in:
Coco Duan
2023-07-14 18:11:08 +00:00
committed by Android (Google) Code Review
3 changed files with 291 additions and 26 deletions

View File

@@ -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" }
}
}

View File

@@ -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

View File

@@ -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)
}
}