1 /* 2 * Copyright (C) 2021 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.temporarydisplay.chipbar 18 19 import android.os.PowerManager 20 import android.os.VibrationAttributes 21 import android.os.VibrationEffect 22 import android.testing.TestableLooper 23 import android.view.MotionEvent 24 import android.view.View 25 import android.view.ViewGroup 26 import android.view.WindowManager 27 import android.view.accessibility.AccessibilityManager 28 import android.widget.ImageView 29 import android.widget.TextView 30 import androidx.core.animation.doOnCancel 31 import androidx.test.ext.junit.runners.AndroidJUnit4 32 import androidx.test.filters.SmallTest 33 import com.android.app.viewcapture.ViewCapture 34 import com.android.app.viewcapture.ViewCaptureAwareWindowManager 35 import com.android.internal.logging.InstanceId 36 import com.android.internal.logging.testing.UiEventLoggerFake 37 import com.android.systemui.SysuiTestCase 38 import com.android.systemui.classifier.FalsingCollector 39 import com.android.systemui.common.shared.model.ContentDescription 40 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription 41 import com.android.systemui.common.shared.model.Icon 42 import com.android.systemui.common.shared.model.Text 43 import com.android.systemui.common.shared.model.TintedIcon 44 import com.android.systemui.dump.DumpManager 45 import com.android.systemui.plugins.FalsingManager 46 import com.android.systemui.res.R 47 import com.android.systemui.statusbar.VibratorHelper 48 import com.android.systemui.statusbar.policy.ConfigurationController 49 import com.android.systemui.temporarydisplay.TemporaryViewUiEvent 50 import com.android.systemui.temporarydisplay.TemporaryViewUiEventLogger 51 import com.android.systemui.temporarydisplay.ViewPriority 52 import com.android.systemui.util.concurrency.FakeExecutor 53 import com.android.systemui.util.mockito.any 54 import com.android.systemui.util.mockito.argumentCaptor 55 import com.android.systemui.util.mockito.capture 56 import com.android.systemui.util.mockito.eq 57 import com.android.systemui.util.time.FakeSystemClock 58 import com.android.systemui.util.view.ViewUtil 59 import com.android.systemui.util.wakelock.WakeLockFake 60 import com.google.common.truth.Truth.assertThat 61 import org.junit.Before 62 import org.junit.Test 63 import org.junit.runner.RunWith 64 import org.mockito.ArgumentCaptor 65 import org.mockito.ArgumentMatchers.anyInt 66 import org.mockito.Mock 67 import org.mockito.Mockito.never 68 import org.mockito.Mockito.verify 69 import org.mockito.Mockito.`when` as whenever 70 import org.mockito.MockitoAnnotations 71 72 @SmallTest 73 @RunWith(AndroidJUnit4::class) 74 @TestableLooper.RunWithLooper 75 class ChipbarCoordinatorTest : SysuiTestCase() { 76 private lateinit var underTest: ChipbarCoordinator 77 78 @Mock private lateinit var logger: ChipbarLogger 79 @Mock private lateinit var accessibilityManager: AccessibilityManager 80 @Mock private lateinit var configurationController: ConfigurationController 81 @Mock private lateinit var dumpManager: DumpManager 82 @Mock private lateinit var powerManager: PowerManager 83 @Mock private lateinit var windowManager: WindowManager 84 @Mock private lateinit var falsingManager: FalsingManager 85 @Mock private lateinit var falsingCollector: FalsingCollector 86 @Mock private lateinit var viewUtil: ViewUtil 87 @Mock private lateinit var vibratorHelper: VibratorHelper 88 @Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler 89 @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture> 90 private lateinit var chipbarAnimator: TestChipbarAnimator 91 private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder 92 private lateinit var fakeWakeLock: WakeLockFake 93 private lateinit var fakeClock: FakeSystemClock 94 private lateinit var fakeExecutor: FakeExecutor 95 private lateinit var uiEventLoggerFake: UiEventLoggerFake 96 private lateinit var uiEventLogger: TemporaryViewUiEventLogger 97 98 @Before setUpnull99 fun setUp() { 100 MockitoAnnotations.initMocks(this) 101 whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT) 102 103 fakeClock = FakeSystemClock() 104 fakeExecutor = FakeExecutor(fakeClock) 105 106 fakeWakeLock = WakeLockFake() 107 fakeWakeLockBuilder = WakeLockFake.Builder(context) 108 fakeWakeLockBuilder.setWakeLock(fakeWakeLock) 109 110 uiEventLoggerFake = UiEventLoggerFake() 111 uiEventLogger = TemporaryViewUiEventLogger(uiEventLoggerFake) 112 chipbarAnimator = TestChipbarAnimator() 113 114 underTest = 115 ChipbarCoordinator( 116 context, 117 logger, 118 ViewCaptureAwareWindowManager(windowManager, lazyViewCapture, 119 isViewCaptureEnabled = false), 120 fakeExecutor, 121 accessibilityManager, 122 configurationController, 123 dumpManager, 124 powerManager, 125 chipbarAnimator, 126 falsingManager, 127 falsingCollector, 128 swipeGestureHandler, 129 viewUtil, 130 vibratorHelper, 131 fakeWakeLockBuilder, 132 fakeClock, 133 uiEventLogger, 134 ) 135 underTest.start() 136 } 137 138 @Test displayView_contentDescription_iconHasDescriptionnull139 fun displayView_contentDescription_iconHasDescription() { 140 underTest.displayView( 141 createChipbarInfo( 142 Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("loadedCD")), 143 Text.Loaded("text"), 144 endItem = null, 145 ) 146 ) 147 148 val contentDescView = getChipbarView().getInnerView() 149 assertThat(contentDescView.contentDescription.toString()).contains("loadedCD") 150 assertThat(contentDescView.contentDescription.toString()).contains("text") 151 } 152 153 @Test displayView_contentDescription_iconHasNoDescriptionnull154 fun displayView_contentDescription_iconHasNoDescription() { 155 underTest.displayView( 156 createChipbarInfo( 157 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 158 Text.Loaded("text"), 159 endItem = null, 160 ) 161 ) 162 163 val contentDescView = getChipbarView().getInnerView() 164 assertThat(contentDescView.contentDescription.toString()).isEqualTo("text") 165 } 166 167 @Test displayView_contentDescription_endIsLoadingnull168 fun displayView_contentDescription_endIsLoading() { 169 underTest.displayView( 170 createChipbarInfo( 171 Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("loadedCD")), 172 Text.Loaded("text"), 173 endItem = ChipbarEndItem.Loading, 174 ) 175 ) 176 177 val contentDescView = getChipbarView().getInnerView() 178 val loadingDesc = context.resources.getString(R.string.media_transfer_loading) 179 assertThat(contentDescView.contentDescription.toString()).contains("text") 180 assertThat(contentDescView.contentDescription.toString()).contains(loadingDesc) 181 } 182 183 @Test displayView_contentDescription_endNotLoadingnull184 fun displayView_contentDescription_endNotLoading() { 185 underTest.displayView( 186 createChipbarInfo( 187 Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("loadedCD")), 188 Text.Loaded("text"), 189 endItem = ChipbarEndItem.Error, 190 ) 191 ) 192 193 val contentDescView = getChipbarView().getInnerView() 194 val loadingDesc = context.resources.getString(R.string.media_transfer_loading) 195 assertThat(contentDescView.contentDescription.toString()).contains("text") 196 assertThat(contentDescView.contentDescription.toString()).doesNotContain(loadingDesc) 197 } 198 199 @Test displayView_loadedIcon_correctlyRenderednull200 fun displayView_loadedIcon_correctlyRendered() { 201 val drawable = context.getDrawable(R.drawable.ic_celebration)!! 202 203 underTest.displayView( 204 createChipbarInfo( 205 Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")), 206 Text.Loaded("text"), 207 endItem = null, 208 ) 209 ) 210 211 val iconView = getChipbarView().getStartIconView() 212 assertThat(iconView.drawable).isEqualTo(drawable) 213 assertThat(iconView.contentDescription).isEqualTo("loadedCD") 214 } 215 216 @Test displayView_resourceIcon_correctlyRenderednull217 fun displayView_resourceIcon_correctlyRendered() { 218 val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout) 219 underTest.displayView( 220 createChipbarInfo( 221 Icon.Resource(R.drawable.ic_cake, contentDescription), 222 Text.Loaded("text"), 223 endItem = null, 224 ) 225 ) 226 227 val iconView = getChipbarView().getStartIconView() 228 assertThat(iconView.contentDescription) 229 .isEqualTo(contentDescription.loadContentDescription(context)) 230 } 231 232 @Test displayView_loadedText_correctlyRenderednull233 fun displayView_loadedText_correctlyRendered() { 234 underTest.displayView( 235 createChipbarInfo( 236 Icon.Resource(R.id.check_box, null), 237 Text.Loaded("display view text here"), 238 endItem = null, 239 ) 240 ) 241 242 assertThat(getChipbarView().getChipText()).isEqualTo("display view text here") 243 } 244 245 @Test displayView_resourceText_correctlyRenderednull246 fun displayView_resourceText_correctlyRendered() { 247 underTest.displayView( 248 createChipbarInfo( 249 Icon.Resource(R.id.check_box, null), 250 Text.Resource(R.string.screenrecord_start_error), 251 endItem = null, 252 ) 253 ) 254 255 assertThat(getChipbarView().getChipText()) 256 .isEqualTo(context.getString(R.string.screenrecord_start_error)) 257 } 258 259 @Test displayView_endItemNull_correctlyRenderednull260 fun displayView_endItemNull_correctlyRendered() { 261 underTest.displayView( 262 createChipbarInfo( 263 Icon.Resource(R.id.check_box, null), 264 Text.Loaded("text"), 265 endItem = null, 266 ) 267 ) 268 269 val chipbarView = getChipbarView() 270 assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) 271 assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) 272 assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) 273 } 274 275 @Test displayView_endItemLoading_correctlyRenderednull276 fun displayView_endItemLoading_correctlyRendered() { 277 underTest.displayView( 278 createChipbarInfo( 279 Icon.Resource(R.id.check_box, null), 280 Text.Loaded("text"), 281 endItem = ChipbarEndItem.Loading, 282 ) 283 ) 284 285 val chipbarView = getChipbarView() 286 assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) 287 assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) 288 assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) 289 } 290 291 @Test displayView_endItemError_correctlyRenderednull292 fun displayView_endItemError_correctlyRendered() { 293 underTest.displayView( 294 createChipbarInfo( 295 Icon.Resource(R.id.check_box, null), 296 Text.Loaded("text"), 297 endItem = ChipbarEndItem.Error, 298 ) 299 ) 300 301 val chipbarView = getChipbarView() 302 assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) 303 assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) 304 assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) 305 } 306 307 @Test displayView_endItemButton_correctlyRenderednull308 fun displayView_endItemButton_correctlyRendered() { 309 underTest.displayView( 310 createChipbarInfo( 311 Icon.Resource(R.id.check_box, null), 312 Text.Loaded("text"), 313 endItem = 314 ChipbarEndItem.Button( 315 Text.Loaded("button text"), 316 onClickListener = {}, 317 ), 318 ) 319 ) 320 321 val chipbarView = getChipbarView() 322 assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) 323 assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) 324 assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.VISIBLE) 325 assertThat(chipbarView.getEndButton().text).isEqualTo("button text") 326 assertThat(chipbarView.getEndButton().hasOnClickListeners()).isTrue() 327 } 328 329 @Test displayView_endItemButtonClicked_falseTap_listenerNotRunnull330 fun displayView_endItemButtonClicked_falseTap_listenerNotRun() { 331 whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) 332 var isClicked = false 333 val buttonClickListener = View.OnClickListener { isClicked = true } 334 335 underTest.displayView( 336 createChipbarInfo( 337 Icon.Resource(R.id.check_box, null), 338 Text.Loaded("text"), 339 endItem = 340 ChipbarEndItem.Button( 341 Text.Loaded("button text"), 342 buttonClickListener, 343 ), 344 ) 345 ) 346 347 getChipbarView().getEndButton().performClick() 348 349 assertThat(isClicked).isFalse() 350 } 351 352 @Test displayView_endItemButtonClicked_notFalseTap_listenerRunnull353 fun displayView_endItemButtonClicked_notFalseTap_listenerRun() { 354 whenever(falsingManager.isFalseTap(anyInt())).thenReturn(false) 355 var isClicked = false 356 val buttonClickListener = View.OnClickListener { isClicked = true } 357 358 underTest.displayView( 359 createChipbarInfo( 360 Icon.Resource(R.id.check_box, null), 361 Text.Loaded("text"), 362 endItem = 363 ChipbarEndItem.Button( 364 Text.Loaded("button text"), 365 buttonClickListener, 366 ), 367 ) 368 ) 369 370 getChipbarView().getEndButton().performClick() 371 372 assertThat(isClicked).isTrue() 373 } 374 375 @Test displayView_loading_animationStartednull376 fun displayView_loading_animationStarted() { 377 underTest.displayView( 378 createChipbarInfo( 379 Icon.Resource(R.id.check_box, null), 380 Text.Loaded("text"), 381 endItem = ChipbarEndItem.Loading, 382 ) 383 ) 384 385 assertThat(underTest.loadingDetails!!.animator.isStarted).isTrue() 386 } 387 388 @Test displayView_notLoading_noAnimationnull389 fun displayView_notLoading_noAnimation() { 390 underTest.displayView( 391 createChipbarInfo( 392 Icon.Resource(R.id.check_box, null), 393 Text.Loaded("text"), 394 endItem = ChipbarEndItem.Error, 395 ) 396 ) 397 398 assertThat(underTest.loadingDetails).isNull() 399 } 400 401 @Test displayView_loadingThenNotLoading_animationStoppednull402 fun displayView_loadingThenNotLoading_animationStopped() { 403 underTest.displayView( 404 createChipbarInfo( 405 Icon.Resource(R.id.check_box, null), 406 Text.Loaded("text"), 407 endItem = ChipbarEndItem.Loading, 408 ) 409 ) 410 411 val animator = underTest.loadingDetails!!.animator 412 var cancelled = false 413 animator.doOnCancel { cancelled = true } 414 415 underTest.displayView( 416 createChipbarInfo( 417 Icon.Resource(R.id.check_box, null), 418 Text.Loaded("text"), 419 endItem = ChipbarEndItem.Button(Text.Loaded("button")) {}, 420 ) 421 ) 422 423 assertThat(cancelled).isTrue() 424 assertThat(underTest.loadingDetails).isNull() 425 } 426 427 @Test displayView_loadingThenHideView_animationStoppednull428 fun displayView_loadingThenHideView_animationStopped() { 429 underTest.displayView( 430 createChipbarInfo( 431 Icon.Resource(R.id.check_box, null), 432 Text.Loaded("text"), 433 endItem = ChipbarEndItem.Loading, 434 ) 435 ) 436 437 val animator = underTest.loadingDetails!!.animator 438 var cancelled = false 439 animator.doOnCancel { cancelled = true } 440 441 underTest.removeView(DEVICE_ID, "TestReason") 442 443 assertThat(cancelled).isTrue() 444 assertThat(underTest.loadingDetails).isNull() 445 } 446 447 @Test displayView_loadingThenNewLoading_animationStaysTheSamenull448 fun displayView_loadingThenNewLoading_animationStaysTheSame() { 449 underTest.displayView( 450 createChipbarInfo( 451 Icon.Resource(R.id.check_box, null), 452 Text.Loaded("text"), 453 endItem = ChipbarEndItem.Loading, 454 ) 455 ) 456 457 val animator = underTest.loadingDetails!!.animator 458 var cancelled = false 459 animator.doOnCancel { cancelled = true } 460 461 underTest.displayView( 462 createChipbarInfo( 463 Icon.Resource(R.id.check_box, null), 464 Text.Loaded("new text"), 465 endItem = ChipbarEndItem.Loading, 466 ) 467 ) 468 469 assertThat(underTest.loadingDetails!!.animator).isEqualTo(animator) 470 assertThat(underTest.loadingDetails!!.animator.isStarted).isTrue() 471 assertThat(cancelled).isFalse() 472 } 473 474 @Test displayView_vibrationEffect_doubleClickEffectWithHardwareFeedbacknull475 fun displayView_vibrationEffect_doubleClickEffectWithHardwareFeedback() { 476 underTest.displayView( 477 createChipbarInfo( 478 Icon.Resource(R.id.check_box, null), 479 Text.Loaded("text"), 480 endItem = null, 481 vibrationEffect = VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), 482 ) 483 ) 484 485 verify(vibratorHelper) 486 .vibrate( 487 any(), 488 any(), 489 eq(VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)), 490 any(), 491 eq(VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)), 492 ) 493 } 494 495 /** Regression test for b/266119467. */ 496 @Test displayView_animationFailure_viewsStillBecomeVisiblenull497 fun displayView_animationFailure_viewsStillBecomeVisible() { 498 chipbarAnimator.allowAnimation = false 499 500 underTest.displayView( 501 createChipbarInfo( 502 Icon.Resource(R.id.check_box, null), 503 Text.Loaded("text"), 504 endItem = ChipbarEndItem.Loading, 505 ) 506 ) 507 508 val view = getChipbarView() 509 assertThat(view.getInnerView().alpha).isEqualTo(1f) 510 assertThat(view.getStartIconView().alpha).isEqualTo(1f) 511 assertThat(view.getLoadingIcon().alpha).isEqualTo(1f) 512 assertThat(view.getChipTextView().alpha).isEqualTo(1f) 513 } 514 515 @Test updateView_viewUpdatednull516 fun updateView_viewUpdated() { 517 // First, display a view 518 val drawable = context.getDrawable(R.drawable.ic_celebration)!! 519 520 underTest.displayView( 521 createChipbarInfo( 522 Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")), 523 Text.Loaded("title text"), 524 endItem = ChipbarEndItem.Loading, 525 ) 526 ) 527 528 val chipbarView = getChipbarView() 529 assertThat(chipbarView.getStartIconView().drawable).isEqualTo(drawable) 530 assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("loadedCD") 531 assertThat(chipbarView.getChipText()).isEqualTo("title text") 532 assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.VISIBLE) 533 assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.GONE) 534 assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) 535 536 // WHEN the view is updated 537 val newDrawable = context.getDrawable(R.drawable.ic_cake)!! 538 underTest.updateView( 539 createChipbarInfo( 540 Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")), 541 Text.Loaded("new title text"), 542 endItem = ChipbarEndItem.Error, 543 ), 544 chipbarView 545 ) 546 547 // THEN we display the new view 548 assertThat(chipbarView.getStartIconView().drawable).isEqualTo(newDrawable) 549 assertThat(chipbarView.getStartIconView().contentDescription).isEqualTo("new CD") 550 assertThat(chipbarView.getChipText()).isEqualTo("new title text") 551 assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE) 552 assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE) 553 assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE) 554 } 555 556 @Test viewUpdates_loggednull557 fun viewUpdates_logged() { 558 val drawable = context.getDrawable(R.drawable.ic_celebration)!! 559 underTest.displayView( 560 createChipbarInfo( 561 Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")), 562 Text.Loaded("title text"), 563 endItem = ChipbarEndItem.Loading, 564 ) 565 ) 566 567 verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("title text"), any()) 568 569 underTest.displayView( 570 createChipbarInfo( 571 Icon.Loaded(drawable, ContentDescription.Loaded("new CD")), 572 Text.Loaded("new title text"), 573 endItem = ChipbarEndItem.Error, 574 ) 575 ) 576 577 verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any()) 578 } 579 580 /** Regression test for b/266209420. */ 581 @Test displayViewThenImmediateRemoval_viewStillRemovednull582 fun displayViewThenImmediateRemoval_viewStillRemoved() { 583 underTest.displayView( 584 createChipbarInfo( 585 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 586 Text.Loaded("title text"), 587 endItem = ChipbarEndItem.Error, 588 ), 589 ) 590 val chipbarView = getChipbarView() 591 592 underTest.removeView(DEVICE_ID, "test reason") 593 594 verify(windowManager).removeView(chipbarView) 595 } 596 597 /** Regression test for b/266209420. */ 598 @Test removeView_animationFailure_viewStillRemovednull599 fun removeView_animationFailure_viewStillRemoved() { 600 chipbarAnimator.allowAnimation = false 601 602 underTest.displayView( 603 createChipbarInfo( 604 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 605 Text.Loaded("title text"), 606 endItem = ChipbarEndItem.Error, 607 ), 608 ) 609 val chipbarView = getChipbarView() 610 611 underTest.removeView(DEVICE_ID, "test reason") 612 613 verify(windowManager).removeView(chipbarView) 614 } 615 616 @Test swipeToDismiss_false_neverListensForGesturenull617 fun swipeToDismiss_false_neverListensForGesture() { 618 underTest.displayView( 619 createChipbarInfo( 620 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 621 Text.Loaded("title text"), 622 endItem = ChipbarEndItem.Loading, 623 allowSwipeToDismiss = false, 624 ) 625 ) 626 627 verify(swipeGestureHandler, never()).addOnGestureDetectedCallback(any(), any()) 628 } 629 630 @Test swipeToDismiss_true_listensForGesturenull631 fun swipeToDismiss_true_listensForGesture() { 632 underTest.displayView( 633 createChipbarInfo( 634 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 635 Text.Loaded("title text"), 636 endItem = ChipbarEndItem.Loading, 637 allowSwipeToDismiss = true, 638 ) 639 ) 640 641 verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), any()) 642 } 643 644 @Test swipeToDismiss_swipeOccurs_viewDismissed_manuallyDismissedLoggednull645 fun swipeToDismiss_swipeOccurs_viewDismissed_manuallyDismissedLogged() { 646 underTest.displayView( 647 createChipbarInfo( 648 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 649 Text.Loaded("title text"), 650 endItem = ChipbarEndItem.Loading, 651 allowSwipeToDismiss = true, 652 ) 653 ) 654 val view = getChipbarView() 655 656 val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>() 657 verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor)) 658 659 callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0)) 660 661 verify(windowManager).removeView(view) 662 assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2) 663 assertThat(uiEventLoggerFake.eventId(1)) 664 .isEqualTo(TemporaryViewUiEvent.TEMPORARY_VIEW_MANUALLY_DISMISSED.id) 665 } 666 667 @Test swipeToDismiss_viewUpdatedToFalse_swipeOccurs_viewNotDismissednull668 fun swipeToDismiss_viewUpdatedToFalse_swipeOccurs_viewNotDismissed() { 669 underTest.displayView( 670 createChipbarInfo( 671 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 672 Text.Loaded("title text"), 673 endItem = ChipbarEndItem.Loading, 674 allowSwipeToDismiss = true, 675 ) 676 ) 677 val view = getChipbarView() 678 val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>() 679 verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor)) 680 681 // only one log for view addition 682 assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) 683 assertThat(uiEventLoggerFake.eventId(0)) 684 .isEqualTo(TemporaryViewUiEvent.TEMPORARY_VIEW_ADDED.id) 685 686 // WHEN the view is updated to not allow swipe-to-dismiss 687 underTest.displayView( 688 createChipbarInfo( 689 Icon.Resource(R.drawable.ic_cake, contentDescription = null), 690 Text.Loaded("title text"), 691 endItem = ChipbarEndItem.Loading, 692 allowSwipeToDismiss = false, 693 ) 694 ) 695 696 // THEN the callback is removed 697 verify(swipeGestureHandler).removeOnGestureDetectedCallback(any()) 698 699 // And WHEN the old callback is invoked 700 callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0)) 701 702 // THEN it is ignored and view isn't removed 703 verify(windowManager, never()).removeView(view) 704 assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) 705 } 706 createChipbarInfonull707 private fun createChipbarInfo( 708 startIcon: Icon, 709 text: Text, 710 endItem: ChipbarEndItem?, 711 vibrationEffect: VibrationEffect? = null, 712 allowSwipeToDismiss: Boolean = false, 713 ): ChipbarInfo { 714 return ChipbarInfo( 715 TintedIcon(startIcon, tint = null), 716 text, 717 endItem, 718 vibrationEffect, 719 allowSwipeToDismiss, 720 windowTitle = WINDOW_TITLE, 721 wakeReason = WAKE_REASON, 722 timeoutMs = TIMEOUT, 723 id = DEVICE_ID, 724 priority = ViewPriority.NORMAL, 725 instanceId = InstanceId.fakeInstanceId(0), 726 ) 727 } 728 getInnerViewnull729 private fun ViewGroup.getInnerView() = this.requireViewById<ViewGroup>(R.id.chipbar_inner) 730 731 private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon) 732 733 private fun ViewGroup.getChipTextView() = this.requireViewById<TextView>(R.id.text) 734 735 private fun ViewGroup.getChipText(): String = this.getChipTextView().text as String 736 737 private fun ViewGroup.getLoadingIcon(): View = this.requireViewById(R.id.loading) 738 739 private fun ViewGroup.getEndButton(): TextView = this.requireViewById(R.id.end_button) 740 741 private fun ViewGroup.getErrorIcon(): View = this.requireViewById(R.id.error) 742 743 private fun getChipbarView(): ViewGroup { 744 val viewCaptor = ArgumentCaptor.forClass(View::class.java) 745 verify(windowManager).addView(viewCaptor.capture(), any()) 746 return viewCaptor.value as ViewGroup 747 } 748 749 /** Test class that lets us disallow animations. */ 750 inner class TestChipbarAnimator : ChipbarAnimator() { 751 var allowAnimation: Boolean = true 752 animateViewInnull753 override fun animateViewIn(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean { 754 if (!allowAnimation) { 755 return false 756 } 757 return super.animateViewIn(innerView, onAnimationEnd) 758 } 759 animateViewOutnull760 override fun animateViewOut(innerView: ViewGroup, onAnimationEnd: Runnable): Boolean { 761 if (!allowAnimation) { 762 return false 763 } 764 return super.animateViewOut(innerView, onAnimationEnd) 765 } 766 } 767 } 768 769 private const val TIMEOUT = 10000 770 private const val WINDOW_TITLE = "Test Chipbar Window Title" 771 private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON" 772 private const val DEVICE_ID = "id" 773