1 /* 2 * Copyright (C) 2017 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.statusbar.notification.stack; 18 19 import static android.view.WindowInsets.Type.ime; 20 21 import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag; 22 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; 23 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; 24 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL; 25 26 import static com.google.common.truth.Truth.assertThat; 27 import static com.google.common.truth.Truth.assertWithMessage; 28 29 import static junit.framework.Assert.assertEquals; 30 import static junit.framework.Assert.assertTrue; 31 32 import static org.junit.Assert.assertFalse; 33 import static org.mockito.ArgumentMatchers.any; 34 import static org.mockito.ArgumentMatchers.anyBoolean; 35 import static org.mockito.ArgumentMatchers.anyFloat; 36 import static org.mockito.ArgumentMatchers.anyInt; 37 import static org.mockito.ArgumentMatchers.eq; 38 import static org.mockito.Mockito.clearInvocations; 39 import static org.mockito.Mockito.doAnswer; 40 import static org.mockito.Mockito.doNothing; 41 import static org.mockito.Mockito.doReturn; 42 import static org.mockito.Mockito.mock; 43 import static org.mockito.Mockito.never; 44 import static org.mockito.Mockito.spy; 45 import static org.mockito.Mockito.verify; 46 import static org.mockito.Mockito.when; 47 48 import android.annotation.DimenRes; 49 import android.graphics.Insets; 50 import android.graphics.Rect; 51 import android.os.SystemClock; 52 import android.platform.test.annotations.DisableFlags; 53 import android.platform.test.annotations.EnableFlags; 54 import android.platform.test.flag.junit.FlagsParameterization; 55 import android.testing.TestableLooper; 56 import android.testing.TestableResources; 57 import android.util.MathUtils; 58 import android.view.MotionEvent; 59 import android.view.View; 60 import android.view.ViewGroup; 61 import android.view.WindowInsets; 62 import android.view.WindowInsetsAnimation; 63 64 import androidx.test.filters.SmallTest; 65 66 import com.android.keyguard.BouncerPanelExpansionCalculator; 67 import com.android.systemui.ExpandHelper; 68 import com.android.systemui.SysuiTestCase; 69 import com.android.systemui.dump.DumpManager; 70 import com.android.systemui.flags.BrokenWithSceneContainer; 71 import com.android.systemui.flags.DisableSceneContainer; 72 import com.android.systemui.flags.EnableSceneContainer; 73 import com.android.systemui.flags.FakeFeatureFlags; 74 import com.android.systemui.flags.FeatureFlags; 75 import com.android.systemui.flags.Flags; 76 import com.android.systemui.qs.flags.NewQsUI; 77 import com.android.systemui.qs.flags.QSComposeFragment; 78 import com.android.systemui.res.R; 79 import com.android.systemui.shade.QSHeaderBoundsProvider; 80 import com.android.systemui.shade.ShadeController; 81 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; 82 import com.android.systemui.statusbar.NotificationShelf; 83 import com.android.systemui.statusbar.StatusBarState; 84 import com.android.systemui.statusbar.SysuiStatusBarStateController; 85 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; 86 import com.android.systemui.statusbar.notification.collection.EntryAdapter; 87 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 88 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; 89 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; 90 import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository; 91 import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix; 92 import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView; 93 import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; 94 import com.android.systemui.statusbar.notification.headsup.AvalancheController; 95 import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; 96 import com.android.systemui.statusbar.notification.headsup.NotificationsHunSharedAnimationValues; 97 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 98 import com.android.systemui.statusbar.notification.row.ExpandableView; 99 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; 100 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds; 101 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape; 102 import com.android.systemui.statusbar.phone.KeyguardBypassController; 103 import com.android.systemui.statusbar.phone.ScreenOffAnimationController; 104 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 105 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; 106 import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor; 107 108 import kotlin.Unit; 109 110 import org.junit.Assert; 111 import org.junit.Before; 112 import org.junit.Rule; 113 import org.junit.Test; 114 import org.junit.runner.RunWith; 115 import org.mockito.ArgumentCaptor; 116 import org.mockito.Mock; 117 import org.mockito.junit.MockitoJUnit; 118 import org.mockito.junit.MockitoRule; 119 120 import java.util.ArrayList; 121 import java.util.List; 122 import java.util.function.Consumer; 123 124 import platform.test.runner.parameterized.ParameterizedAndroidJunit4; 125 import platform.test.runner.parameterized.Parameters; 126 127 /** 128 * Tests for {@link NotificationStackScrollLayout}. 129 */ 130 @SmallTest 131 @RunWith(ParameterizedAndroidJunit4.class) 132 @TestableLooper.RunWithLooper 133 public class NotificationStackScrollLayoutTest extends SysuiTestCase { 134 135 @Parameters(name = "{0}") getParams()136 public static List<FlagsParameterization> getParams() { 137 return parameterizeSceneContainerFlag(); 138 } 139 140 private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); 141 private NotificationStackScrollLayout mStackScroller; // Normally test this 142 private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below 143 private AmbientState mAmbientState; 144 private TestableResources mTestableResources; 145 @Rule public MockitoRule mockito = MockitoJUnit.rule(); 146 @Mock private SysuiStatusBarStateController mBarState; 147 @Mock private GroupMembershipManager mGroupMembershipManger; 148 @Mock private GroupExpansionManager mGroupExpansionManager; 149 @Mock private DumpManager mDumpManager; 150 @Mock private ExpandHelper mExpandHelper; 151 @Mock private EmptyShadeView mEmptyShadeView; 152 @Mock private NotificationRoundnessManager mNotificationRoundnessManager; 153 @Mock private KeyguardBypassController mBypassController; 154 @Mock private NotificationSectionsManager mNotificationSectionsManager; 155 @Mock private NotificationSection mNotificationSection; 156 @Mock private NotificationSwipeHelper mNotificationSwipeHelper; 157 @Mock private NotificationStackScrollLayoutController mStackScrollLayoutController; 158 @Mock private ScreenOffAnimationController mScreenOffAnimationController; 159 @Mock private NotificationShelf mNotificationShelf; 160 @Mock private WallpaperInteractor mWallpaperInteractor; 161 @Mock private NotificationStackSizeCalculator mStackSizeCalculator; 162 @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 163 @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator; 164 @Mock private AvalancheController mAvalancheController; 165 @Mock private HeadsUpRepository mHeadsUpRepository; 166 NotificationStackScrollLayoutTest(FlagsParameterization flags)167 public NotificationStackScrollLayoutTest(FlagsParameterization flags) { 168 super(); 169 mSetFlagsRule.setFlagsParameterization(flags); 170 } 171 172 @Before setUp()173 public void setUp() throws Exception { 174 allowTestableLooperAsMainThread(); 175 mTestableResources = mContext.getOrCreateTestableResources(); 176 177 // Interact with real instance of AmbientState. 178 mAmbientState = spy(new AmbientState( 179 mContext, 180 mDumpManager, 181 mNotificationSectionsManager, 182 mBypassController, 183 mStatusBarKeyguardViewManager, 184 mLargeScreenShadeInterpolator, 185 mHeadsUpRepository, 186 mAvalancheController 187 )); 188 189 // Register the debug flags we use 190 assertFalse(Flags.NSSL_DEBUG_LINES.getDefault()); 191 assertFalse(Flags.NSSL_DEBUG_REMOVE_ANIMATION.getDefault()); 192 mFeatureFlags.set(Flags.NSSL_DEBUG_LINES, false); 193 mFeatureFlags.set(Flags.NSSL_DEBUG_REMOVE_ANIMATION, false); 194 mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); 195 196 // Inject dependencies before initializing the layout 197 mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); 198 mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState); 199 mDependency.injectMockDependency(ShadeController.class); 200 mDependency.injectTestDependency( 201 NotificationSectionsManager.class, mNotificationSectionsManager); 202 mDependency.injectTestDependency(GroupMembershipManager.class, mGroupMembershipManger); 203 mDependency.injectTestDependency(GroupExpansionManager.class, mGroupExpansionManager); 204 mDependency.injectTestDependency(AmbientState.class, mAmbientState); 205 mDependency.injectTestDependency(NotificationShelf.class, mNotificationShelf); 206 mDependency.injectTestDependency( 207 ScreenOffAnimationController.class, mScreenOffAnimationController); 208 209 when(mNotificationSectionsManager.createSectionsForBuckets()).thenReturn( 210 new NotificationSection[]{ 211 mNotificationSection 212 }); 213 214 // The actual class under test. You may need to work with this class directly when 215 // testing anonymous class members of mStackScroller, like mMenuEventListener, 216 // which refer to members of NotificationStackScrollLayout. The spy 217 // holds a copy of the CUT's instances of these KeyguardBypassController, so they still 218 // refer to the CUT's member variables, not the spy's member variables. 219 mStackScrollerInternal = new NotificationStackScrollLayout(getContext(), null); 220 mStackScrollerInternal.initView(getContext(), mNotificationSwipeHelper, 221 mStackSizeCalculator); 222 mStackScroller = spy(mStackScrollerInternal); 223 mStackScroller.setResetUserExpandedStatesRunnable(() -> {}); 224 mStackScroller.setEmptyShadeView(mEmptyShadeView); 225 when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true); 226 when(mStackScrollLayoutController.getNotificationRoundnessManager()) 227 .thenReturn(mNotificationRoundnessManager); 228 mStackScroller.setController(mStackScrollLayoutController); 229 mStackScroller.setShelf(mNotificationShelf); 230 mStackScroller.setWallpaperInteractor(mWallpaperInteractor); 231 when(mStackScroller.getExpandHelper()).thenReturn(mExpandHelper); 232 233 doNothing().when(mGroupExpansionManager).collapseGroups(); 234 doNothing().when(mExpandHelper).cancelImmediately(); 235 doNothing().when(mNotificationShelf).setAnimationsEnabled(anyBoolean()); 236 } 237 238 @Test 239 @DisableSceneContainer // TODO(b/332574413) cover stack bounds integration with tests testUpdateStackHeight_qsExpansionGreaterThanZero()240 public void testUpdateStackHeight_qsExpansionGreaterThanZero() { 241 final float expansionFraction = 0.2f; 242 final float overExpansion = 50f; 243 244 mStackScroller.setQsExpansionFraction(1f); 245 mAmbientState.setExpansionFraction(expansionFraction); 246 mAmbientState.setOverExpansion(overExpansion); 247 when(mAmbientState.isBouncerInTransit()).thenReturn(true); 248 249 250 mStackScroller.setExpandedHeight(100f); 251 252 float expected = MathUtils.lerp(0, overExpansion, 253 BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansionFraction)); 254 assertThat(mAmbientState.getStackY()).isEqualTo(expected); 255 } 256 257 @Test 258 @EnableSceneContainer testIntrinsicStackHeight()259 public void testIntrinsicStackHeight() { 260 int stackHeight = 300; 261 when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat())) 262 .thenReturn((float) stackHeight); 263 264 mStackScroller.updateIntrinsicStackHeight(); 265 266 assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(stackHeight); 267 } 268 269 @Test 270 @DisableSceneContainer // TODO(b/312473478): address disabled test testUpdateStackHeight_qsExpansionZero()271 public void testUpdateStackHeight_qsExpansionZero() { 272 final float expansionFraction = 0.2f; 273 final float overExpansion = 50f; 274 275 mStackScroller.setQsExpansionFraction(0f); 276 mAmbientState.setExpansionFraction(expansionFraction); 277 mAmbientState.setOverExpansion(overExpansion); 278 when(mAmbientState.isBouncerInTransit()).thenReturn(true); 279 280 mStackScroller.setExpandedHeight(100f); 281 282 float expected = MathUtils.lerp(0, overExpansion, expansionFraction); 283 assertThat(mAmbientState.getStackY()).isEqualTo(expected); 284 } 285 286 @Test testUpdateStackHeight_withExpansionAmount_whenDozeNotChanging()287 public void testUpdateStackHeight_withExpansionAmount_whenDozeNotChanging() { 288 final float endHeight = 8f; 289 final float expansionFraction = 0.5f; 290 final float expected = MathUtils.lerp( 291 endHeight * StackScrollAlgorithm.START_FRACTION, 292 endHeight, expansionFraction); 293 294 mStackScroller.updateInterpolatedStackHeight(endHeight, expansionFraction); 295 assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(expected); 296 } 297 298 @Test 299 @EnableSceneContainer updateStackEndHeightAndStackHeight_shadeFullyExpanded_withSceneContainer()300 public void updateStackEndHeightAndStackHeight_shadeFullyExpanded_withSceneContainer() { 301 final float stackTop = 200f; 302 final float stackCutoff = 1000f; 303 final float stackEndHeight = stackCutoff - stackTop; 304 mAmbientState.setStackTop(stackTop); 305 mAmbientState.setStackCutoff(stackCutoff); 306 mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); 307 clearInvocations(mAmbientState); 308 309 // WHEN shade is fully expanded 310 mStackScroller.updateStackEndHeightAndStackHeight(/* fraction = */ 1.0f); 311 312 // THEN stackHeight and stackEndHeight are the same 313 verify(mAmbientState).setStackEndHeight(stackEndHeight); 314 verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight); 315 } 316 317 @Test 318 @EnableSceneContainer updateStackEndHeightAndStackHeight_shadeExpanding_withSceneContainer()319 public void updateStackEndHeightAndStackHeight_shadeExpanding_withSceneContainer() { 320 final float stackTop = 200f; 321 final float stackCutoff = 1000f; 322 final float stackEndHeight = stackCutoff - stackTop; 323 mAmbientState.setStackTop(stackTop); 324 mAmbientState.setStackCutoff(stackCutoff); 325 mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); 326 clearInvocations(mAmbientState); 327 328 // WHEN shade is expanding 329 final float expansionFraction = 0.5f; 330 mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction); 331 332 // THEN stackHeight is changed by the expansion frac 333 verify(mAmbientState).setStackEndHeight(stackEndHeight); 334 verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight * 0.75f); 335 } 336 337 @Test 338 @EnableSceneContainer updateStackEndHeightAndStackHeight_shadeOverscrolledToTop_withSceneContainer()339 public void updateStackEndHeightAndStackHeight_shadeOverscrolledToTop_withSceneContainer() { 340 // GIVEN stack scrolled over the top, stack top is negative 341 final float stackTop = -2000f; 342 final float stackCutoff = 1000f; 343 final float stackEndHeight = stackCutoff - stackTop; 344 mAmbientState.setStackTop(stackTop); 345 mAmbientState.setStackCutoff(stackCutoff); 346 mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); 347 clearInvocations(mAmbientState); 348 349 // WHEN stack is updated 350 mStackScroller.updateStackEndHeightAndStackHeight(/* fraction = */ 1.0f); 351 352 // THEN stackHeight is measured from the stack top 353 verify(mAmbientState).setStackEndHeight(stackEndHeight); 354 verify(mAmbientState).setInterpolatedStackHeight(stackEndHeight); 355 } 356 357 @Test 358 @EnableSceneContainer updateStackCutoff_updatesStackEndHeight()359 public void updateStackCutoff_updatesStackEndHeight() { 360 // GIVEN shade is fully open 361 final float stackTop = 200f; 362 final float stackCutoff = 1000f; 363 final float stackHeight = stackCutoff - stackTop; 364 mAmbientState.setStackTop(stackTop); 365 mAmbientState.setStackCutoff(stackCutoff); 366 mAmbientState.setStatusBarState(StatusBarState.SHADE); 367 mStackScroller.setMaxDisplayedNotifications(-1); // no limit on the shade 368 mStackScroller.setExpandFraction(1f); // shade is fully expanded 369 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight); 370 assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(stackHeight); 371 372 // WHEN stackCutoff changes 373 final float newStackCutoff = 800; 374 mStackScroller.setStackCutoff(newStackCutoff); 375 376 // THEN stackEndHeight is updated 377 final float newStackHeight = newStackCutoff - stackTop; 378 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(newStackHeight); 379 assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(newStackHeight); 380 } 381 382 @Test 383 @EnableSceneContainer updateStackEndHeightAndStackHeight_maxNotificationsSet_withSceneContainer()384 public void updateStackEndHeightAndStackHeight_maxNotificationsSet_withSceneContainer() { 385 float stackHeight = 300f; 386 when(mStackSizeCalculator.computeHeight(eq(mStackScroller), anyInt(), anyFloat())) 387 .thenReturn(stackHeight); 388 mStackScroller.setMaxDisplayedNotifications(3); // any non-zero amount 389 390 clearInvocations(mAmbientState); 391 mStackScroller.updateStackEndHeightAndStackHeight(1f); 392 393 verify(mAmbientState).setInterpolatedStackHeight(eq(300f)); 394 } 395 396 @Test 397 @DisableSceneContainer updateStackEndHeightAndStackHeight_onlyUpdatesStackHeightDuringSwipeUp()398 public void updateStackEndHeightAndStackHeight_onlyUpdatesStackHeightDuringSwipeUp() { 399 final float expansionFraction = 0.5f; 400 mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); 401 mAmbientState.setSwipingUp(true); 402 403 // Validate that when the gesture is in progress, we update only the stackHeight 404 clearInvocations(mAmbientState); 405 mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction); 406 verify(mAmbientState, never()).setStackEndHeight(anyFloat()); 407 verify(mAmbientState).setInterpolatedStackHeight(anyFloat()); 408 } 409 410 @Test 411 @DisableSceneContainer setPanelFlinging_updatesStackEndHeightOnlyOnFinish()412 public void setPanelFlinging_updatesStackEndHeightOnlyOnFinish() { 413 final float expansionFraction = 0.5f; 414 mAmbientState.setStatusBarState(StatusBarState.KEYGUARD); 415 mAmbientState.setSwipingUp(true); 416 mStackScroller.setPanelFlinging(true); 417 mAmbientState.setSwipingUp(false); 418 419 // Validate that when the animation is running, we update only the stackHeight 420 clearInvocations(mAmbientState); 421 mStackScroller.updateStackEndHeightAndStackHeight(expansionFraction); 422 verify(mAmbientState, never()).setStackEndHeight(anyFloat()); 423 verify(mAmbientState).setInterpolatedStackHeight(anyFloat()); 424 425 // Validate that when the animation ends the stackEndHeight is recalculated immediately 426 clearInvocations(mAmbientState); 427 mStackScroller.setPanelFlinging(false); 428 verify(mAmbientState).setFlinging(eq(false)); 429 verify(mAmbientState).setStackEndHeight(anyFloat()); 430 verify(mAmbientState).setInterpolatedStackHeight(anyFloat()); 431 } 432 433 @Test 434 @DisableFlags(ModesEmptyShadeFix.FLAG_NAME) updateEmptyView_dndSuppressing()435 public void updateEmptyView_dndSuppressing() { 436 when(mEmptyShadeView.willBeGone()).thenReturn(true); 437 438 mStackScroller.updateEmptyShadeView(/* visible = */ true, 439 /* areNotificationsHiddenInShade = */ true, 440 /* hasFilteredOutSeenNotifications = */ false); 441 442 verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text); 443 } 444 445 @Test 446 @DisableFlags(ModesEmptyShadeFix.FLAG_NAME) updateEmptyView_dndNotSuppressing()447 public void updateEmptyView_dndNotSuppressing() { 448 mStackScroller.setEmptyShadeView(mEmptyShadeView); 449 when(mEmptyShadeView.willBeGone()).thenReturn(true); 450 451 mStackScroller.updateEmptyShadeView(/* visible = */ true, 452 /* areNotificationsHiddenInShade = */ false, 453 /* hasFilteredOutSeenNotifications = */ false); 454 455 verify(mEmptyShadeView).setText(R.string.empty_shade_text); 456 } 457 458 @Test 459 @DisableFlags(ModesEmptyShadeFix.FLAG_NAME) updateEmptyView_noNotificationsToDndSuppressing()460 public void updateEmptyView_noNotificationsToDndSuppressing() { 461 mStackScroller.setEmptyShadeView(mEmptyShadeView); 462 when(mEmptyShadeView.willBeGone()).thenReturn(true); 463 mStackScroller.updateEmptyShadeView(/* visible = */ true, 464 /* areNotificationsHiddenInShade = */ false, 465 /* hasFilteredOutSeenNotifications = */ false); 466 verify(mEmptyShadeView).setText(R.string.empty_shade_text); 467 468 mStackScroller.updateEmptyShadeView(/* visible = */ true, 469 /* areNotificationsHiddenInShade = */ true, 470 /* hasFilteredOutSeenNotifications = */ false); 471 verify(mEmptyShadeView).setText(R.string.dnd_suppressing_shade_text); 472 } 473 474 @Test 475 @EnableSceneContainer setExpandFraction_fullyCollapsed()476 public void setExpandFraction_fullyCollapsed() { 477 // Given: NSSL has a height 478 when(mStackScroller.getHeight()).thenReturn(1200); 479 // And: stack bounds are set 480 float expandFraction = 0.0f; 481 float stackTop = 100; 482 float stackCutoff = 1100; 483 float stackHeight = stackCutoff - stackTop; 484 mStackScroller.setStackTop(stackTop); 485 mStackScroller.setStackCutoff(stackCutoff); 486 487 // When: panel is fully collapsed 488 mStackScroller.setExpandFraction(expandFraction); 489 490 // Then 491 assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction); 492 assertThat(mAmbientState.isExpansionChanging()).isFalse(); 493 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight); 494 assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo( 495 stackHeight * StackScrollAlgorithm.START_FRACTION); 496 assertThat(mAmbientState.isShadeExpanded()).isFalse(); 497 assertThat(mStackScroller.getExpandedHeight()).isZero(); 498 } 499 500 @Test 501 @EnableSceneContainer setExpandFraction_expanding()502 public void setExpandFraction_expanding() { 503 // Given: NSSL has a height 504 when(mStackScroller.getHeight()).thenReturn(1200); 505 // And: stack bounds are set 506 float expandFraction = 0.6f; 507 float stackTop = 100; 508 float stackCutoff = 1100; 509 float stackHeight = stackCutoff - stackTop; 510 mStackScroller.setStackTop(stackTop); 511 mStackScroller.setStackCutoff(stackCutoff); 512 513 // When: panel is expanding 514 mStackScroller.setExpandFraction(expandFraction); 515 516 // Then 517 assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction); 518 assertThat(mAmbientState.isExpansionChanging()).isTrue(); 519 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight); 520 assertThat(mAmbientState.getInterpolatedStackHeight()).isGreaterThan( 521 stackHeight * StackScrollAlgorithm.START_FRACTION); 522 assertThat(mAmbientState.getInterpolatedStackHeight()).isLessThan(stackHeight); 523 assertThat(mStackScroller.getExpandedHeight()).isGreaterThan(0f); 524 assertThat(mAmbientState.isShadeExpanded()).isTrue(); 525 } 526 527 @Test 528 @EnableSceneContainer setExpandFraction_fullyExpanded()529 public void setExpandFraction_fullyExpanded() { 530 // Given: NSSL has a height 531 int viewHeight = 1200; 532 when(mStackScroller.getHeight()).thenReturn(viewHeight); 533 // And: stack bounds are set 534 float expandFraction = 1.0f; 535 float stackTop = 100; 536 float stackCutoff = 1100; 537 float stackHeight = stackCutoff - stackTop; 538 mStackScroller.setStackTop(stackTop); 539 mStackScroller.setStackCutoff(stackCutoff); 540 541 // When: panel is fully expanded 542 mStackScroller.setExpandFraction(expandFraction); 543 544 // Then 545 assertThat(mAmbientState.getExpansionFraction()).isEqualTo(expandFraction); 546 assertThat(mAmbientState.isExpansionChanging()).isFalse(); 547 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeight); 548 assertThat(mAmbientState.getInterpolatedStackHeight()).isEqualTo(stackHeight); 549 assertThat(mStackScroller.getExpandedHeight()).isEqualTo(viewHeight); 550 assertThat(mAmbientState.isShadeExpanded()).isTrue(); 551 } 552 553 @Test 554 @DisableSceneContainer testSetExpandedHeight_listenerReceivedCallbacks()555 public void testSetExpandedHeight_listenerReceivedCallbacks() { 556 final float expectedHeight = 0f; 557 558 mStackScroller.addOnExpandedHeightChangedListener((height, appear) -> { 559 Assert.assertEquals(expectedHeight, height, 0); 560 }); 561 mStackScroller.setExpandedHeight(expectedHeight); 562 } 563 564 @Test testAppearFractionCalculationIsNotNegativeWhenShelfBecomesSmaller()565 public void testAppearFractionCalculationIsNotNegativeWhenShelfBecomesSmaller() { 566 // this situation might occur if status bar height is defined in pixels while shelf height 567 // in dp and screen density changes - appear start position 568 // (calculated in NSSL#getMinExpansionHeight) that is adjusting for status bar might 569 // increase and become bigger that end position, which should be prevented 570 571 // appear start position 572 when(mNotificationShelf.getIntrinsicHeight()).thenReturn(80); 573 mStackScroller.mStatusBarHeight = 100; 574 // appear end position 575 when(mEmptyShadeView.getHeight()).thenReturn(90); 576 577 assertThat(mStackScroller.calculateAppearFraction(100)).isAtLeast(0); 578 } 579 580 @Test 581 @DisableSceneContainer testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight()582 public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() { 583 mTestableResources 584 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true); 585 final int[] expectedStackHeight = {0}; 586 587 mStackScroller.addOnExpandedHeightChangedListener((expandedHeight, appear) -> { 588 assertWithMessage("Given shade enabled: %s", 589 true) 590 .that(mStackScroller.getHeight()) 591 .isEqualTo(expectedStackHeight[0]); 592 }); 593 594 mTestableResources 595 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false); 596 expectedStackHeight[0] = 0; 597 mStackScroller.setExpandedHeight(100f); 598 599 mTestableResources 600 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true); 601 expectedStackHeight[0] = 100; 602 mStackScroller.setExpandedHeight(100f); 603 } 604 605 @Test testFooterPosition_atEnd()606 public void testFooterPosition_atEnd() { 607 // add footer 608 FooterView view = mock(FooterView.class); 609 mStackScroller.setFooterView(view); 610 611 // add notification 612 ExpandableNotificationRow row = createClearableRow(); 613 mStackScroller.addContainerView(row); 614 615 mStackScroller.onUpdateRowStates(); 616 617 // Expecting the footer to be the last child 618 int expected = mStackScroller.getChildCount() - 1; 619 verify(mStackScroller).changeViewPosition(any(FooterView.class), eq(expected)); 620 } 621 622 @Test 623 @DisableFlags(ModesEmptyShadeFix.FLAG_NAME) testReInflatesEmptyShadeView()624 public void testReInflatesEmptyShadeView() { 625 when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text); 626 clearInvocations(mStackScroller); 627 mStackScroller.reinflateViews(); 628 verify(mStackScroller, never()).setFooterView(any()); 629 verify(mStackScroller).setEmptyShadeView(any()); 630 } 631 632 @Test testSetIsBeingDraggedResetsExposedMenu()633 public void testSetIsBeingDraggedResetsExposedMenu() { 634 mStackScroller.setIsBeingDragged(true); 635 verify(mNotificationSwipeHelper).resetExposedMenuView(true, true); 636 } 637 638 @Test testPanelTrackingStartResetsExposedMenu()639 public void testPanelTrackingStartResetsExposedMenu() { 640 mStackScroller.onPanelTrackingStarted(); 641 verify(mNotificationSwipeHelper).resetExposedMenuView(true, true); 642 } 643 644 @Test testDarkModeResetsExposedMenu()645 public void testDarkModeResetsExposedMenu() { 646 mStackScroller.setHideAmount(0.1f, 0.1f); 647 verify(mNotificationSwipeHelper).resetExposedMenuView(true, true); 648 } 649 650 @Test testClearNotifications_All()651 public void testClearNotifications_All() { 652 final int[] numCalls = {0}; 653 final int[] selected = {-1}; 654 mStackScroller.setClearAllListener(selectedRows -> { 655 numCalls[0]++; 656 selected[0] = selectedRows; 657 }); 658 659 mStackScroller.clearNotifications(ROWS_ALL, 660 /* closeShade = */ true, 661 /* hideSilentSection = */ true); 662 assertEquals(1, numCalls[0]); 663 assertEquals(ROWS_ALL, selected[0]); 664 } 665 666 @Test testClearNotifications_Gentle()667 public void testClearNotifications_Gentle() { 668 final int[] numCalls = {0}; 669 final int[] selected = {-1}; 670 mStackScroller.setClearAllListener(selectedRows -> { 671 numCalls[0]++; 672 selected[0] = selectedRows; 673 }); 674 675 mStackScroller.clearNotifications(NotificationStackScrollLayout.ROWS_GENTLE, 676 /* closeShade = */ false, 677 /* hideSilentSection = */ true); 678 assertEquals(1, numCalls[0]); 679 assertEquals(ROWS_GENTLE, selected[0]); 680 } 681 682 @Test testClearNotifications_clearAllInProgress()683 public void testClearNotifications_clearAllInProgress() { 684 ExpandableNotificationRow row = createClearableRow(); 685 when(row.hasFinishedInitialization()).thenReturn(true); 686 doReturn(true).when(mStackScroller).isVisible(row); 687 mStackScroller.addContainerView(row); 688 689 mStackScroller.clearNotifications(ROWS_ALL, 690 /* closeShade = */ false, 691 /* hideSilentSection = */ false); 692 693 assertClearAllInProgress(true); 694 verify(mNotificationRoundnessManager).setClearAllInProgress(true); 695 } 696 697 @Test testOnChildAnimationFinished_resetsClearAllInProgress()698 public void testOnChildAnimationFinished_resetsClearAllInProgress() { 699 mStackScroller.setClearAllInProgress(true); 700 701 mStackScroller.onChildAnimationFinished(); 702 703 assertClearAllInProgress(false); 704 verify(mNotificationRoundnessManager).setClearAllInProgress(false); 705 } 706 707 @Test testShadeCollapsed_resetsClearAllInProgress()708 public void testShadeCollapsed_resetsClearAllInProgress() { 709 mStackScroller.setClearAllInProgress(true); 710 711 mStackScroller.setIsExpanded(false); 712 713 assertClearAllInProgress(false); 714 verify(mNotificationRoundnessManager).setClearAllInProgress(false); 715 } 716 717 @Test testShadeExpanded_doesntChangeClearAllInProgress()718 public void testShadeExpanded_doesntChangeClearAllInProgress() { 719 mStackScroller.setClearAllInProgress(true); 720 clearInvocations(mNotificationRoundnessManager); 721 722 mStackScroller.setIsExpanded(true); 723 724 assertClearAllInProgress(true); 725 verify(mNotificationRoundnessManager, never()).setClearAllInProgress(anyBoolean()); 726 } 727 728 @Test testAddNotificationUpdatesSpeedBumpIndex()729 public void testAddNotificationUpdatesSpeedBumpIndex() { 730 // initial state calculated == 0 731 assertEquals(0, mStackScroller.getSpeedBumpIndex()); 732 733 // add notification that's before the speed bump 734 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 735 NotificationEntry entry = mock(NotificationEntry.class); 736 when(row.getEntry()).thenReturn(entry); 737 when(row.getEntryLegacy()).thenReturn(entry); 738 when(entry.isAmbient()).thenReturn(false); 739 EntryAdapter entryAdapter = mock(EntryAdapter.class); 740 when(entryAdapter.isAmbient()).thenReturn(false); 741 when(row.getEntryAdapter()).thenReturn(entryAdapter); 742 mStackScroller.addContainerView(row); 743 744 // speed bump = 1 745 assertEquals(1, mStackScroller.getSpeedBumpIndex()); 746 } 747 748 @Test testAddAmbientNotificationNoSpeedBumpUpdate()749 public void testAddAmbientNotificationNoSpeedBumpUpdate() { 750 // initial state calculated == 0 751 assertEquals(0, mStackScroller.getSpeedBumpIndex()); 752 753 // add notification that's after the speed bump 754 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 755 NotificationEntry entry = mock(NotificationEntry.class); 756 when(row.getEntry()).thenReturn(entry); 757 when(row.getEntryLegacy()).thenReturn(entry); 758 when(entry.isAmbient()).thenReturn(true); 759 EntryAdapter entryAdapter = mock(EntryAdapter.class); 760 when(entryAdapter.isAmbient()).thenReturn(true); 761 when(row.getEntryAdapter()).thenReturn(entryAdapter); 762 mStackScroller.addContainerView(row); 763 764 // speed bump is set to 0 765 assertEquals(0, mStackScroller.getSpeedBumpIndex()); 766 } 767 768 @Test testRemoveNotificationUpdatesSpeedBump()769 public void testRemoveNotificationUpdatesSpeedBump() { 770 // initial state calculated == 0 771 assertEquals(0, mStackScroller.getSpeedBumpIndex()); 772 773 // add 3 notification that are after the speed bump 774 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 775 NotificationEntry entry = mock(NotificationEntry.class); 776 when(row.getEntry()).thenReturn(entry); 777 when(row.getEntryLegacy()).thenReturn(entry); 778 when(entry.isAmbient()).thenReturn(false); 779 EntryAdapter entryAdapter = mock(EntryAdapter.class); 780 when(entryAdapter.isAmbient()).thenReturn(false); 781 when(row.getEntryAdapter()).thenReturn(entryAdapter); 782 mStackScroller.addContainerView(row); 783 784 // speed bump is 1 785 assertEquals(1, mStackScroller.getSpeedBumpIndex()); 786 787 // remove the notification that was before the speed bump 788 mStackScroller.removeContainerView(row); 789 790 // speed bump is now 0 791 assertEquals(0, mStackScroller.getSpeedBumpIndex()); 792 } 793 794 @Test 795 @DisableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME}) 796 @DisableSceneContainer testInsideQSHeader_noOffset()797 public void testInsideQSHeader_noOffset() { 798 ViewGroup qsHeader = mock(ViewGroup.class); 799 Rect boundsOnScreen = new Rect(0, 0, 1000, 1000); 800 mockBoundsOnScreen(qsHeader, boundsOnScreen); 801 802 mStackScroller.setQsHeader(qsHeader); 803 mStackScroller.setLeftTopRightBottom(0, 0, 2000, 2000); 804 805 MotionEvent event1 = transformEventForView(createMotionEvent(100f, 100f), mStackScroller); 806 assertTrue(mStackScroller.isInsideQsHeader(event1)); 807 808 MotionEvent event2 = transformEventForView(createMotionEvent(1100f, 100f), mStackScroller); 809 assertFalse(mStackScroller.isInsideQsHeader(event2)); 810 } 811 812 @Test 813 @DisableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME}) 814 @DisableSceneContainer testInsideQSHeader_Offset()815 public void testInsideQSHeader_Offset() { 816 ViewGroup qsHeader = mock(ViewGroup.class); 817 Rect boundsOnScreen = new Rect(100, 100, 1000, 1000); 818 mockBoundsOnScreen(qsHeader, boundsOnScreen); 819 820 mStackScroller.setQsHeader(qsHeader); 821 mStackScroller.setLeftTopRightBottom(200, 200, 2000, 2000); 822 823 MotionEvent event1 = transformEventForView(createMotionEvent(50f, 50f), mStackScroller); 824 assertFalse(mStackScroller.isInsideQsHeader(event1)); 825 826 MotionEvent event2 = transformEventForView(createMotionEvent(150f, 150f), mStackScroller); 827 assertFalse(mStackScroller.isInsideQsHeader(event2)); 828 829 MotionEvent event3 = transformEventForView(createMotionEvent(250f, 250f), mStackScroller); 830 assertTrue(mStackScroller.isInsideQsHeader(event3)); 831 } 832 833 @Test 834 @EnableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME}) 835 @DisableSceneContainer testInsideQSHeader_noOffset_qsCompose()836 public void testInsideQSHeader_noOffset_qsCompose() { 837 ViewGroup qsHeader = mock(ViewGroup.class); 838 Rect boundsOnScreen = new Rect(0, 0, 1000, 1000); 839 mockBoundsOnScreen(qsHeader, boundsOnScreen); 840 841 QSHeaderBoundsProvider provider = new QSHeaderBoundsProvider( 842 () -> 0, 843 boundsOnScreen::height, 844 rect -> { 845 qsHeader.getBoundsOnScreen(rect); 846 return Unit.INSTANCE; 847 } 848 ); 849 850 mStackScroller.setQsHeaderBoundsProvider(provider); 851 mStackScroller.setLeftTopRightBottom(0, 0, 2000, 2000); 852 853 MotionEvent event1 = transformEventForView(createMotionEvent(100f, 100f), mStackScroller); 854 assertTrue(mStackScroller.isInsideQsHeader(event1)); 855 856 MotionEvent event2 = transformEventForView(createMotionEvent(1100f, 100f), mStackScroller); 857 assertFalse(mStackScroller.isInsideQsHeader(event2)); 858 } 859 860 @Test 861 @EnableFlags({QSComposeFragment.FLAG_NAME, NewQsUI.FLAG_NAME}) 862 @DisableSceneContainer testInsideQSHeader_Offset_qsCompose()863 public void testInsideQSHeader_Offset_qsCompose() { 864 ViewGroup qsHeader = mock(ViewGroup.class); 865 Rect boundsOnScreen = new Rect(100, 100, 1000, 1000); 866 mockBoundsOnScreen(qsHeader, boundsOnScreen); 867 868 QSHeaderBoundsProvider provider = new QSHeaderBoundsProvider( 869 () -> 0, 870 boundsOnScreen::height, 871 rect -> { 872 qsHeader.getBoundsOnScreen(rect); 873 return Unit.INSTANCE; 874 } 875 ); 876 877 mStackScroller.setQsHeaderBoundsProvider(provider); 878 mStackScroller.setLeftTopRightBottom(200, 200, 2000, 2000); 879 880 MotionEvent event1 = transformEventForView(createMotionEvent(50f, 50f), mStackScroller); 881 assertFalse(mStackScroller.isInsideQsHeader(event1)); 882 883 MotionEvent event2 = transformEventForView(createMotionEvent(150f, 150f), mStackScroller); 884 assertFalse(mStackScroller.isInsideQsHeader(event2)); 885 886 MotionEvent event3 = transformEventForView(createMotionEvent(250f, 250f), mStackScroller); 887 assertTrue(mStackScroller.isInsideQsHeader(event3)); 888 } 889 890 @Test 891 @EnableSceneContainer testIsInsideScrollableRegion_noScrim()892 public void testIsInsideScrollableRegion_noScrim() { 893 mStackScroller.setLeftTopRightBottom(0, 0, 2000, 2000); 894 895 MotionEvent event = transformEventForView(createMotionEvent(250f, 250f), mStackScroller); 896 assertThat(mStackScroller.isInScrollableRegion(event)).isTrue(); 897 } 898 899 @Test 900 @EnableSceneContainer testIsInsideScrollableRegion_noOffset()901 public void testIsInsideScrollableRegion_noOffset() { 902 mStackScroller.setLeftTopRightBottom(0, 0, 1000, 2000); 903 mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000)); 904 905 MotionEvent event1 = transformEventForView(createMotionEvent(500f, 400f), mStackScroller); 906 assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse(); 907 908 MotionEvent event2 = transformEventForView(createMotionEvent(50, 1000f), mStackScroller); 909 assertThat(mStackScroller.isInScrollableRegion(event2)).isFalse(); 910 911 MotionEvent event3 = transformEventForView(createMotionEvent(950f, 1000f), mStackScroller); 912 assertThat(mStackScroller.isInScrollableRegion(event3)).isFalse(); 913 914 MotionEvent event4 = transformEventForView(createMotionEvent(500f, 1000f), mStackScroller); 915 assertThat(mStackScroller.isInScrollableRegion(event4)).isTrue(); 916 } 917 918 @Test 919 @EnableSceneContainer testIsInsideScrollableRegion_offset()920 public void testIsInsideScrollableRegion_offset() { 921 mStackScroller.setLeftTopRightBottom(1000, 0, 2000, 2000); 922 mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000)); 923 924 MotionEvent event1 = transformEventForView(createMotionEvent(1500f, 400f), mStackScroller); 925 assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse(); 926 927 MotionEvent event2 = transformEventForView(createMotionEvent(1050, 1000f), mStackScroller); 928 assertThat(mStackScroller.isInScrollableRegion(event2)).isFalse(); 929 930 MotionEvent event3 = transformEventForView(createMotionEvent(1950f, 1000f), mStackScroller); 931 assertThat(mStackScroller.isInScrollableRegion(event3)).isFalse(); 932 933 MotionEvent event4 = transformEventForView(createMotionEvent(1500f, 1000f), mStackScroller); 934 assertThat(mStackScroller.isInScrollableRegion(event4)).isTrue(); 935 } 936 937 @Test 938 @DisableSceneContainer // TODO(b/312473478): address disabled test setFractionToShade_recomputesStackHeight()939 public void setFractionToShade_recomputesStackHeight() { 940 mStackScroller.setFractionToShade(1f); 941 verify(mStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat()); 942 } 943 944 @Test 945 @DisableSceneContainer // TODO(b/312473478): address disabled test testSetOwnScrollY_shadeNotClosing_scrollYChanges()946 public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() { 947 // Given: shade is not closing, scrollY is 0 948 mAmbientState.setScrollY(0); 949 assertEquals(0, mAmbientState.getScrollY()); 950 mAmbientState.setIsClosing(false); 951 952 // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1 953 mStackScroller.setOwnScrollY(1); 954 955 // Then: scrollY should be set to 1 956 assertEquals(1, mAmbientState.getScrollY()); 957 958 // Reset scrollY back to 0 to avoid interfering with other tests 959 mStackScroller.setOwnScrollY(0); 960 assertEquals(0, mAmbientState.getScrollY()); 961 } 962 963 @Test testSetOwnScrollY_shadeClosing_scrollYDoesNotChange()964 public void testSetOwnScrollY_shadeClosing_scrollYDoesNotChange() { 965 // Given: shade is closing, scrollY is 0 966 mAmbientState.setScrollY(0); 967 assertEquals(0, mAmbientState.getScrollY()); 968 mAmbientState.setIsClosing(true); 969 970 // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1 971 mStackScroller.setOwnScrollY(1); 972 973 // Then: scrollY should not change, it should still be 0 974 assertEquals(0, mAmbientState.getScrollY()); 975 976 // Reset scrollY and mAmbientState.mIsClosing to avoid interfering with other tests 977 mAmbientState.setIsClosing(false); 978 mStackScroller.setOwnScrollY(0); 979 assertEquals(0, mAmbientState.getScrollY()); 980 } 981 982 @Test testSetOwnScrollY_clearAllInProgress_scrollYDoesNotChange()983 public void testSetOwnScrollY_clearAllInProgress_scrollYDoesNotChange() { 984 // Given: clear all is in progress, scrollY is 0 985 mAmbientState.setScrollY(0); 986 assertEquals(0, mAmbientState.getScrollY()); 987 mAmbientState.setClearAllInProgress(true); 988 989 // When: call NotificationStackScrollLayout.setOwnScrollY to set scrollY to 1 990 mStackScroller.setOwnScrollY(1); 991 992 // Then: scrollY should not change, it should still be 0 993 assertEquals(0, mAmbientState.getScrollY()); 994 995 // Reset scrollY and mAmbientState.mIsClosing to avoid interfering with other tests 996 mAmbientState.setClearAllInProgress(false); 997 mStackScroller.setOwnScrollY(0); 998 assertEquals(0, mAmbientState.getScrollY()); 999 } 1000 1001 @Test onShadeFlingClosingEnd_scrollYShouldBeSetToZero()1002 public void onShadeFlingClosingEnd_scrollYShouldBeSetToZero() { 1003 // Given: mAmbientState.mIsClosing is set to be true 1004 // mIsExpanded is set to be false 1005 mAmbientState.setIsClosing(true); 1006 mStackScroller.setIsExpanded(false); 1007 1008 // When: onExpansionStopped is called 1009 mStackScroller.onExpansionStopped(); 1010 1011 // Then: mAmbientState.scrollY should be set to be 0 1012 assertEquals(mAmbientState.getScrollY(), 0); 1013 } 1014 1015 @Test onShadeClosesWithAnimationWillResetTouchState()1016 public void onShadeClosesWithAnimationWillResetTouchState() { 1017 // GIVEN shade is expanded 1018 mStackScroller.setIsExpanded(true); 1019 clearInvocations(mNotificationSwipeHelper); 1020 1021 // WHEN closing the shade with the animations 1022 mStackScroller.onExpansionStarted(); 1023 mStackScroller.setIsExpanded(false); 1024 mStackScroller.onExpansionStopped(); 1025 1026 // VERIFY touch is reset 1027 verify(mNotificationSwipeHelper).resetTouchState(); 1028 } 1029 1030 @Test onShadeClosesWithoutAnimationWillResetTouchState()1031 public void onShadeClosesWithoutAnimationWillResetTouchState() { 1032 // GIVEN shade is expanded 1033 mStackScroller.setIsExpanded(true); 1034 clearInvocations(mNotificationSwipeHelper); 1035 1036 // WHEN closing the shade without the animation 1037 mStackScroller.setIsExpanded(false); 1038 1039 // VERIFY touch is reset 1040 verify(mNotificationSwipeHelper).resetTouchState(); 1041 } 1042 1043 @Test 1044 @DisableSceneContainer // TODO(b/312473478): address disabled test testSplitShade_hasTopOverscroll()1045 public void testSplitShade_hasTopOverscroll() { 1046 mTestableResources 1047 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true); 1048 mStackScroller.passSplitShadeStateController(new ResourcesSplitShadeStateController()); 1049 mStackScroller.updateSplitNotificationShade(); 1050 mAmbientState.setExpansionFraction(1f); 1051 1052 int topOverscrollPixels = 100; 1053 mStackScroller.setOverScrolledPixels(topOverscrollPixels, true, false); 1054 1055 float expectedTopOverscrollAmount = topOverscrollPixels * RUBBER_BAND_FACTOR_NORMAL; 1056 assertEquals(expectedTopOverscrollAmount, mStackScroller.getCurrentOverScrollAmount(true)); 1057 assertEquals(expectedTopOverscrollAmount, mAmbientState.getStackY()); 1058 } 1059 1060 @Test 1061 @DisableSceneContainer // NSSL has no more scroll logic when SceneContainer is on testNormalShade_hasNoTopOverscroll()1062 public void testNormalShade_hasNoTopOverscroll() { 1063 mTestableResources 1064 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false); 1065 mStackScroller.passSplitShadeStateController(new ResourcesSplitShadeStateController()); 1066 mStackScroller.updateSplitNotificationShade(); 1067 mAmbientState.setExpansionFraction(1f); 1068 1069 int topOverscrollPixels = 100; 1070 mStackScroller.setOverScrolledPixels(topOverscrollPixels, true, false); 1071 1072 float expectedTopOverscrollAmount = topOverscrollPixels * RUBBER_BAND_FACTOR_NORMAL; 1073 assertEquals(expectedTopOverscrollAmount, mStackScroller.getCurrentOverScrollAmount(true)); 1074 // When not in split shade mode, then the overscroll effect is handled in 1075 // NotificationPanelViewController and not in NotificationStackScrollLayout. Therefore 1076 // mAmbientState must have stackY set to 0 1077 assertEquals(0f, mAmbientState.getStackY()); 1078 } 1079 1080 @Test 1081 @DisableSceneContainer testWindowInsetAnimationProgress_updatesBottomInset()1082 public void testWindowInsetAnimationProgress_updatesBottomInset() { 1083 int imeInset = 100; 1084 WindowInsets windowInsets = new WindowInsets.Builder() 1085 .setInsets(ime(), Insets.of(0, 0, 0, imeInset)).build(); 1086 ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>(); 1087 mStackScrollerInternal 1088 .dispatchWindowInsetsAnimationProgress(windowInsets, windowInsetsAnimations); 1089 1090 assertEquals(imeInset, mStackScrollerInternal.mImeInset); 1091 } 1092 1093 @Test 1094 @EnableSceneContainer testSetMaxDisplayedNotifications_updatesStackHeight()1095 public void testSetMaxDisplayedNotifications_updatesStackHeight() { 1096 int fullStackHeight = 300; 1097 int limitedStackHeight = 100; 1098 int maxNotifs = 2; // any non-zero limit 1099 float stackTop = 100; 1100 float stackCutoff = 1100; 1101 float stackViewPortHeight = stackCutoff - stackTop; 1102 mStackScroller.setStackTop(stackTop); 1103 mStackScroller.setStackCutoff(stackCutoff); 1104 when(mStackSizeCalculator.computeHeight(eq(mStackScroller), eq(-1), anyFloat())) 1105 .thenReturn((float) fullStackHeight); 1106 when(mStackSizeCalculator.computeHeight(eq(mStackScroller), eq(maxNotifs), anyFloat())) 1107 .thenReturn((float) limitedStackHeight); 1108 1109 // When we set a limit on max displayed notifications 1110 mStackScroller.setMaxDisplayedNotifications(maxNotifs); 1111 1112 // Then 1113 assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(limitedStackHeight); 1114 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(limitedStackHeight); 1115 1116 // When there is no limit on max displayed notifications 1117 mStackScroller.setMaxDisplayedNotifications(-1); 1118 1119 // Then 1120 assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(fullStackHeight); 1121 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackViewPortHeight); 1122 } 1123 1124 @Test 1125 @EnableSceneContainer testChildHeightUpdated_whenMaxDisplayedNotificationsSet_updatesStackHeight()1126 public void testChildHeightUpdated_whenMaxDisplayedNotificationsSet_updatesStackHeight() { 1127 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1128 int maxNotifs = 1; // any non-zero limit 1129 float stackTop = 100; 1130 float stackCutoff = 1100; 1131 mStackScroller.setStackTop(stackTop); 1132 mStackScroller.setStackCutoff(stackCutoff); 1133 1134 // Given we have a limit on max displayed notifications 1135 int stackHeightBeforeUpdate = 100; 1136 when(mStackSizeCalculator.computeHeight(eq(mStackScroller), eq(maxNotifs), anyFloat())) 1137 .thenReturn((float) stackHeightBeforeUpdate); 1138 mStackScroller.setMaxDisplayedNotifications(maxNotifs); 1139 1140 // And the stack heights are set 1141 assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(stackHeightBeforeUpdate); 1142 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeightBeforeUpdate); 1143 1144 // When a child changes its height 1145 int stackHeightAfterUpdate = 300; 1146 when(mStackSizeCalculator.computeHeight(eq(mStackScroller), eq(maxNotifs), anyFloat())) 1147 .thenReturn((float) stackHeightAfterUpdate); 1148 mStackScroller.onChildHeightChanged(row, /* needsAnimation = */ false); 1149 1150 // Then the stack heights are updated 1151 assertThat(mStackScroller.getIntrinsicStackHeight()).isEqualTo(stackHeightAfterUpdate); 1152 assertThat(mAmbientState.getStackEndHeight()).isEqualTo(stackHeightAfterUpdate); 1153 } 1154 1155 @Test 1156 @DisableSceneContainer testSetMaxDisplayedNotifications_notifiesListeners()1157 public void testSetMaxDisplayedNotifications_notifiesListeners() { 1158 ExpandableView.OnHeightChangedListener listener = 1159 mock(ExpandableView.OnHeightChangedListener.class); 1160 Runnable runnable = mock(Runnable.class); 1161 mStackScroller.setOnHeightChangedListener(listener); 1162 mStackScroller.setOnHeightChangedRunnable(runnable); 1163 1164 mStackScroller.setMaxDisplayedNotifications(50); 1165 1166 verify(listener).onHeightChanged(mNotificationShelf, false); 1167 verify(runnable).run(); 1168 } 1169 1170 @Test 1171 @DisableSceneContainer testDispatchTouchEvent_sceneContainerDisabled()1172 public void testDispatchTouchEvent_sceneContainerDisabled() { 1173 MotionEvent event = MotionEvent.obtain( 1174 SystemClock.uptimeMillis(), 1175 SystemClock.uptimeMillis(), 1176 MotionEvent.ACTION_MOVE, 1177 0, 1178 0, 1179 0 1180 ); 1181 1182 mStackScroller.dispatchTouchEvent(event); 1183 1184 verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any()); 1185 } 1186 1187 @Test 1188 @EnableSceneContainer testDispatchTouchEvent_sceneContainerEnabled()1189 public void testDispatchTouchEvent_sceneContainerEnabled() { 1190 mStackScroller.setIsBeingDragged(true); 1191 1192 long downTime = SystemClock.uptimeMillis() - 100; 1193 MotionEvent moveEvent1 = MotionEvent.obtain( 1194 /* downTime= */ downTime, 1195 /* eventTime= */ SystemClock.uptimeMillis(), 1196 MotionEvent.ACTION_MOVE, 1197 101, 1198 201, 1199 0 1200 ); 1201 MotionEvent syntheticDownEvent = moveEvent1.copy(); 1202 syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN); 1203 mStackScroller.dispatchTouchEvent(moveEvent1); 1204 1205 assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(syntheticDownEvent); 1206 assertTrue(mStackScroller.getIsBeingDragged()); 1207 clearInvocations(mStackScrollLayoutController); 1208 1209 MotionEvent moveEvent2 = MotionEvent.obtain( 1210 /* downTime= */ downTime, 1211 /* eventTime= */ SystemClock.uptimeMillis(), 1212 MotionEvent.ACTION_MOVE, 1213 102, 1214 202, 1215 0 1216 ); 1217 1218 mStackScroller.dispatchTouchEvent(moveEvent2); 1219 1220 assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(moveEvent2); 1221 assertTrue(mStackScroller.getIsBeingDragged()); 1222 clearInvocations(mStackScrollLayoutController); 1223 1224 MotionEvent upEvent = MotionEvent.obtain( 1225 /* downTime= */ downTime, 1226 /* eventTime= */ SystemClock.uptimeMillis(), 1227 MotionEvent.ACTION_UP, 1228 103, 1229 203, 1230 0 1231 ); 1232 1233 mStackScroller.dispatchTouchEvent(upEvent); 1234 1235 assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(upEvent); 1236 assertFalse(mStackScroller.getIsBeingDragged()); 1237 } 1238 1239 @Test 1240 @EnableSceneContainer testDispatchTouchEvent_sceneContainerEnabled_ignoresInitialActionUp()1241 public void testDispatchTouchEvent_sceneContainerEnabled_ignoresInitialActionUp() { 1242 mStackScroller.setIsBeingDragged(true); 1243 1244 MotionEvent upEvent = MotionEvent.obtain( 1245 SystemClock.uptimeMillis(), 1246 SystemClock.uptimeMillis(), 1247 MotionEvent.ACTION_UP, 1248 0, 1249 0, 1250 0 1251 ); 1252 1253 mStackScroller.dispatchTouchEvent(upEvent); 1254 verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any()); 1255 assertFalse(mStackScroller.getIsBeingDragged()); 1256 } 1257 1258 1259 @Test 1260 @EnableSceneContainer testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway()1261 public void testGenerateHeadsUpDisappearEvent_setsHeadsUpAnimatingAway() { 1262 // GIVEN NSSL is ready for HUN animations 1263 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1264 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1265 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1266 1267 // WHEN we generate a disappear event 1268 mStackScroller.generateHeadsUpAnimation( 1269 row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false); 1270 1271 // THEN headsUpAnimatingAway is true 1272 verify(headsUpAnimatingAwayListener).accept(true); 1273 assertTrue(mStackScroller.mHeadsUpAnimatingAway); 1274 } 1275 1276 @Test 1277 @EnableSceneContainer 1278 @DisableFlags(StatusBarNotifChips.FLAG_NAME) testGenerateHeadsUpDisappearEvent_notifChipsFlagOff_statusBarChipNotSet()1279 public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOff_statusBarChipNotSet() { 1280 // GIVEN NSSL is ready for HUN animations 1281 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1282 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1283 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1284 1285 mStackScroller.generateHeadsUpAnimation( 1286 row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true); 1287 1288 verify(row, never()).setHasStatusBarChipDuringHeadsUpAnimation(anyBoolean()); 1289 } 1290 1291 @Test 1292 @EnableSceneContainer 1293 @EnableFlags(StatusBarNotifChips.FLAG_NAME) testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToFalse()1294 public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToFalse() { 1295 // GIVEN NSSL is ready for HUN animations 1296 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1297 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1298 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1299 1300 mStackScroller.generateHeadsUpAnimation( 1301 row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false); 1302 1303 verify(row).setHasStatusBarChipDuringHeadsUpAnimation(false); 1304 } 1305 1306 @Test 1307 @EnableSceneContainer 1308 @EnableFlags(StatusBarNotifChips.FLAG_NAME) testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToTrue()1309 public void testGenerateHeadsUpDisappearEvent_notifChipsFlagOn_statusBarChipSetToTrue() { 1310 // GIVEN NSSL is ready for HUN animations 1311 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1312 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1313 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1314 1315 mStackScroller.generateHeadsUpAnimation( 1316 row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ true); 1317 1318 verify(row).setHasStatusBarChipDuringHeadsUpAnimation(true); 1319 } 1320 1321 @Test 1322 @EnableSceneContainer testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet()1323 public void testGenerateHeadsUpDisappearEvent_stackExpanded_headsUpAnimatingAwayNotSet() { 1324 // GIVEN NSSL would be ready for HUN animations, BUT it is expanded 1325 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1326 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1327 assertTrue("Should be expanded by default.", mStackScroller.isExpanded()); 1328 mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener); 1329 mStackScroller.setAnimationsEnabled(true); 1330 mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true); 1331 1332 // WHEN we generate a disappear event 1333 mStackScroller.generateHeadsUpAnimation( 1334 row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false); 1335 1336 // THEN nothing happens 1337 verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean()); 1338 assertFalse(mStackScroller.mHeadsUpAnimatingAway); 1339 } 1340 1341 @Test 1342 @EnableSceneContainer testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet()1343 public void testGenerateHeadsUpDisappearEvent_pendingAppearEvent_headsUpAnimatingAwayNotSet() { 1344 // GIVEN NSSL is ready for HUN animations 1345 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1346 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1347 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1348 // BUT there is a pending appear event 1349 mStackScroller.generateHeadsUpAnimation( 1350 row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false); 1351 1352 // WHEN we generate a disappear event 1353 mStackScroller.generateHeadsUpAnimation( 1354 row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false); 1355 1356 // THEN nothing happens 1357 verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean()); 1358 assertFalse(mStackScroller.mHeadsUpAnimatingAway); 1359 } 1360 1361 @Test 1362 @EnableSceneContainer testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet()1363 public void testGenerateHeadsUpAppearEvent_headsUpAnimatingAwayNotSet() { 1364 // GIVEN NSSL is ready for HUN animations 1365 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1366 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1367 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1368 1369 // WHEN we generate a disappear event 1370 mStackScroller.generateHeadsUpAnimation( 1371 row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false); 1372 1373 // THEN headsUpAnimatingWay is not set 1374 verify(headsUpAnimatingAwayListener, never()).accept(anyBoolean()); 1375 assertFalse(mStackScroller.mHeadsUpAnimatingAway); 1376 } 1377 1378 @Test 1379 @EnableFlags(NotificationThrottleHun.FLAG_NAME) 1380 @BrokenWithSceneContainer(bugId = 332732878) // because NSSL#mAnimationsEnabled is always true testGenerateHeadsUpAnimation_isSeenInShade_noAnimation()1381 public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() { 1382 // GIVEN NSSL is ready for HUN animations 1383 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1384 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1385 1386 // Entry was seen in shade 1387 NotificationEntry entry = mock(NotificationEntry.class); 1388 when(entry.isSeenInShade()).thenReturn(true); 1389 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1390 when(row.getEntryLegacy()).thenReturn(entry); 1391 when(row.getEntry()).thenReturn(entry); 1392 1393 // WHEN we generate an add event 1394 mStackScroller.generateHeadsUpAnimation( 1395 row, /* isHeadsUp = */ true, /* hasStatusBarChip= */ false); 1396 1397 // THEN nothing happens 1398 assertThat(mStackScroller.isAddOrRemoveAnimationPending()).isFalse(); 1399 } 1400 1401 @Test 1402 @EnableSceneContainer testOnChildAnimationsFinished_resetsheadsUpAnimatingAway()1403 public void testOnChildAnimationsFinished_resetsheadsUpAnimatingAway() { 1404 // GIVEN NSSL is ready for HUN animations 1405 Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class); 1406 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1407 prepareStackScrollerForHunAnimations(headsUpAnimatingAwayListener); 1408 1409 // AND there is a HUN animating away 1410 mStackScroller.generateHeadsUpAnimation( 1411 row, /* isHeadsUp = */ false, /* hasStatusBarChip= */ false); 1412 assertTrue("a HUN should be animating away", mStackScroller.mHeadsUpAnimatingAway); 1413 1414 // WHEN the child animations are finished 1415 mStackScroller.onChildAnimationFinished(); 1416 1417 // THEN headsUpAnimatingAway is false 1418 verify(headsUpAnimatingAwayListener).accept(false); 1419 assertFalse(mStackScroller.mHeadsUpAnimatingAway); 1420 } 1421 captureTouchSentToSceneFramework()1422 private MotionEvent captureTouchSentToSceneFramework() { 1423 ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class); 1424 verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture()); 1425 return captor.getValue(); 1426 } 1427 setBarStateForTest(int state)1428 private void setBarStateForTest(int state) { 1429 // Can't inject this through the listener or we end up on the actual implementation 1430 // rather than the mock because the spy just coppied the anonymous inner /shruggie. 1431 mStackScroller.setStatusBarState(state); 1432 } 1433 prepareStackScrollerForHunAnimations( Consumer<Boolean> headsUpAnimatingAwayListener)1434 private void prepareStackScrollerForHunAnimations( 1435 Consumer<Boolean> headsUpAnimatingAwayListener) { 1436 mStackScroller.setHeadsUpAnimatingAwayListener(headsUpAnimatingAwayListener); 1437 mStackScroller.setIsExpanded(false); 1438 mStackScroller.setAnimationsEnabled(true); 1439 mStackScroller.setHeadsUpGoingAwayAnimationsAllowed(true); 1440 } 1441 createClearableRow()1442 private ExpandableNotificationRow createClearableRow() { 1443 ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); 1444 NotificationEntry entry = mock(NotificationEntry.class); 1445 when(row.canViewBeCleared()).thenReturn(true); 1446 when(row.getEntry()).thenReturn(entry); 1447 when(row.getEntryLegacy()).thenReturn(entry); 1448 when(entry.isClearable()).thenReturn(true); 1449 EntryAdapter entryAdapter = mock(EntryAdapter.class); 1450 when(entryAdapter.isClearable()).thenReturn(true); 1451 when(row.getEntryAdapter()).thenReturn(entryAdapter); 1452 1453 return row; 1454 } 1455 assertClearAllInProgress(boolean expected)1456 private void assertClearAllInProgress(boolean expected) { 1457 assertEquals(expected, mStackScroller.getClearAllInProgress()); 1458 assertEquals(expected, mAmbientState.isClearAllInProgress()); 1459 } 1460 px(@imenRes int id)1461 private int px(@DimenRes int id) { 1462 return mTestableResources.getResources().getDimensionPixelSize(id); 1463 } 1464 mockBoundsOnScreen(View view, Rect bounds)1465 private static void mockBoundsOnScreen(View view, Rect bounds) { 1466 doAnswer(invocation -> { 1467 Rect out = invocation.getArgument(0); 1468 out.set(bounds); 1469 return null; 1470 }).when(view).getBoundsOnScreen(any()); 1471 } 1472 transformEventForView(MotionEvent event, View view)1473 private static MotionEvent transformEventForView(MotionEvent event, View view) { 1474 // From `ViewGroup#dispatchTransformedTouchEvent` 1475 MotionEvent transformed = event.copy(); 1476 transformed.offsetLocation(/* deltaX = */-view.getLeft(), /* deltaY = */ -view.getTop()); 1477 return transformed; 1478 } 1479 createMotionEvent(float x, float y)1480 private static MotionEvent createMotionEvent(float x, float y) { 1481 return MotionEvent.obtain( 1482 /* downTime= */0, 1483 /* eventTime= */0, 1484 MotionEvent.ACTION_DOWN, 1485 x, 1486 y, 1487 /* metaState= */0 1488 ); 1489 } 1490 assertThatMotionEvent(MotionEvent actual)1491 private MotionEventSubject assertThatMotionEvent(MotionEvent actual) { 1492 return new MotionEventSubject(actual); 1493 } 1494 1495 private static class MotionEventSubject { 1496 private final MotionEvent mActual; 1497 MotionEventSubject(MotionEvent actual)1498 MotionEventSubject(MotionEvent actual) { 1499 mActual = actual; 1500 } 1501 matches(MotionEvent expected)1502 public void matches(MotionEvent expected) { 1503 assertThat(mActual.getActionMasked()).isEqualTo(expected.getActionMasked()); 1504 assertThat(mActual.getDownTime()).isEqualTo(expected.getDownTime()); 1505 assertThat(mActual.getEventTime()).isEqualTo(expected.getEventTime()); 1506 assertThat(mActual.getX()).isEqualTo(expected.getX()); 1507 assertThat(mActual.getY()).isEqualTo(expected.getY()); 1508 } 1509 } 1510 1511 private abstract static class BooleanConsumer implements Consumer<Boolean> { } 1512 createScrimShape(int left, int top, int right, int bottom)1513 private ShadeScrimShape createScrimShape(int left, int top, int right, int bottom) { 1514 ShadeScrimBounds bounds = new ShadeScrimBounds(left, top, right, bottom); 1515 return new ShadeScrimShape(bounds, 0, 0); 1516 } 1517 } 1518