1 /* 2 * Copyright (C) 2023 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 17 package com.android.systemui.shade 18 19 import android.graphics.Insets 20 import android.graphics.Rect 21 import android.os.PowerManager 22 import android.platform.test.annotations.DisableFlags 23 import android.platform.test.annotations.EnableFlags 24 import android.testing.AndroidTestingRunner 25 import android.testing.TestableLooper 26 import android.testing.ViewUtils 27 import android.view.MotionEvent 28 import android.view.View 29 import android.view.WindowInsets 30 import android.widget.FrameLayout 31 import androidx.lifecycle.Lifecycle 32 import androidx.lifecycle.LifecycleOwner 33 import androidx.test.filters.SmallTest 34 import com.android.compose.animation.scene.SceneKey 35 import com.android.systemui.Flags 36 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 37 import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX 38 import com.android.systemui.SysuiTestCase 39 import com.android.systemui.ambient.touch.TouchHandler 40 import com.android.systemui.ambient.touch.TouchMonitor 41 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent 42 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository 43 import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository 44 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository 45 import com.android.systemui.communal.domain.interactor.communalInteractor 46 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor 47 import com.android.systemui.communal.domain.interactor.setCommunalAvailable 48 import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled 49 import com.android.systemui.communal.shared.model.CommunalScenes 50 import com.android.systemui.communal.ui.compose.CommunalContent 51 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel 52 import com.android.systemui.communal.util.CommunalColors 53 import com.android.systemui.coroutines.collectLastValue 54 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository 55 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor 56 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor 57 import com.android.systemui.keyguard.shared.model.KeyguardState 58 import com.android.systemui.keyguard.shared.model.TransitionState 59 import com.android.systemui.keyguard.shared.model.TransitionStep 60 import com.android.systemui.kosmos.Kosmos 61 import com.android.systemui.kosmos.testDispatcher 62 import com.android.systemui.kosmos.testScope 63 import com.android.systemui.log.logcatLogBuffer 64 import com.android.systemui.media.controls.controller.keyguardMediaController 65 import com.android.systemui.res.R 66 import com.android.systemui.scene.shared.model.sceneDataSourceDelegator 67 import com.android.systemui.shade.domain.interactor.shadeInteractor 68 import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController 69 import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController 70 import com.android.systemui.testKosmos 71 import com.google.common.truth.Truth.assertThat 72 import kotlinx.coroutines.ExperimentalCoroutinesApi 73 import kotlinx.coroutines.launch 74 import kotlinx.coroutines.test.UnconfinedTestDispatcher 75 import kotlinx.coroutines.test.runTest 76 import org.junit.After 77 import org.junit.Assert.assertThrows 78 import org.junit.Before 79 import org.junit.Test 80 import org.junit.runner.RunWith 81 import org.mockito.kotlin.any 82 import org.mockito.kotlin.clearInvocations 83 import org.mockito.kotlin.doReturn 84 import org.mockito.kotlin.mock 85 import org.mockito.kotlin.never 86 import org.mockito.kotlin.spy 87 import org.mockito.kotlin.verify 88 import org.mockito.kotlin.whenever 89 90 @ExperimentalCoroutinesApi 91 @RunWith(AndroidTestingRunner::class) 92 @TestableLooper.RunWithLooper(setAsMainLooper = true) 93 @SmallTest 94 class GlanceableHubContainerControllerTest : SysuiTestCase() { 95 private val kosmos: Kosmos = <lambda>null96 testKosmos().apply { 97 // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using 98 // SharedFlow 99 testDispatcher = UnconfinedTestDispatcher() 100 } 101 102 private var communalViewModel = mock<CommunalViewModel>() 103 private var powerManager = mock<PowerManager>() 104 private var touchMonitor = mock<TouchMonitor>() 105 private var communalColors = mock<CommunalColors>() 106 private var communalContent = mock<CommunalContent>() 107 private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory 108 109 private lateinit var parentView: FrameLayout 110 private lateinit var containerView: View 111 private lateinit var testableLooper: TestableLooper 112 113 private lateinit var communalRepository: FakeCommunalSceneRepository 114 private lateinit var underTest: GlanceableHubContainerController 115 116 @Before setUpnull117 fun setUp() { 118 communalRepository = kosmos.fakeCommunalSceneRepository 119 120 ambientTouchComponentFactory = 121 object : AmbientTouchComponent.Factory { 122 override fun create( 123 lifecycleOwner: LifecycleOwner, 124 touchHandlers: Set<TouchHandler>, 125 loggingName: String, 126 ): AmbientTouchComponent = 127 object : AmbientTouchComponent { 128 override fun getTouchMonitor(): TouchMonitor = touchMonitor 129 } 130 } 131 132 with(kosmos) { 133 underTest = 134 GlanceableHubContainerController( 135 communalInteractor, 136 communalSettingsInteractor, 137 communalViewModel, 138 keyguardInteractor, 139 keyguardTransitionInteractor, 140 shadeInteractor, 141 powerManager, 142 communalColors, 143 ambientTouchComponentFactory, 144 communalContent, 145 sceneDataSourceDelegator, 146 notificationStackScrollLayoutController, 147 keyguardMediaController, 148 lockscreenSmartspaceController, 149 logcatLogBuffer("GlanceableHubContainerControllerTest"), 150 ) 151 152 // Make below last notification true by default or else touches will be ignored by 153 // default when the hub is not showing. 154 whenever(notificationStackScrollLayoutController.isBelowLastNotification(any(), any())) 155 .thenReturn(true) 156 } 157 testableLooper = TestableLooper.get(this) 158 159 overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH) 160 overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH) 161 overrideResource( 162 R.dimen.communal_bottom_edge_swipe_region_height, 163 BOTTOM_SWIPE_REGION_WIDTH, 164 ) 165 166 // Make communal available so that communalInteractor.desiredScene accurately reflects 167 // scene changes instead of just returning Blank. 168 mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB) 169 with(kosmos.testScope) { 170 launch { kosmos.setCommunalAvailable(true) } 171 testScheduler.runCurrent() 172 } 173 174 initAndAttachContainerView() 175 } 176 177 @After tearDownnull178 fun tearDown() { 179 ViewUtils.detachView(parentView) 180 } 181 182 @Test initView_calledTwice_throwsExceptionnull183 fun initView_calledTwice_throwsException() = 184 with(kosmos) { 185 testScope.runTest { 186 underTest = 187 GlanceableHubContainerController( 188 communalInteractor, 189 kosmos.communalSettingsInteractor, 190 communalViewModel, 191 keyguardInteractor, 192 kosmos.keyguardTransitionInteractor, 193 shadeInteractor, 194 powerManager, 195 communalColors, 196 ambientTouchComponentFactory, 197 communalContent, 198 kosmos.sceneDataSourceDelegator, 199 kosmos.notificationStackScrollLayoutController, 200 kosmos.keyguardMediaController, 201 kosmos.lockscreenSmartspaceController, 202 logcatLogBuffer("GlanceableHubContainerControllerTest"), 203 ) 204 205 // First call succeeds. 206 underTest.initView(context) 207 208 // Second call throws. 209 assertThrows(RuntimeException::class.java) { underTest.initView(context) } 210 } 211 } 212 213 @Test lifecycle_initializedAfterConstructionnull214 fun lifecycle_initializedAfterConstruction() = 215 with(kosmos) { 216 val underTest = 217 GlanceableHubContainerController( 218 communalInteractor, 219 kosmos.communalSettingsInteractor, 220 communalViewModel, 221 keyguardInteractor, 222 kosmos.keyguardTransitionInteractor, 223 shadeInteractor, 224 powerManager, 225 communalColors, 226 ambientTouchComponentFactory, 227 communalContent, 228 kosmos.sceneDataSourceDelegator, 229 kosmos.notificationStackScrollLayoutController, 230 kosmos.keyguardMediaController, 231 kosmos.lockscreenSmartspaceController, 232 logcatLogBuffer("GlanceableHubContainerControllerTest"), 233 ) 234 235 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED) 236 } 237 238 @Test lifecycle_createdAfterViewCreatednull239 fun lifecycle_createdAfterViewCreated() = 240 with(kosmos) { 241 val underTest = 242 GlanceableHubContainerController( 243 communalInteractor, 244 kosmos.communalSettingsInteractor, 245 communalViewModel, 246 keyguardInteractor, 247 kosmos.keyguardTransitionInteractor, 248 shadeInteractor, 249 powerManager, 250 communalColors, 251 ambientTouchComponentFactory, 252 communalContent, 253 kosmos.sceneDataSourceDelegator, 254 kosmos.notificationStackScrollLayoutController, 255 kosmos.keyguardMediaController, 256 kosmos.lockscreenSmartspaceController, 257 logcatLogBuffer("GlanceableHubContainerControllerTest"), 258 ) 259 260 // Only initView without attaching a view as we don't want the flows to start collecting 261 // yet. 262 underTest.initView(View(context)) 263 264 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED) 265 } 266 267 @Test lifecycle_startedAfterFlowsUpdatenull268 fun lifecycle_startedAfterFlowsUpdate() { 269 // Flows start collecting due to test setup, causing the state to advance to STARTED. 270 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 271 } 272 273 @Test lifecycle_resumedAfterCommunalShowsnull274 fun lifecycle_resumedAfterCommunalShows() = 275 with(kosmos) { 276 testScope.runTest { 277 // Communal is open. 278 goToScene(CommunalScenes.Communal) 279 280 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED) 281 } 282 } 283 284 @Test lifecycle_startedAfterCommunalClosesnull285 fun lifecycle_startedAfterCommunalCloses() = 286 with(kosmos) { 287 testScope.runTest { 288 // Communal is open. 289 goToScene(CommunalScenes.Communal) 290 291 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED) 292 293 // Communal closes. 294 goToScene(CommunalScenes.Blank) 295 296 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 297 } 298 } 299 300 @Test lifecycle_startedAfterPrimaryBouncerShowsnull301 fun lifecycle_startedAfterPrimaryBouncerShows() = 302 with(kosmos) { 303 testScope.runTest { 304 // Communal is open. 305 goToScene(CommunalScenes.Communal) 306 307 // Bouncer is visible. 308 fakeKeyguardBouncerRepository.setPrimaryShow(true) 309 testableLooper.processAllMessages() 310 311 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 312 } 313 } 314 315 @Test lifecycle_startedAfterAlternateBouncerShowsnull316 fun lifecycle_startedAfterAlternateBouncerShows() = 317 with(kosmos) { 318 testScope.runTest { 319 // Communal is open. 320 goToScene(CommunalScenes.Communal) 321 322 // Bouncer is visible. 323 fakeKeyguardBouncerRepository.setAlternateVisible(true) 324 testableLooper.processAllMessages() 325 326 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 327 } 328 } 329 330 @Test lifecycle_startedWhenEditActivityShowingnull331 fun lifecycle_startedWhenEditActivityShowing() = 332 with(kosmos) { 333 testScope.runTest { 334 // Communal is open. 335 goToScene(CommunalScenes.Communal) 336 337 // Edit activity is showing. 338 communalInteractor.setEditActivityShowing(true) 339 testableLooper.processAllMessages() 340 341 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 342 } 343 } 344 345 @Test lifecycle_startedWhenEditModeTransitionStartednull346 fun lifecycle_startedWhenEditModeTransitionStarted() = 347 with(kosmos) { 348 testScope.runTest { 349 // Communal is open. 350 goToScene(CommunalScenes.Communal) 351 352 // Leaving edit mode to return to the hub. 353 fakeKeyguardTransitionRepository.sendTransitionStep( 354 TransitionStep( 355 from = KeyguardState.GONE, 356 to = KeyguardState.GLANCEABLE_HUB, 357 value = 1.0f, 358 transitionState = TransitionState.RUNNING, 359 ) 360 ) 361 testableLooper.processAllMessages() 362 363 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 364 } 365 } 366 367 @Test lifecycle_createdAfterDisposeViewnull368 fun lifecycle_createdAfterDisposeView() { 369 // Container view disposed. 370 underTest.disposeView() 371 372 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED) 373 } 374 375 @Test lifecycle_startedAfterShadeShowsnull376 fun lifecycle_startedAfterShadeShows() = 377 with(kosmos) { 378 testScope.runTest { 379 // Communal is open. 380 goToScene(CommunalScenes.Communal) 381 382 // Shade shows up. 383 shadeTestUtil.setQsExpansion(1.0f) 384 testableLooper.processAllMessages() 385 386 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 387 } 388 } 389 390 @Test lifecycle_doesNotResumeOnUserInteractivityOnceExpandednull391 fun lifecycle_doesNotResumeOnUserInteractivityOnceExpanded() = 392 with(kosmos) { 393 testScope.runTest { 394 // Communal is open. 395 goToScene(CommunalScenes.Communal) 396 397 // Shade shows up. 398 shadeTestUtil.setShadeExpansion(1.0f) 399 testableLooper.processAllMessages() 400 underTest.onTouchEvent(DOWN_EVENT) 401 testableLooper.processAllMessages() 402 403 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 404 405 // Shade starts collapsing. 406 shadeTestUtil.setShadeExpansion(.5f) 407 testableLooper.processAllMessages() 408 underTest.onTouchEvent(DOWN_EVENT) 409 testableLooper.processAllMessages() 410 411 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED) 412 413 // Shade fully collpase, and then expand should with touch interaction should now 414 // be resumed. 415 shadeTestUtil.setShadeExpansion(0f) 416 testableLooper.processAllMessages() 417 shadeTestUtil.setShadeExpansion(.5f) 418 testableLooper.processAllMessages() 419 underTest.onTouchEvent(DOWN_EVENT) 420 testableLooper.processAllMessages() 421 422 assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED) 423 } 424 } 425 426 @Test touchHandling_moveEventProcessedAfterCancelnull427 fun touchHandling_moveEventProcessedAfterCancel() = 428 with(kosmos) { 429 testScope.runTest { 430 // Communal is open. 431 goToScene(CommunalScenes.Communal) 432 433 // Touch starts and ends. 434 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() 435 assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue() 436 437 // Up event is no longer processed 438 assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse() 439 440 // Move event can still be processed 441 assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue() 442 assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue() 443 assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue() 444 } 445 } 446 447 @Test editMode_communalAvailablenull448 fun editMode_communalAvailable() = 449 with(kosmos) { 450 testScope.runTest { 451 val available by collectLastValue(underTest.communalAvailable()) 452 setCommunalAvailable(false) 453 454 assertThat(available).isFalse() 455 communalInteractor.setEditModeOpen(true) 456 assertThat(available).isTrue() 457 } 458 } 459 460 @Test 461 @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) gestureExclusionZone_setAfterInitnull462 fun gestureExclusionZone_setAfterInit() = 463 with(kosmos) { 464 testScope.runTest { 465 goToScene(CommunalScenes.Communal) 466 467 assertThat(containerView.systemGestureExclusionRects) 468 .containsExactly( 469 Rect( 470 /* left= */ 0, 471 /* top= */ TOP_SWIPE_REGION_WIDTH, 472 /* right= */ CONTAINER_WIDTH, 473 /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH, 474 ) 475 ) 476 } 477 } 478 479 @Test 480 @EnableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) gestureExclusionZone_setAfterInit_fullSwipenull481 fun gestureExclusionZone_setAfterInit_fullSwipe() = 482 with(kosmos) { 483 testScope.runTest { 484 goToScene(CommunalScenes.Communal) 485 486 assertThat(containerView.systemGestureExclusionRects).isEmpty() 487 } 488 } 489 490 @Test 491 @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) gestureExclusionZone_unsetWhenShadeOpennull492 fun gestureExclusionZone_unsetWhenShadeOpen() = 493 with(kosmos) { 494 testScope.runTest { 495 goToScene(CommunalScenes.Communal) 496 497 // Exclusion rect is set. 498 assertThat(containerView.systemGestureExclusionRects).isNotEmpty() 499 500 // Shade shows up. 501 shadeTestUtil.setQsExpansion(1.0f) 502 testableLooper.processAllMessages() 503 504 // Exclusion rects are unset. 505 assertThat(containerView.systemGestureExclusionRects).isEmpty() 506 } 507 } 508 509 @Test 510 @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) gestureExclusionZone_unsetWhenBouncerOpennull511 fun gestureExclusionZone_unsetWhenBouncerOpen() = 512 with(kosmos) { 513 testScope.runTest { 514 goToScene(CommunalScenes.Communal) 515 516 // Exclusion rect is set. 517 assertThat(containerView.systemGestureExclusionRects).isNotEmpty() 518 519 // Bouncer is visible. 520 fakeKeyguardBouncerRepository.setPrimaryShow(true) 521 testableLooper.processAllMessages() 522 523 // Exclusion rects are unset. 524 assertThat(containerView.systemGestureExclusionRects).isEmpty() 525 } 526 } 527 528 @Test 529 @DisableFlags(FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX) gestureExclusionZone_unsetWhenHubClosednull530 fun gestureExclusionZone_unsetWhenHubClosed() = 531 with(kosmos) { 532 testScope.runTest { 533 goToScene(CommunalScenes.Communal) 534 535 // Exclusion rect is set. 536 assertThat(containerView.systemGestureExclusionRects).isNotEmpty() 537 538 // Leave the hub. 539 goToScene(CommunalScenes.Blank) 540 541 // Exclusion rect is unset. 542 assertThat(containerView.systemGestureExclusionRects).isEmpty() 543 } 544 } 545 546 @Test fullScreenSwipeGesture_doNotProcessTouchesInNotificationStacknull547 fun fullScreenSwipeGesture_doNotProcessTouchesInNotificationStack() = 548 with(kosmos) { 549 testScope.runTest { 550 // Communal is closed. 551 goToScene(CommunalScenes.Blank) 552 whenever( 553 notificationStackScrollLayoutController.isBelowLastNotification( 554 any(), 555 any(), 556 ) 557 ) 558 .thenReturn(false) 559 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() 560 } 561 } 562 563 @Test fullScreenSwipeGesture_doNotProcessTouchesInUmonull564 fun fullScreenSwipeGesture_doNotProcessTouchesInUmo() = 565 with(kosmos) { 566 testScope.runTest { 567 // Communal is closed. 568 goToScene(CommunalScenes.Blank) 569 whenever(keyguardMediaController.isWithinMediaViewBounds(any(), any())) 570 .thenReturn(true) 571 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() 572 } 573 } 574 575 @Test fullScreenSwipeGesture_doNotProcessTouchesInSmartspacenull576 fun fullScreenSwipeGesture_doNotProcessTouchesInSmartspace() = 577 with(kosmos) { 578 testScope.runTest { 579 // Communal is closed. 580 goToScene(CommunalScenes.Blank) 581 whenever(lockscreenSmartspaceController.isWithinSmartspaceBounds(any(), any())) 582 .thenReturn(true) 583 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() 584 } 585 } 586 587 @Test onTouchEvent_hubOpen_touchesDispatchednull588 fun onTouchEvent_hubOpen_touchesDispatched() = 589 with(kosmos) { 590 testScope.runTest { 591 // Communal is open. 592 goToScene(CommunalScenes.Communal) 593 594 // Touch event is sent to the container view. 595 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() 596 verify(containerView).onTouchEvent(DOWN_EVENT) 597 assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue() 598 verify(containerView).onTouchEvent(UP_EVENT) 599 } 600 } 601 602 @Test onTouchEvent_editActivityShowing_touchesConsumedButNotDispatchednull603 fun onTouchEvent_editActivityShowing_touchesConsumedButNotDispatched() = 604 with(kosmos) { 605 testScope.runTest { 606 // Communal is open. 607 goToScene(CommunalScenes.Communal) 608 609 // Transitioning to or from edit mode. 610 communalInteractor.setEditActivityShowing(true) 611 testableLooper.processAllMessages() 612 613 // onTouchEvent returns true to consume the touch, but it is not sent to the 614 // container view. 615 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() 616 verify(containerView, never()).onTouchEvent(any()) 617 } 618 } 619 620 @Test onTouchEvent_editModeTransitionStarted_touchesConsumedButNotDispatchednull621 fun onTouchEvent_editModeTransitionStarted_touchesConsumedButNotDispatched() = 622 with(kosmos) { 623 testScope.runTest { 624 // Communal is open. 625 goToScene(CommunalScenes.Communal) 626 627 // Leaving edit mode to return to the hub. 628 fakeKeyguardTransitionRepository.sendTransitionStep( 629 TransitionStep( 630 from = KeyguardState.GONE, 631 to = KeyguardState.GLANCEABLE_HUB, 632 value = 1.0f, 633 transitionState = TransitionState.RUNNING, 634 ) 635 ) 636 testableLooper.processAllMessages() 637 638 // onTouchEvent returns true to consume the touch, but it is not sent to the 639 // container view. 640 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() 641 verify(containerView, never()).onTouchEvent(any()) 642 } 643 } 644 645 @DisableFlags(FLAG_GLANCEABLE_HUB_V2) 646 @Test onTouchEvent_shadeInteracting_movesNotDispatchednull647 fun onTouchEvent_shadeInteracting_movesNotDispatched() = 648 with(kosmos) { 649 testScope.runTest { 650 // On lockscreen. 651 goToScene(CommunalScenes.Blank) 652 whenever( 653 notificationStackScrollLayoutController.isBelowLastNotification( 654 any(), 655 any(), 656 ) 657 ) 658 .thenReturn(true) 659 660 // Touches not consumed by default but are received by containerView. 661 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() 662 verify(containerView).onTouchEvent(DOWN_EVENT) 663 664 // User is interacting with shade on lockscreen. 665 shadeTestUtil.setLockscreenShadeTracking(true) 666 testableLooper.processAllMessages() 667 668 // A move event is ignored while the user is already interacting. 669 assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse() 670 verify(containerView, never()).onTouchEvent(MOVE_EVENT) 671 672 // An up event is still delivered. 673 assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse() 674 verify(containerView).onTouchEvent(UP_EVENT) 675 } 676 } 677 678 @Test onTouchEvent_shadeExpanding_touchesNotDispatchednull679 fun onTouchEvent_shadeExpanding_touchesNotDispatched() = 680 with(kosmos) { 681 testScope.runTest { 682 // On lockscreen. 683 goToScene(CommunalScenes.Blank) 684 whenever( 685 notificationStackScrollLayoutController.isBelowLastNotification( 686 any(), 687 any(), 688 ) 689 ) 690 .thenReturn(true) 691 692 // Shade is open slightly. 693 shadeTestUtil.setShadeExpansion(0.01f) 694 testableLooper.processAllMessages() 695 696 // Touches are not consumed. 697 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() 698 verify(containerView, never()).onTouchEvent(DOWN_EVENT) 699 } 700 } 701 702 @DisableFlags(FLAG_GLANCEABLE_HUB_V2) 703 @Test onTouchEvent_bouncerInteracting_movesNotDispatchednull704 fun onTouchEvent_bouncerInteracting_movesNotDispatched() = 705 with(kosmos) { 706 testScope.runTest { 707 // On lockscreen. 708 goToScene(CommunalScenes.Blank) 709 whenever( 710 notificationStackScrollLayoutController.isBelowLastNotification( 711 any(), 712 any(), 713 ) 714 ) 715 .thenReturn(true) 716 717 // Touches not consumed by default but are received by containerView. 718 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() 719 verify(containerView).onTouchEvent(DOWN_EVENT) 720 721 // User is interacting with bouncer on lockscreen. 722 fakeKeyguardBouncerRepository.setPrimaryShow(true) 723 testableLooper.processAllMessages() 724 725 // A move event is ignored while the user is already interacting. 726 assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse() 727 verify(containerView, never()).onTouchEvent(MOVE_EVENT) 728 729 // An up event is still delivered. 730 assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse() 731 verify(containerView).onTouchEvent(UP_EVENT) 732 } 733 } 734 735 @EnableFlags(FLAG_GLANCEABLE_HUB_V2) 736 @Test onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnorednull737 fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() = 738 with(kosmos) { 739 testScope.runTest { 740 kosmos.setCommunalV2ConfigEnabled(true) 741 742 // On lockscreen. 743 goToScene(CommunalScenes.Blank) 744 745 assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() 746 verify(containerView, never()).onTouchEvent(DOWN_EVENT) 747 } 748 } 749 750 @Test disposeView_destroysTouchMonitornull751 fun disposeView_destroysTouchMonitor() { 752 clearInvocations(touchMonitor) 753 754 underTest.disposeView() 755 756 verify(touchMonitor).destroy() 757 } 758 initAndAttachContainerViewnull759 private fun initAndAttachContainerView() { 760 val mockInsets = 761 mock<WindowInsets> { 762 on { getInsets(WindowInsets.Type.systemGestures()) } doReturn FAKE_INSETS 763 } 764 765 containerView = 766 spy(View(context)) { 767 on { rootWindowInsets } doReturn mockInsets 768 // Return true to handle touch events or else further events in the gesture will not 769 // be received as we are using real View objects. 770 onGeneric { onTouchEvent(any()) } doReturn true 771 } 772 773 parentView = FrameLayout(context) 774 775 parentView.addView(underTest.initView(containerView)) 776 777 // Attach the view so that flows start collecting. 778 ViewUtils.attachView(parentView, CONTAINER_WIDTH, CONTAINER_HEIGHT) 779 // Attaching is async so processAllMessages is required for view.repeatWhenAttached to run. 780 testableLooper.processAllMessages() 781 } 782 goToScenenull783 private suspend fun goToScene(scene: SceneKey) { 784 communalRepository.changeScene(scene) 785 if (scene == CommunalScenes.Communal) { 786 kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( 787 from = KeyguardState.LOCKSCREEN, 788 to = KeyguardState.GLANCEABLE_HUB, 789 kosmos.testScope, 790 ) 791 } else { 792 kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( 793 from = KeyguardState.GLANCEABLE_HUB, 794 to = KeyguardState.LOCKSCREEN, 795 kosmos.testScope, 796 ) 797 } 798 testableLooper.processAllMessages() 799 } 800 801 companion object { 802 private const val CONTAINER_WIDTH = 100 803 private const val CONTAINER_HEIGHT = 100 804 private const val RIGHT_SWIPE_REGION_WIDTH = 20 805 private const val TOP_SWIPE_REGION_WIDTH = 12 806 private const val BOTTOM_SWIPE_REGION_WIDTH = 14 807 private val FAKE_INSETS = Insets.of(10, 20, 30, 50) 808 809 /** 810 * A touch down event right in the middle of the screen, to avoid being in any of the swipe 811 * regions. 812 */ 813 private val DOWN_EVENT = 814 MotionEvent.obtain( 815 0L, 816 0L, 817 MotionEvent.ACTION_DOWN, 818 CONTAINER_WIDTH.toFloat() / 2, 819 CONTAINER_HEIGHT.toFloat() / 2, 820 0, 821 ) 822 823 private val CANCEL_EVENT = 824 MotionEvent.obtain( 825 0L, 826 0L, 827 MotionEvent.ACTION_CANCEL, 828 CONTAINER_WIDTH.toFloat() / 2, 829 CONTAINER_HEIGHT.toFloat() / 2, 830 0, 831 ) 832 833 private val MOVE_EVENT = 834 MotionEvent.obtain( 835 0L, 836 0L, 837 MotionEvent.ACTION_MOVE, 838 CONTAINER_WIDTH.toFloat() / 2, 839 CONTAINER_HEIGHT.toFloat() / 2, 840 0, 841 ) 842 843 private val UP_EVENT = 844 MotionEvent.obtain( 845 0L, 846 0L, 847 MotionEvent.ACTION_UP, 848 CONTAINER_WIDTH.toFloat() / 2, 849 CONTAINER_HEIGHT.toFloat() / 2, 850 0, 851 ) 852 } 853 } 854