1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.systemui.statusbar.notification.collection.coordinator 17 18 import android.app.Notification 19 import android.app.Notification.MediaStyle 20 import android.media.session.MediaSession 21 import android.platform.test.flag.junit.FlagsParameterization 22 import android.provider.Settings 23 import androidx.test.filters.SmallTest 24 import com.android.systemui.SysuiTestCase 25 import com.android.systemui.dump.dumpManager 26 import com.android.systemui.flags.andSceneContainer 27 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository 28 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository 29 import com.android.systemui.keyguard.data.repository.keyguardRepository 30 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor 31 import com.android.systemui.keyguard.shared.model.KeyguardState 32 import com.android.systemui.keyguard.shared.model.TransitionStep 33 import com.android.systemui.kosmos.testDispatcher 34 import com.android.systemui.kosmos.testScope 35 import com.android.systemui.log.logcatLogBuffer 36 import com.android.systemui.plugins.statusbar.statusBarStateController 37 import com.android.systemui.scene.data.repository.Idle 38 import com.android.systemui.scene.data.repository.setTransition 39 import com.android.systemui.scene.domain.interactor.sceneInteractor 40 import com.android.systemui.scene.shared.model.Scenes 41 import com.android.systemui.statusbar.SbnBuilder 42 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder 43 import com.android.systemui.statusbar.notification.collection.NotifPipeline 44 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder 45 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter 46 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable 47 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener 48 import com.android.systemui.statusbar.notification.collection.notifcollection.UpdateSource 49 import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor 50 import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting 51 import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor 52 import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener 53 import com.android.systemui.statusbar.notification.headsup.mockHeadsUpManager 54 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow 55 import com.android.systemui.testKosmos 56 import com.android.systemui.util.settings.FakeSettings 57 import com.android.systemui.util.settings.fakeSettings 58 import com.google.common.truth.Truth.assertThat 59 import kotlin.time.Duration.Companion.seconds 60 import kotlinx.coroutines.CoroutineScope 61 import kotlinx.coroutines.test.TestCoroutineScheduler 62 import kotlinx.coroutines.test.TestScope 63 import kotlinx.coroutines.test.UnconfinedTestDispatcher 64 import kotlinx.coroutines.test.runTest 65 import org.junit.Test 66 import org.junit.runner.RunWith 67 import org.mockito.ArgumentMatchers.same 68 import org.mockito.kotlin.any 69 import org.mockito.kotlin.argumentCaptor 70 import org.mockito.kotlin.mock 71 import org.mockito.kotlin.never 72 import org.mockito.kotlin.verify 73 import org.mockito.kotlin.whenever 74 import platform.test.runner.parameterized.ParameterizedAndroidJunit4 75 import platform.test.runner.parameterized.Parameters 76 77 @SmallTest 78 @RunWith(ParameterizedAndroidJunit4::class) 79 class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() { 80 81 private val kosmos = <lambda>null82 testKosmos().apply { 83 testDispatcher = UnconfinedTestDispatcher() 84 statusBarStateController = mock() 85 fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1) 86 } 87 88 private val keyguardRepository 89 get() = kosmos.fakeKeyguardRepository 90 91 private val keyguardTransitionRepository 92 get() = kosmos.fakeKeyguardTransitionRepository 93 94 private val statusBarStateController 95 get() = kosmos.statusBarStateController 96 97 private val notifPipeline: NotifPipeline = mock() 98 99 init { 100 mSetFlagsRule.setFlagsParameterization(flags) 101 } 102 103 @Test unseenFilterSuppressesSeenNotifWhileKeyguardShowingnull104 fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() { 105 // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present 106 keyguardRepository.setKeyguardShowing(false) 107 whenever(statusBarStateController.isExpanded).thenReturn(true) 108 runKeyguardCoordinatorTest { 109 val fakeEntry = NotificationEntryBuilder().build() 110 collectionListener.onEntryAdded(fakeEntry) 111 112 // WHEN: The keyguard is now showing 113 keyguardRepository.setKeyguardShowing(true) 114 testScheduler.runCurrent() 115 116 // THEN: The notification is recognized as "seen" and is filtered out. 117 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() 118 119 // WHEN: The keyguard goes away 120 keyguardRepository.setKeyguardShowing(false) 121 testScheduler.runCurrent() 122 123 // THEN: The notification is shown regardless 124 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 125 } 126 } 127 128 @Test unseenFilterStopsMarkingSeenNotifWhenTransitionToAodnull129 fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() { 130 // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present 131 keyguardRepository.setKeyguardShowing(false) 132 whenever(statusBarStateController.isExpanded).thenReturn(false) 133 runKeyguardCoordinatorTest { 134 val fakeEntry = NotificationEntryBuilder().build() 135 collectionListener.onEntryAdded(fakeEntry) 136 137 // WHEN: The device transitions to AOD 138 keyguardTransitionRepository.sendTransitionSteps( 139 from = KeyguardState.GONE, 140 to = KeyguardState.AOD, 141 this.testScheduler, 142 ) 143 testScheduler.runCurrent() 144 145 // THEN: We are no longer listening for shade expansions 146 verify(statusBarStateController, never()).addCallback(any()) 147 } 148 } 149 150 @Test unseenFilter_headsUpMarkedAsSeennull151 fun unseenFilter_headsUpMarkedAsSeen() { 152 // GIVEN: Keyguard is not showing, shade is not expanded 153 keyguardRepository.setKeyguardShowing(false) 154 whenever(statusBarStateController.isExpanded).thenReturn(false) 155 runKeyguardCoordinatorTest { 156 kosmos.setTransition( 157 sceneTransition = Idle(Scenes.Gone), 158 stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE), 159 ) 160 161 // WHEN: A notification is posted 162 val fakeEntry = NotificationEntryBuilder().build() 163 collectionListener.onEntryAdded(fakeEntry) 164 165 // WHEN: That notification is heads up 166 onHeadsUpChangedListener.onHeadsUpStateChanged(fakeEntry, /* isHeadsUp= */ true) 167 testScheduler.runCurrent() 168 169 // WHEN: The keyguard is now showing 170 keyguardRepository.setKeyguardShowing(true) 171 kosmos.setTransition( 172 sceneTransition = Idle(Scenes.Lockscreen), 173 stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD), 174 ) 175 176 // THEN: The notification is recognized as "seen" and is filtered out. 177 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() 178 179 // WHEN: The keyguard goes away 180 keyguardRepository.setKeyguardShowing(false) 181 kosmos.setTransition( 182 sceneTransition = Idle(Scenes.Gone), 183 stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE), 184 ) 185 186 // THEN: The notification is shown regardless 187 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 188 } 189 } 190 191 @Test unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowingnull192 fun unseenFilterDoesNotSuppressSeenOngoingNotifWhileKeyguardShowing() { 193 // GIVEN: Keyguard is not showing, shade is expanded, and an ongoing notification is present 194 keyguardRepository.setKeyguardShowing(false) 195 whenever(statusBarStateController.isExpanded).thenReturn(true) 196 runKeyguardCoordinatorTest { 197 val fakeEntry = 198 NotificationEntryBuilder() 199 .setNotification(Notification.Builder(mContext, "id").setOngoing(true).build()) 200 .build() 201 collectionListener.onEntryAdded(fakeEntry) 202 203 // WHEN: The keyguard is now showing 204 keyguardRepository.setKeyguardShowing(true) 205 testScheduler.runCurrent() 206 207 // THEN: The notification is recognized as "ongoing" and is not filtered out. 208 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 209 } 210 } 211 212 @Test unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowingnull213 fun unseenFilterDoesNotSuppressSeenMediaNotifWhileKeyguardShowing() { 214 // GIVEN: Keyguard is not showing, shade is expanded, and a media notification is present 215 keyguardRepository.setKeyguardShowing(false) 216 whenever(statusBarStateController.isExpanded).thenReturn(true) 217 runKeyguardCoordinatorTest { 218 val fakeEntry = 219 NotificationEntryBuilder().build().apply { 220 row = 221 mock<ExpandableNotificationRow>().apply { 222 whenever(isMediaRow).thenReturn(true) 223 } 224 sbn = 225 SbnBuilder() 226 .setNotification( 227 Notification.Builder(context, "channel") 228 .setStyle( 229 MediaStyle() 230 .setMediaSession( 231 MediaSession(context, "tag").sessionToken 232 ) 233 ) 234 .build() 235 ) 236 .build() 237 } 238 collectionListener.onEntryAdded(fakeEntry) 239 240 // WHEN: The keyguard is now showing 241 keyguardRepository.setKeyguardShowing(true) 242 testScheduler.runCurrent() 243 244 // THEN: The notification is recognized as "media" and is not filtered out. 245 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 246 } 247 } 248 249 @Test unseenFilterUpdatesSeenProviderWhenSuppressingnull250 fun unseenFilterUpdatesSeenProviderWhenSuppressing() { 251 // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present 252 keyguardRepository.setKeyguardShowing(false) 253 whenever(statusBarStateController.isExpanded).thenReturn(true) 254 runKeyguardCoordinatorTest { 255 val fakeEntry = NotificationEntryBuilder().build() 256 collectionListener.onEntryAdded(fakeEntry) 257 258 // WHEN: The keyguard is now showing 259 keyguardRepository.setKeyguardShowing(true) 260 testScheduler.runCurrent() 261 262 // THEN: The notification is recognized as "seen" and is filtered out. 263 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() 264 265 // WHEN: The filter is cleaned up 266 unseenFilter.onCleanup() 267 268 // THEN: The SeenNotificationProvider has been updated to reflect the suppression 269 assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue() 270 } 271 } 272 273 @Test unseenFilterInvalidatesWhenSettingChangesnull274 fun unseenFilterInvalidatesWhenSettingChanges() { 275 // GIVEN: Keyguard is not showing, and shade is expanded 276 keyguardRepository.setKeyguardShowing(false) 277 whenever(statusBarStateController.isExpanded).thenReturn(true) 278 runKeyguardCoordinatorTest { 279 // GIVEN: A notification is present 280 val fakeEntry = NotificationEntryBuilder().build() 281 collectionListener.onEntryAdded(fakeEntry) 282 283 // GIVEN: The setting for filtering unseen notifications is disabled 284 kosmos.lockScreenShowOnlyUnseenNotificationsSetting = false 285 286 // GIVEN: The pipeline has registered the unseen filter for invalidation 287 val invalidationListener: Pluggable.PluggableListener<NotifFilter> = mock() 288 unseenFilter.setInvalidationListener(invalidationListener) 289 290 // WHEN: The keyguard is now showing 291 keyguardRepository.setKeyguardShowing(true) 292 testScheduler.runCurrent() 293 294 // THEN: The notification is not filtered out 295 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 296 297 // WHEN: The secure setting is changed 298 kosmos.lockScreenShowOnlyUnseenNotificationsSetting = true 299 300 // THEN: The pipeline is invalidated 301 verify(invalidationListener).onPluggableInvalidated(same(unseenFilter), any()) 302 303 // THEN: The notification is recognized as "seen" and is filtered out. 304 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() 305 } 306 } 307 308 @Test unseenFilterAllowsNewNotifnull309 fun unseenFilterAllowsNewNotif() { 310 // GIVEN: Keyguard is showing, no notifications present 311 keyguardRepository.setKeyguardShowing(true) 312 runKeyguardCoordinatorTest { 313 // WHEN: A new notification is posted 314 val fakeEntry = NotificationEntryBuilder().build() 315 collectionListener.onEntryAdded(fakeEntry) 316 317 // THEN: The notification is recognized as "unseen" and is not filtered out. 318 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 319 } 320 } 321 322 @Test unseenFilterSeenGroupSummaryWithUnseenChildnull323 fun unseenFilterSeenGroupSummaryWithUnseenChild() { 324 // GIVEN: Keyguard is not showing, shade is expanded, and a notification is present 325 keyguardRepository.setKeyguardShowing(false) 326 whenever(statusBarStateController.isExpanded).thenReturn(true) 327 runKeyguardCoordinatorTest { 328 // WHEN: A new notification is posted 329 val fakeSummary = NotificationEntryBuilder().build() 330 val fakeChild = 331 NotificationEntryBuilder() 332 .setGroup(context, "group") 333 .setGroupSummary(context, false) 334 .build() 335 GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build() 336 337 collectionListener.onEntryAdded(fakeSummary) 338 collectionListener.onEntryAdded(fakeChild) 339 340 // WHEN: Keyguard is now showing, both notifications are marked as seen 341 keyguardRepository.setKeyguardShowing(true) 342 testScheduler.runCurrent() 343 344 // WHEN: The child notification is now unseen 345 collectionListener.onEntryUpdated(fakeChild) 346 347 // THEN: The summary is not filtered out, because the child is unseen 348 assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse() 349 } 350 } 351 352 @Test unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAwaynull353 fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() { 354 // GIVEN: Keyguard is showing, not dozing, unseen notification is present 355 keyguardRepository.setKeyguardShowing(true) 356 keyguardRepository.setIsDozing(false) 357 runKeyguardCoordinatorTest { 358 val fakeEntry = NotificationEntryBuilder().build() 359 collectionListener.onEntryAdded(fakeEntry) 360 keyguardTransitionRepository.sendTransitionSteps( 361 from = KeyguardState.AOD, 362 to = KeyguardState.LOCKSCREEN, 363 this.testScheduler, 364 ) 365 testScheduler.runCurrent() 366 367 // WHEN: five seconds have passed 368 testScheduler.advanceTimeBy(5.seconds) 369 testScheduler.runCurrent() 370 371 // WHEN: Keyguard is no longer showing 372 keyguardRepository.setKeyguardShowing(false) 373 kosmos.setTransition( 374 sceneTransition = Idle(Scenes.Gone), 375 stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE), 376 ) 377 378 // WHEN: Keyguard is shown again 379 keyguardRepository.setKeyguardShowing(true) 380 kosmos.setTransition( 381 sceneTransition = Idle(Scenes.Lockscreen), 382 stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD), 383 ) 384 385 // THEN: The notification is now recognized as "seen" and is filtered out. 386 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() 387 } 388 } 389 390 @Test unseenNotificationIsNotMarkedAsSeenIfShadeNotExpandednull391 fun unseenNotificationIsNotMarkedAsSeenIfShadeNotExpanded() { 392 // GIVEN: Keyguard is showing, unseen notification is present 393 keyguardRepository.setKeyguardShowing(true) 394 runKeyguardCoordinatorTest { 395 keyguardTransitionRepository.sendTransitionSteps( 396 from = KeyguardState.GONE, 397 to = KeyguardState.LOCKSCREEN, 398 this.testScheduler, 399 ) 400 val fakeEntry = NotificationEntryBuilder().build() 401 collectionListener.onEntryAdded(fakeEntry) 402 403 // WHEN: Keyguard is no longer showing 404 keyguardRepository.setKeyguardShowing(false) 405 keyguardTransitionRepository.sendTransitionSteps( 406 from = KeyguardState.LOCKSCREEN, 407 to = KeyguardState.GONE, 408 this.testScheduler, 409 ) 410 411 // WHEN: Keyguard is shown again 412 keyguardRepository.setKeyguardShowing(true) 413 testScheduler.runCurrent() 414 415 // THEN: The notification is not recognized as "seen" and is not filtered out. 416 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 417 } 418 } 419 420 @Test unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnoughnull421 fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() { 422 // GIVEN: Keyguard is showing, not dozing, unseen notification is present 423 keyguardRepository.setKeyguardShowing(true) 424 keyguardRepository.setIsDozing(false) 425 runKeyguardCoordinatorTest { 426 kosmos.setTransition( 427 sceneTransition = Idle(Scenes.Lockscreen), 428 stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN), 429 ) 430 val firstEntry = NotificationEntryBuilder().setId(1).build() 431 collectionListener.onEntryAdded(firstEntry) 432 testScheduler.runCurrent() 433 434 // WHEN: one second has passed 435 testScheduler.advanceTimeBy(1.seconds) 436 testScheduler.runCurrent() 437 438 // WHEN: another unseen notification is posted 439 val secondEntry = NotificationEntryBuilder().setId(2).build() 440 collectionListener.onEntryAdded(secondEntry) 441 testScheduler.runCurrent() 442 443 // WHEN: four more seconds have passed 444 testScheduler.advanceTimeBy(4.seconds) 445 testScheduler.runCurrent() 446 447 // WHEN: the keyguard is no longer showing 448 keyguardRepository.setKeyguardShowing(false) 449 kosmos.setTransition( 450 sceneTransition = Idle(Scenes.Gone), 451 stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE), 452 ) 453 454 // WHEN: Keyguard is shown again 455 keyguardRepository.setKeyguardShowing(true) 456 kosmos.setTransition( 457 sceneTransition = Idle(Scenes.Lockscreen), 458 stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN), 459 ) 460 461 // THEN: The first notification is considered seen and is filtered out. 462 assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue() 463 464 // THEN: The second notification is still considered unseen and is not filtered out 465 assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse() 466 } 467 } 468 469 @Test unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThresholdnull470 fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() { 471 // GIVEN: Keyguard is showing, not dozing 472 keyguardRepository.setKeyguardShowing(true) 473 keyguardRepository.setIsDozing(false) 474 runKeyguardCoordinatorTest { 475 keyguardTransitionRepository.sendTransitionSteps( 476 from = KeyguardState.GONE, 477 to = KeyguardState.LOCKSCREEN, 478 this.testScheduler, 479 ) 480 testScheduler.runCurrent() 481 482 // WHEN: a new notification is posted 483 val entry = NotificationEntryBuilder().setId(1).build() 484 collectionListener.onEntryAdded(entry) 485 testScheduler.runCurrent() 486 487 // WHEN: five more seconds have passed 488 testScheduler.advanceTimeBy(5.seconds) 489 testScheduler.runCurrent() 490 491 // WHEN: the notification is removed 492 collectionListener.onEntryRemoved(entry, 0) 493 testScheduler.runCurrent() 494 495 // WHEN: the notification is re-posted 496 collectionListener.onEntryAdded(entry) 497 testScheduler.runCurrent() 498 499 // WHEN: one more second has passed 500 testScheduler.advanceTimeBy(1.seconds) 501 testScheduler.runCurrent() 502 503 // WHEN: the keyguard is no longer showing 504 keyguardRepository.setKeyguardShowing(false) 505 keyguardTransitionRepository.sendTransitionSteps( 506 from = KeyguardState.LOCKSCREEN, 507 to = KeyguardState.GONE, 508 this.testScheduler, 509 ) 510 testScheduler.runCurrent() 511 512 // WHEN: Keyguard is shown again 513 keyguardRepository.setKeyguardShowing(true) 514 keyguardTransitionRepository.sendTransitionSteps( 515 from = KeyguardState.GONE, 516 to = KeyguardState.LOCKSCREEN, 517 this.testScheduler, 518 ) 519 testScheduler.runCurrent() 520 521 // THEN: The notification is considered unseen and is not filtered out. 522 assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() 523 } 524 } 525 526 @Test unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThresholdnull527 fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() { 528 // GIVEN: Keyguard is showing, not dozing 529 keyguardRepository.setKeyguardShowing(true) 530 keyguardRepository.setIsDozing(false) 531 runKeyguardCoordinatorTest { 532 keyguardTransitionRepository.sendTransitionSteps( 533 from = KeyguardState.GONE, 534 to = KeyguardState.LOCKSCREEN, 535 this.testScheduler, 536 ) 537 testScheduler.runCurrent() 538 539 // WHEN: a new notification is posted 540 val entry = NotificationEntryBuilder().setId(1).build() 541 collectionListener.onEntryAdded(entry) 542 testScheduler.runCurrent() 543 544 // WHEN: one second has passed 545 testScheduler.advanceTimeBy(1.seconds) 546 testScheduler.runCurrent() 547 548 // WHEN: the notification is removed 549 collectionListener.onEntryRemoved(entry, 0) 550 testScheduler.runCurrent() 551 552 // WHEN: the notification is re-posted 553 collectionListener.onEntryAdded(entry) 554 testScheduler.runCurrent() 555 556 // WHEN: one more second has passed 557 testScheduler.advanceTimeBy(1.seconds) 558 testScheduler.runCurrent() 559 560 // WHEN: the keyguard is no longer showing 561 keyguardRepository.setKeyguardShowing(false) 562 keyguardTransitionRepository.sendTransitionSteps( 563 from = KeyguardState.LOCKSCREEN, 564 to = KeyguardState.GONE, 565 this.testScheduler, 566 ) 567 testScheduler.runCurrent() 568 569 // WHEN: Keyguard is shown again 570 keyguardRepository.setKeyguardShowing(true) 571 keyguardTransitionRepository.sendTransitionSteps( 572 from = KeyguardState.GONE, 573 to = KeyguardState.LOCKSCREEN, 574 this.testScheduler, 575 ) 576 testScheduler.runCurrent() 577 578 // THEN: The notification is considered unseen and is not filtered out. 579 assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() 580 } 581 } 582 583 @Test unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThresholdnull584 fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() { 585 // GIVEN: Keyguard is showing, not dozing 586 keyguardRepository.setKeyguardShowing(true) 587 keyguardRepository.setIsDozing(false) 588 runKeyguardCoordinatorTest { 589 keyguardTransitionRepository.sendTransitionSteps( 590 from = KeyguardState.GONE, 591 to = KeyguardState.LOCKSCREEN, 592 this.testScheduler, 593 ) 594 testScheduler.runCurrent() 595 596 // WHEN: a new notification is posted 597 val entry = NotificationEntryBuilder().setId(1).build() 598 collectionListener.onEntryAdded(entry) 599 testScheduler.runCurrent() 600 601 // WHEN: one second has passed 602 testScheduler.advanceTimeBy(1.seconds) 603 testScheduler.runCurrent() 604 605 // WHEN: the notification is updated 606 collectionListener.onEntryUpdated(entry, UpdateSource.App) 607 testScheduler.runCurrent() 608 609 // WHEN: four more seconds have passed 610 testScheduler.advanceTimeBy(4.seconds) 611 testScheduler.runCurrent() 612 613 // WHEN: the keyguard is no longer showing 614 keyguardRepository.setKeyguardShowing(false) 615 keyguardTransitionRepository.sendTransitionSteps( 616 from = KeyguardState.LOCKSCREEN, 617 to = KeyguardState.GONE, 618 this.testScheduler, 619 ) 620 testScheduler.runCurrent() 621 622 // WHEN: Keyguard is shown again 623 keyguardRepository.setKeyguardShowing(true) 624 keyguardTransitionRepository.sendTransitionSteps( 625 from = KeyguardState.GONE, 626 to = KeyguardState.LOCKSCREEN, 627 this.testScheduler, 628 ) 629 testScheduler.runCurrent() 630 631 // THEN: The notification is considered unseen and is not filtered out. 632 assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse() 633 } 634 } 635 636 @Test seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemServernull637 fun seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemServer() { 638 // GIVEN: Keyguard is showing, not dozing, unseen notification is present 639 keyguardRepository.setKeyguardShowing(true) 640 keyguardRepository.setIsDozing(false) 641 runKeyguardCoordinatorTest { 642 val fakeEntry = NotificationEntryBuilder().build() 643 collectionListener.onEntryAdded(fakeEntry) 644 keyguardTransitionRepository.sendTransitionSteps( 645 from = KeyguardState.AOD, 646 to = KeyguardState.LOCKSCREEN, 647 this.testScheduler, 648 ) 649 testScheduler.runCurrent() 650 651 // WHEN: five seconds have passed 652 testScheduler.advanceTimeBy(5.seconds) 653 testScheduler.runCurrent() 654 655 // WHEN: Keyguard is no longer showing 656 keyguardRepository.setKeyguardShowing(false) 657 kosmos.setTransition( 658 sceneTransition = Idle(Scenes.Gone), 659 stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE), 660 ) 661 662 // WHEN: the notification is updated by the Server 663 collectionListener.onEntryUpdated(fakeEntry, UpdateSource.SystemServer) 664 testScheduler.runCurrent() 665 666 // WHEN: Keyguard is shown again 667 keyguardRepository.setKeyguardShowing(true) 668 kosmos.setTransition( 669 sceneTransition = Idle(Scenes.Lockscreen), 670 stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD), 671 ) 672 673 // THEN: The notification is still recognized as "seen" and is filtered out. 674 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() 675 } 676 } 677 678 @Test seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemUinull679 fun seenNotificationOnKeyguardMarkedAsSeenIfUpdatedBySystemUi() { 680 // GIVEN: Keyguard is showing, not dozing, unseen notification is present 681 keyguardRepository.setKeyguardShowing(true) 682 keyguardRepository.setIsDozing(false) 683 runKeyguardCoordinatorTest { 684 val fakeEntry = NotificationEntryBuilder().build() 685 collectionListener.onEntryAdded(fakeEntry) 686 keyguardTransitionRepository.sendTransitionSteps( 687 from = KeyguardState.AOD, 688 to = KeyguardState.LOCKSCREEN, 689 this.testScheduler, 690 ) 691 testScheduler.runCurrent() 692 693 // WHEN: five seconds have passed 694 testScheduler.advanceTimeBy(5.seconds) 695 testScheduler.runCurrent() 696 697 // WHEN: Keyguard is no longer showing 698 keyguardRepository.setKeyguardShowing(false) 699 kosmos.setTransition( 700 sceneTransition = Idle(Scenes.Gone), 701 stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE), 702 ) 703 704 // WHEN: the notification is updated by the SystemUi 705 collectionListener.onEntryUpdated(fakeEntry, UpdateSource.SystemUi) 706 testScheduler.runCurrent() 707 708 // WHEN: Keyguard is shown again 709 keyguardRepository.setKeyguardShowing(true) 710 kosmos.setTransition( 711 sceneTransition = Idle(Scenes.Lockscreen), 712 stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD), 713 ) 714 715 // THEN: The notification is still recognized as "seen" and is filtered out. 716 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue() 717 } 718 } 719 720 @Test seenNotificationOnKeyguardMarkedAsUnseenIfUpdatedByAppnull721 fun seenNotificationOnKeyguardMarkedAsUnseenIfUpdatedByApp() { 722 // GIVEN: Keyguard is showing, not dozing, unseen notification is present 723 keyguardRepository.setKeyguardShowing(true) 724 keyguardRepository.setIsDozing(false) 725 runKeyguardCoordinatorTest { 726 val fakeEntry = NotificationEntryBuilder().build() 727 collectionListener.onEntryAdded(fakeEntry) 728 keyguardTransitionRepository.sendTransitionSteps( 729 from = KeyguardState.AOD, 730 to = KeyguardState.LOCKSCREEN, 731 this.testScheduler, 732 ) 733 testScheduler.runCurrent() 734 735 // WHEN: five seconds have passed 736 testScheduler.advanceTimeBy(5.seconds) 737 testScheduler.runCurrent() 738 739 // WHEN: Keyguard is no longer showing 740 keyguardRepository.setKeyguardShowing(false) 741 kosmos.setTransition( 742 sceneTransition = Idle(Scenes.Gone), 743 stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE), 744 ) 745 746 // WHEN: the notification is updated by the App 747 collectionListener.onEntryUpdated(fakeEntry, UpdateSource.App) 748 testScheduler.runCurrent() 749 750 // WHEN: Keyguard is shown again 751 keyguardRepository.setKeyguardShowing(true) 752 kosmos.setTransition( 753 sceneTransition = Idle(Scenes.Lockscreen), 754 stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD), 755 ) 756 757 // THEN: The notification is now recognized as "unseen" and is not filtered out. 758 assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() 759 } 760 } 761 runKeyguardCoordinatorTestnull762 private fun runKeyguardCoordinatorTest( 763 testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit 764 ) { 765 val keyguardCoordinator = 766 OriginalUnseenKeyguardCoordinator( 767 dumpManager = kosmos.dumpManager, 768 headsUpManager = kosmos.mockHeadsUpManager, 769 keyguardRepository = kosmos.keyguardRepository, 770 keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, 771 logger = KeyguardCoordinatorLogger(logcatLogBuffer()), 772 scope = kosmos.testScope.backgroundScope, 773 seenNotificationsInteractor = kosmos.seenNotificationsInteractor, 774 statusBarStateController = kosmos.statusBarStateController, 775 sceneInteractor = kosmos.sceneInteractor, 776 ) 777 keyguardCoordinator.attach(notifPipeline) 778 kosmos.testScope.runTest { 779 KeyguardCoordinatorTestScope( 780 keyguardCoordinator, 781 kosmos.testScope, 782 kosmos.seenNotificationsInteractor, 783 kosmos.fakeSettings, 784 ) 785 .testBlock() 786 } 787 } 788 789 private inner class KeyguardCoordinatorTestScope( 790 private val keyguardCoordinator: OriginalUnseenKeyguardCoordinator, 791 private val scope: TestScope, 792 val seenNotificationsInteractor: SeenNotificationsInteractor, 793 private val fakeSettings: FakeSettings, <lambda>null794 ) : CoroutineScope by scope { 795 val testScheduler: TestCoroutineScheduler 796 get() = scope.testScheduler 797 798 val unseenFilter: NotifFilter 799 get() = keyguardCoordinator.unseenNotifFilter 800 801 val collectionListener: NotifCollectionListener = 802 argumentCaptor { verify(notifPipeline).addCollectionListener(capture()) }.lastValue 803 804 val onHeadsUpChangedListener: OnHeadsUpChangedListener 805 get() = 806 argumentCaptor { verify(kosmos.mockHeadsUpManager).addListener(capture()) } 807 .lastValue 808 } 809 810 companion object { 811 @JvmStatic 812 @Parameters(name = "{0}") getParamsnull813 fun getParams(): List<FlagsParameterization> { 814 return FlagsParameterization.allCombinationsOf().andSceneContainer() 815 } 816 } 817 } 818