• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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