1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.dreams.touch;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyFloat;
23 import static org.mockito.ArgumentMatchers.eq;
24 import static org.mockito.Mockito.never;
25 import static org.mockito.Mockito.reset;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 import android.animation.AnimatorListenerAdapter;
30 import android.animation.ValueAnimator;
31 import android.content.pm.UserInfo;
32 import android.graphics.Rect;
33 import android.graphics.Region;
34 import android.testing.AndroidTestingRunner;
35 import android.view.GestureDetector;
36 import android.view.GestureDetector.OnGestureListener;
37 import android.view.MotionEvent;
38 import android.view.VelocityTracker;
39 
40 import androidx.test.filters.SmallTest;
41 
42 import com.android.internal.logging.UiEventLogger;
43 import com.android.internal.widget.LockPatternUtils;
44 import com.android.systemui.SysuiTestCase;
45 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
46 import com.android.systemui.dreams.touch.scrim.ScrimController;
47 import com.android.systemui.dreams.touch.scrim.ScrimManager;
48 import com.android.systemui.settings.FakeUserTracker;
49 import com.android.systemui.shade.ShadeExpansionChangeEvent;
50 import com.android.systemui.shared.system.InputChannelCompat;
51 import com.android.systemui.statusbar.NotificationShadeWindowController;
52 import com.android.systemui.statusbar.phone.CentralSurfaces;
53 import com.android.wm.shell.animation.FlingAnimationUtils;
54 
55 import org.junit.Before;
56 import org.junit.Test;
57 import org.junit.runner.RunWith;
58 import org.mockito.ArgumentCaptor;
59 import org.mockito.Mock;
60 import org.mockito.Mockito;
61 import org.mockito.MockitoAnnotations;
62 
63 import java.util.Collections;
64 import java.util.Optional;
65 
66 @SmallTest
67 @RunWith(AndroidTestingRunner.class)
68 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
69     @Mock
70     CentralSurfaces mCentralSurfaces;
71 
72     @Mock
73     ScrimManager mScrimManager;
74 
75     @Mock
76     ScrimController mScrimController;
77 
78     @Mock
79     NotificationShadeWindowController mNotificationShadeWindowController;
80 
81     @Mock
82     FlingAnimationUtils mFlingAnimationUtils;
83 
84     @Mock
85     FlingAnimationUtils mFlingAnimationUtilsClosing;
86 
87     @Mock
88     DreamTouchHandler.TouchSession mTouchSession;
89 
90     BouncerSwipeTouchHandler mTouchHandler;
91 
92     @Mock
93     BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
94 
95     @Mock
96     ValueAnimator mValueAnimator;
97 
98     @Mock
99     BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
100 
101     @Mock
102     VelocityTracker mVelocityTracker;
103 
104     @Mock
105     UiEventLogger mUiEventLogger;
106 
107     @Mock
108     LockPatternUtils mLockPatternUtils;
109 
110     FakeUserTracker mUserTracker;
111 
112     private static final float TOUCH_REGION = .3f;
113     private static final int SCREEN_WIDTH_PX = 1024;
114     private static final int SCREEN_HEIGHT_PX = 100;
115 
116     private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
117     private static final UserInfo CURRENT_USER_INFO = new UserInfo(
118             10,
119             /* name= */ "user10",
120             /* flags= */ 0
121     );
122 
123     @Before
setup()124     public void setup() {
125         MockitoAnnotations.initMocks(this);
126         mUserTracker = new FakeUserTracker();
127         mTouchHandler = new BouncerSwipeTouchHandler(
128                 mScrimManager,
129                 Optional.of(mCentralSurfaces),
130                 mNotificationShadeWindowController,
131                 mValueAnimatorCreator,
132                 mVelocityTrackerFactory,
133                 mLockPatternUtils,
134                 mUserTracker,
135                 mFlingAnimationUtils,
136                 mFlingAnimationUtilsClosing,
137                 TOUCH_REGION,
138                 mUiEventLogger);
139 
140         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
141         when(mCentralSurfaces.isBouncerShowing()).thenReturn(false);
142         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
143         when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
144         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
145         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
146         when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
147 
148         mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
149     }
150 
151     /**
152      * Ensures expansion only happens when touch down happens in valid part of the screen.
153      */
154     @Test
testSessionStart()155     public void testSessionStart() {
156         final Region region = Region.obtain();
157         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, region);
158 
159         final Rect bounds = region.getBounds();
160 
161         final Rect expected = new Rect();
162 
163         expected.set(0, Math.round(SCREEN_HEIGHT_PX * (1 - TOUCH_REGION)), SCREEN_WIDTH_PX,
164                 SCREEN_HEIGHT_PX);
165 
166         assertThat(bounds).isEqualTo(expected);
167 
168         mTouchHandler.onSessionStart(mTouchSession);
169         verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any());
170         ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
171                 ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
172         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
173                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
174         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
175         verify(mTouchSession).registerInputListener(eventListenerCaptor.capture());
176 
177         // A touch within range at the bottom of the screen should trigger listening
178         assertThat(gestureListenerCaptor.getValue()
179                 .onScroll(Mockito.mock(MotionEvent.class),
180                         Mockito.mock(MotionEvent.class),
181                         1,
182                         2)).isTrue();
183     }
184 
185     private enum Direction {
186         DOWN,
187         UP,
188     }
189 
190     /**
191      * Makes sure swiping up when bouncer initially showing doesn't change the expansion amount.
192      */
193     @Test
testSwipeUp_whenBouncerInitiallyShowing_doesNotSetExpansion()194     public void testSwipeUp_whenBouncerInitiallyShowing_doesNotSetExpansion() {
195         when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
196 
197         mTouchHandler.onSessionStart(mTouchSession);
198         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
199                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
200         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
201 
202         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
203 
204         final float percent = .3f;
205         final float distanceY = SCREEN_HEIGHT_PX * percent;
206 
207         // Swiping up near the top of the screen where the touch initiation region is.
208         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
209                 0, distanceY, 0);
210         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
211                 0, 0, 0);
212 
213         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
214                 .isTrue();
215 
216         verify(mScrimController, never()).expand(any());
217     }
218 
219     /**
220      * Makes sure swiping down when bouncer initially hidden doesn't change the expansion amount.
221      */
222     @Test
testSwipeDown_whenBouncerInitiallyHidden_doesNotSetExpansion()223     public void testSwipeDown_whenBouncerInitiallyHidden_doesNotSetExpansion() {
224         mTouchHandler.onSessionStart(mTouchSession);
225         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
226                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
227         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
228 
229         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
230 
231         final float percent = .15f;
232         final float distanceY = SCREEN_HEIGHT_PX * percent;
233 
234         // Swiping down near the bottom of the screen where the touch initiation region is.
235         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
236                 0, SCREEN_HEIGHT_PX - distanceY, 0);
237         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
238                 0, SCREEN_HEIGHT_PX, 0);
239 
240         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
241                 .isTrue();
242 
243         verify(mScrimController, never()).expand(any());
244     }
245 
246     /**
247      * Makes sure the expansion amount is proportional to (1 - scroll).
248      */
249     @Test
testSwipeUp_setsCorrectExpansionAmount()250     public void testSwipeUp_setsCorrectExpansionAmount() {
251         mTouchHandler.onSessionStart(mTouchSession);
252         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
253                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
254         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
255 
256         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
257 
258         verifyScroll(.3f, Direction.UP, false, gestureListener);
259 
260         // Ensure that subsequent gestures are treated as expanding even if the bouncer state
261         // changes.
262         when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
263         verifyScroll(.7f, Direction.UP, false, gestureListener);
264     }
265 
266     /**
267      * Makes sure the expansion amount is proportional to scroll.
268      */
269     @Test
testSwipeDown_setsCorrectExpansionAmount()270     public void testSwipeDown_setsCorrectExpansionAmount() {
271         when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
272 
273         mTouchHandler.onSessionStart(mTouchSession);
274         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
275                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
276         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
277 
278         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
279 
280         verifyScroll(.3f, Direction.DOWN, true, gestureListener);
281 
282         // Ensure that subsequent gestures are treated as collapsing even if the bouncer state
283         // changes.
284         when(mCentralSurfaces.isBouncerShowing()).thenReturn(false);
285         verifyScroll(.7f, Direction.DOWN, true, gestureListener);
286     }
287 
288     /**
289      * Makes sure the expansion amount is proportional to (1 - scroll).
290      */
291     @Test
testSwipeUp_keyguardNotSecure_doesNotExpand()292     public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
293         when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
294         mTouchHandler.onSessionStart(mTouchSession);
295         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
296                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
297         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
298 
299         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
300 
301         final float distanceY = SCREEN_HEIGHT_PX * 0.3f;
302         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
303                 0, SCREEN_HEIGHT_PX, 0);
304         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
305                 0, SCREEN_HEIGHT_PX - distanceY, 0);
306 
307         reset(mScrimController);
308         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
309                 .isTrue();
310         // We should not expand since the keyguard is not secure
311         verify(mScrimController, never()).expand(any());
312     }
313 
verifyScroll(float percent, Direction direction, boolean isBouncerInitiallyShowing, GestureDetector.OnGestureListener gestureListener)314     private void verifyScroll(float percent, Direction direction,
315             boolean isBouncerInitiallyShowing, GestureDetector.OnGestureListener gestureListener) {
316         final float distanceY = SCREEN_HEIGHT_PX * percent;
317 
318         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
319                 0, direction == Direction.UP ? SCREEN_HEIGHT_PX : 0, 0);
320         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
321                 0, direction == Direction.UP ? SCREEN_HEIGHT_PX - distanceY : distanceY, 0);
322 
323         reset(mScrimController);
324         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
325                 .isTrue();
326 
327         // Ensure only called once
328         verify(mScrimController).expand(any());
329 
330         final float expansion = isBouncerInitiallyShowing ? percent : 1 - percent;
331         final float dragDownAmount = event2.getY() - event1.getY();
332 
333         // Ensure correct expansion passed in.
334         ShadeExpansionChangeEvent event =
335                 new ShadeExpansionChangeEvent(
336                         expansion, /* expanded= */ false, /* tracking= */ true, dragDownAmount);
337         verify(mScrimController).expand(event);
338     }
339 
340     /**
341      * Tests that ending an upward swipe before the set threshold leads to bouncer collapsing down.
342      */
343     @Test
testSwipeUpPositionBelowThreshold_collapsesBouncer()344     public void testSwipeUpPositionBelowThreshold_collapsesBouncer() {
345         final float swipeUpPercentage = .3f;
346         final float expansion = 1 - swipeUpPercentage;
347         // The upward velocity is ignored.
348         final float velocityY = -1;
349         swipeToPosition(swipeUpPercentage, Direction.UP, velocityY);
350 
351         verify(mValueAnimatorCreator).create(eq(expansion),
352                 eq(KeyguardBouncerConstants.EXPANSION_HIDDEN));
353         verify(mValueAnimator, never()).addListener(any());
354 
355         verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator),
356                 eq(SCREEN_HEIGHT_PX * expansion),
357                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_HIDDEN),
358                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
359         verify(mValueAnimator).start();
360         verify(mUiEventLogger, never()).log(any());
361     }
362 
363     /**
364      * Tests that ending an upward swipe above the set threshold will continue the expansion.
365      */
366     @Test
testSwipeUpPositionAboveThreshold_expandsBouncer()367     public void testSwipeUpPositionAboveThreshold_expandsBouncer() {
368         final float swipeUpPercentage = .7f;
369         final float expansion = 1 - swipeUpPercentage;
370         // The downward velocity is ignored.
371         final float velocityY = 1;
372         swipeToPosition(swipeUpPercentage, Direction.UP, velocityY);
373 
374         verify(mValueAnimatorCreator).create(eq(expansion),
375                 eq(KeyguardBouncerConstants.EXPANSION_VISIBLE));
376 
377         ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor =
378                 ArgumentCaptor.forClass(AnimatorListenerAdapter.class);
379         verify(mValueAnimator).addListener(endAnimationListenerCaptor.capture());
380         AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue();
381 
382         verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion),
383                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE),
384                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
385         verify(mValueAnimator).start();
386         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED);
387 
388         endAnimationListener.onAnimationEnd(mValueAnimator);
389         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
390     }
391 
392     /**
393      * Tests that ending a downward swipe above the set threshold will continue the expansion,
394      * but will not trigger logging of the DREAM_SWIPED event.
395      */
396     @Test
testSwipeDownPositionAboveThreshold_expandsBouncer_doesNotLog()397     public void testSwipeDownPositionAboveThreshold_expandsBouncer_doesNotLog() {
398         when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
399 
400         final float swipeDownPercentage = .3f;
401         // The downward velocity is ignored.
402         final float velocityY = 1;
403         swipeToPosition(swipeDownPercentage, Direction.DOWN, velocityY);
404 
405         verify(mValueAnimatorCreator).create(eq(swipeDownPercentage),
406                 eq(KeyguardBouncerConstants.EXPANSION_VISIBLE));
407         verify(mValueAnimator, never()).addListener(any());
408 
409         verify(mFlingAnimationUtils).apply(eq(mValueAnimator),
410                 eq(SCREEN_HEIGHT_PX * swipeDownPercentage),
411                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE),
412                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
413         verify(mValueAnimator).start();
414         verify(mUiEventLogger, never()).log(any());
415     }
416 
417     /**
418      * Tests that swiping down with a speed above the set threshold leads to bouncer collapsing
419      * down.
420      */
421     @Test
testSwipeDownVelocityAboveMin_collapsesBouncer()422     public void testSwipeDownVelocityAboveMin_collapsesBouncer() {
423         when(mCentralSurfaces.isBouncerShowing()).thenReturn(true);
424         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0);
425 
426         // The ending position above the set threshold is ignored.
427         final float swipeDownPercentage = .3f;
428         final float velocityY = 1;
429         swipeToPosition(swipeDownPercentage, Direction.DOWN, velocityY);
430 
431         verify(mValueAnimatorCreator).create(eq(swipeDownPercentage),
432                 eq(KeyguardBouncerConstants.EXPANSION_HIDDEN));
433         verify(mValueAnimator, never()).addListener(any());
434 
435         verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator),
436                 eq(SCREEN_HEIGHT_PX * swipeDownPercentage),
437                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_HIDDEN),
438                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
439         verify(mValueAnimator).start();
440         verify(mUiEventLogger, never()).log(any());
441     }
442 
443     /**
444      * Tests that swiping up with a speed above the set threshold will continue the expansion.
445      */
446     @Test
testSwipeUpVelocityAboveMin_expandsBouncer()447     public void testSwipeUpVelocityAboveMin_expandsBouncer() {
448         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0);
449 
450         // The ending position below the set threshold is ignored.
451         final float swipeUpPercentage = .3f;
452         final float expansion = 1 - swipeUpPercentage;
453         final float velocityY = -1;
454         swipeToPosition(swipeUpPercentage, Direction.UP, velocityY);
455 
456         verify(mValueAnimatorCreator).create(eq(expansion),
457                 eq(KeyguardBouncerConstants.EXPANSION_VISIBLE));
458 
459         ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor =
460                 ArgumentCaptor.forClass(AnimatorListenerAdapter.class);
461         verify(mValueAnimator).addListener(endAnimationListenerCaptor.capture());
462         AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue();
463 
464         verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion),
465                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE),
466                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
467         verify(mValueAnimator).start();
468         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED);
469 
470         endAnimationListener.onAnimationEnd(mValueAnimator);
471         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
472     }
473 
474     /**
475      * Ensures {@link CentralSurfaces}
476      */
477     @Test
testInformBouncerShowingOnExpand()478     public void testInformBouncerShowingOnExpand() {
479         swipeToPosition(1f, Direction.UP, 0);
480     }
481 
482     /**
483      * Ensures {@link CentralSurfaces}
484      */
485     @Test
testInformBouncerHidingOnCollapse()486     public void testInformBouncerHidingOnCollapse() {
487         // Must swipe up to set initial state.
488         swipeToPosition(1f, Direction.UP, 0);
489         Mockito.clearInvocations(mCentralSurfaces);
490 
491         swipeToPosition(0f, Direction.DOWN, 0);
492     }
493 
494     @Test
testTouchSessionOnRemovedCalledTwice()495     public void testTouchSessionOnRemovedCalledTwice() {
496         mTouchHandler.onSessionStart(mTouchSession);
497         ArgumentCaptor<DreamTouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
498                 ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.Callback.class);
499         verify(mTouchSession).registerCallback(onRemovedCallbackCaptor.capture());
500         onRemovedCallbackCaptor.getValue().onRemoved();
501         onRemovedCallbackCaptor.getValue().onRemoved();
502     }
503 
swipeToPosition(float percent, Direction direction, float velocityY)504     private void swipeToPosition(float percent, Direction direction, float velocityY) {
505         Mockito.clearInvocations(mTouchSession);
506         mTouchHandler.onSessionStart(mTouchSession);
507         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
508                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
509         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
510         ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
511                 ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
512         verify(mTouchSession).registerInputListener(inputEventListenerCaptor.capture());
513 
514         when(mVelocityTracker.getYVelocity()).thenReturn(velocityY);
515 
516         final float distanceY = SCREEN_HEIGHT_PX * percent;
517 
518         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
519                 0, direction == Direction.UP ? SCREEN_HEIGHT_PX : 0, 0);
520         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
521                 0, direction == Direction.UP ? SCREEN_HEIGHT_PX - distanceY : distanceY, 0);
522 
523         assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0, distanceY))
524                 .isTrue();
525 
526         final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
527                 0, 0, 0);
528 
529         inputEventListenerCaptor.getValue().onInputEvent(upEvent);
530     }
531 }
532