1 /*
2  * Copyright (C) 2024 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.ambient.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.verifyNoMoreInteractions;
28 import static org.mockito.Mockito.when;
29 
30 import android.animation.AnimatorListenerAdapter;
31 import android.animation.ValueAnimator;
32 import android.content.pm.UserInfo;
33 import android.graphics.Rect;
34 import android.graphics.Region;
35 import android.platform.test.annotations.DisableFlags;
36 import android.platform.test.annotations.EnableFlags;
37 import android.view.GestureDetector;
38 import android.view.GestureDetector.OnGestureListener;
39 import android.view.MotionEvent;
40 import android.view.VelocityTracker;
41 
42 import androidx.test.ext.junit.runners.AndroidJUnit4;
43 import androidx.test.filters.SmallTest;
44 
45 import com.android.internal.logging.UiEventLogger;
46 import com.android.internal.widget.LockPatternUtils;
47 import com.android.systemui.Flags;
48 import com.android.systemui.SysuiTestCase;
49 import com.android.systemui.ambient.touch.scrim.ScrimController;
50 import com.android.systemui.ambient.touch.scrim.ScrimManager;
51 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
52 import com.android.systemui.settings.FakeUserTracker;
53 import com.android.systemui.shade.ShadeExpansionChangeEvent;
54 import com.android.systemui.shared.system.InputChannelCompat;
55 import com.android.systemui.statusbar.NotificationShadeWindowController;
56 import com.android.systemui.statusbar.phone.CentralSurfaces;
57 import com.android.wm.shell.animation.FlingAnimationUtils;
58 
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.ArgumentCaptor;
63 import org.mockito.Captor;
64 import org.mockito.Mock;
65 import org.mockito.Mockito;
66 import org.mockito.MockitoAnnotations;
67 
68 import java.util.Collections;
69 import java.util.Optional;
70 
71 @SmallTest
72 @RunWith(AndroidJUnit4.class)
73 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
74     @Mock
75     CentralSurfaces mCentralSurfaces;
76 
77     @Mock
78     ScrimManager mScrimManager;
79 
80     @Mock
81     ScrimController mScrimController;
82 
83     @Mock
84     NotificationShadeWindowController mNotificationShadeWindowController;
85 
86     @Mock
87     FlingAnimationUtils mFlingAnimationUtils;
88 
89     @Mock
90     FlingAnimationUtils mFlingAnimationUtilsClosing;
91 
92     @Mock
93     TouchHandler.TouchSession mTouchSession;
94 
95     BouncerSwipeTouchHandler mTouchHandler;
96 
97     @Mock
98     BouncerSwipeTouchHandler.ValueAnimatorCreator mValueAnimatorCreator;
99 
100     @Mock
101     ValueAnimator mValueAnimator;
102 
103     @Mock
104     BouncerSwipeTouchHandler.VelocityTrackerFactory mVelocityTrackerFactory;
105 
106     @Mock
107     VelocityTracker mVelocityTracker;
108 
109     @Mock
110     UiEventLogger mUiEventLogger;
111 
112     @Mock
113     LockPatternUtils mLockPatternUtils;
114 
115     @Mock
116     Region mRegion;
117 
118     @Captor
119     ArgumentCaptor<Rect> mRectCaptor;
120 
121     FakeUserTracker mUserTracker;
122 
123     private static final float TOUCH_REGION = .3f;
124     private static final int SCREEN_WIDTH_PX = 1024;
125     private static final int SCREEN_HEIGHT_PX = 100;
126     private static final float MIN_BOUNCER_HEIGHT = .05f;
127 
128     private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
129     private static final UserInfo CURRENT_USER_INFO = new UserInfo(
130             10,
131             /* name= */ "user10",
132             /* flags= */ 0
133     );
134 
135     @Before
setup()136     public void setup() {
137         MockitoAnnotations.initMocks(this);
138         mUserTracker = new FakeUserTracker();
139         mTouchHandler = new BouncerSwipeTouchHandler(
140                 mScrimManager,
141                 Optional.of(mCentralSurfaces),
142                 mNotificationShadeWindowController,
143                 mValueAnimatorCreator,
144                 mVelocityTrackerFactory,
145                 mLockPatternUtils,
146                 mUserTracker,
147                 mFlingAnimationUtils,
148                 mFlingAnimationUtilsClosing,
149                 TOUCH_REGION,
150                 MIN_BOUNCER_HEIGHT,
151                 mUiEventLogger);
152 
153         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
154         when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
155         when(mVelocityTrackerFactory.obtain()).thenReturn(mVelocityTracker);
156         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn(Float.MAX_VALUE);
157         when(mTouchSession.getBounds()).thenReturn(SCREEN_BOUNDS);
158         when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(true);
159 
160         mUserTracker.set(Collections.singletonList(CURRENT_USER_INFO), 0);
161     }
162 
163     /**
164      * Ensures expansion only happens when touch down happens in valid part of the screen.
165      */
166     @Test
167     @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
testSessionStart()168     public void testSessionStart() {
169         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
170 
171         verify(mRegion).union(mRectCaptor.capture());
172         final Rect bounds = mRectCaptor.getValue();
173 
174         final Rect expected = new Rect();
175 
176         expected.set(0, Math.round(SCREEN_HEIGHT_PX * (1 - TOUCH_REGION)), SCREEN_WIDTH_PX,
177                 SCREEN_HEIGHT_PX);
178 
179         assertThat(bounds).isEqualTo(expected);
180 
181         mTouchHandler.onSessionStart(mTouchSession);
182         verify(mNotificationShadeWindowController).setForcePluginOpen(eq(true), any());
183         ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
184                 ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
185         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
186                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
187         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
188         verify(mTouchSession).registerInputListener(eventListenerCaptor.capture());
189 
190         // A touch within range at the bottom of the screen should trigger listening
191         assertThat(gestureListenerCaptor.getValue()
192                 .onScroll(Mockito.mock(MotionEvent.class),
193                         Mockito.mock(MotionEvent.class),
194                         1,
195                         2)).isTrue();
196     }
197 
198 
199     /**
200      * Ensures expansion only happens when touch down happens in valid part of the screen.
201      */
202     @Test
203     @EnableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
testSessionStart_doesNotModifyNotificationShadeWindow()204     public void testSessionStart_doesNotModifyNotificationShadeWindow() {
205         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
206 
207         verify(mRegion).union(mRectCaptor.capture());
208         final Rect bounds = mRectCaptor.getValue();
209 
210         final Rect expected = new Rect();
211 
212         expected.set(0, Math.round(SCREEN_HEIGHT_PX * (1 - TOUCH_REGION)), SCREEN_WIDTH_PX,
213                 SCREEN_HEIGHT_PX);
214 
215         assertThat(bounds).isEqualTo(expected);
216 
217         mTouchHandler.onSessionStart(mTouchSession);
218         verifyNoMoreInteractions(mNotificationShadeWindowController);
219     }
220 
221     @Test
222     @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects()223     public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() {
224         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
225                 new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX));
226         verify(mRegion).union(mRectCaptor.capture());
227         final Rect bounds = mRectCaptor.getValue();
228 
229         final Rect expected = new Rect();
230         final float minBouncerHeight =
231                 SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT;
232         final int minAllowableBottom = SCREEN_HEIGHT_PX - Math.round(minBouncerHeight);
233 
234         expected.set(0, minAllowableBottom, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
235 
236         assertThat(bounds).isEqualTo(expected);
237 
238         onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
239     }
240 
241     @Test
242     @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea()243     public void testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea() {
244         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
245                 new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX / 4));
246         verify(mRegion).union(mRectCaptor.capture());
247         final Rect bounds = mRectCaptor.getValue();
248 
249         final Rect expected = new Rect();
250         final int gestureAreaTop = SCREEN_HEIGHT_PX - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
251         expected.set(0, gestureAreaTop, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
252 
253         assertThat(bounds).isEqualTo(expected);
254         onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
255     }
256 
257     @Test
258     @DisableFlags(Flags.FLAG_COMMUNAL_BOUNCER_DO_NOT_MODIFY_PLUGIN_OPEN)
testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea()259     public void testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea() {
260         final int normalSwipeAreaTop = SCREEN_HEIGHT_PX
261                 - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
262         final int minimumSwipeAreaTop = SCREEN_HEIGHT_PX
263                 - Math.round(SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT);
264 
265         Rect exclusionRect = new Rect(0, 0, SCREEN_WIDTH_PX,
266                 (normalSwipeAreaTop + minimumSwipeAreaTop) / 2);
267 
268         mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, exclusionRect);
269 
270         verify(mRegion).union(mRectCaptor.capture());
271 
272         final Rect bounds = mRectCaptor.getValue();
273         final Rect expected = new Rect();
274 
275         final int expectedSwipeAreaBottom = exclusionRect.bottom;
276         expected.set(0, expectedSwipeAreaBottom, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
277 
278         assertThat(bounds).isEqualTo(expected);
279 
280         onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
281     }
282 
onSessionStartHelper(BouncerSwipeTouchHandler touchHandler, TouchHandler.TouchSession touchSession, NotificationShadeWindowController notificationShadeWindowController)283     private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler,
284             TouchHandler.TouchSession touchSession,
285             NotificationShadeWindowController notificationShadeWindowController) {
286         touchHandler.onSessionStart(touchSession);
287         verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any());
288         ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
289                 ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
290         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
291                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
292         verify(touchSession).registerGestureListener(gestureListenerCaptor.capture());
293         verify(touchSession).registerInputListener(eventListenerCaptor.capture());
294 
295         // A touch within range at the bottom of the screen should trigger listening
296         assertThat(gestureListenerCaptor.getValue()
297                 .onScroll(Mockito.mock(MotionEvent.class),
298                         Mockito.mock(MotionEvent.class),
299                         1,
300                         2)).isTrue();
301     }
302 
303     /**
304      * Makes sure swiping down doesn't change the expansion amount.
305      */
306     @Test
307     @DisableFlags(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING)
testSwipeDown_doesNotSetExpansion()308     public void testSwipeDown_doesNotSetExpansion() {
309         mTouchHandler.onSessionStart(mTouchSession);
310         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
311                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
312         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
313 
314         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
315 
316         final float percent = .15f;
317         final float distanceY = SCREEN_HEIGHT_PX * percent;
318 
319         // Swiping down near the bottom of the screen where the touch initiation region is.
320         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
321                 0, SCREEN_HEIGHT_PX - distanceY, 0);
322         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
323                 0, SCREEN_HEIGHT_PX, 0);
324 
325         assertThat(gestureListener.onScroll(event1, event2, 0, -distanceY)).isTrue();
326 
327         verify(mScrimController, never()).expand(any());
328     }
329 
330     /**
331      * Makes sure swiping down when bouncer initially hidden doesn't change the expansion amount.
332      */
333     @Test
334     @EnableFlags(Flags.FLAG_DREAM_OVERLAY_BOUNCER_SWIPE_DIRECTION_FILTERING)
testSwipeDown_whenBouncerInitiallyHidden_doesNotSetExpansion_directionFiltering()335     public void testSwipeDown_whenBouncerInitiallyHidden_doesNotSetExpansion_directionFiltering() {
336         mTouchHandler.onSessionStart(mTouchSession);
337         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
338                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
339         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
340 
341         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
342 
343         final float percent = .15f;
344         final float distanceY = SCREEN_HEIGHT_PX * percent;
345 
346         // Swiping down near the bottom of the screen where the touch initiation region is.
347         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
348                 0, SCREEN_HEIGHT_PX - distanceY, 0);
349         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
350                 0, SCREEN_HEIGHT_PX, 0);
351 
352         assertThat(gestureListener.onScroll(event1, event2, 0, -distanceY)).isFalse();
353 
354         verify(mScrimController, never()).expand(any());
355     }
356 
357     /**
358      * Makes sure the expansion amount is proportional to (1 - scroll).
359      */
360     @Test
testSwipeUp_setsCorrectExpansionAmount()361     public void testSwipeUp_setsCorrectExpansionAmount() {
362         mTouchHandler.onSessionStart(mTouchSession);
363         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
364                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
365         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
366 
367         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
368 
369         verifyScroll(.3f, gestureListener);
370         verifyScroll(.7f, gestureListener);
371     }
372 
373     /**
374      * Verifies that swiping up when the lock pattern is not secure dismissed dream and consumes
375      * the gesture.
376      */
377     @Test
testSwipeUp_keyguardNotSecure_doesNotExpand()378     public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
379         when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
380         mTouchHandler.onSessionStart(mTouchSession);
381         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
382                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
383         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
384 
385         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
386 
387         final float distanceY = SCREEN_HEIGHT_PX * 0.3f;
388         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
389                 0, SCREEN_HEIGHT_PX, 0);
390         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
391                 0, SCREEN_HEIGHT_PX - distanceY, 0);
392 
393         reset(mScrimController);
394 
395         // Scroll gesture is consumed.
396         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
397                 .isTrue();
398         // We should not expand since the keyguard is not secure
399         verify(mScrimController, never()).expand(any());
400         // Since we are swiping up, we should wake from dreams.
401         verify(mCentralSurfaces).awakenDreams();
402     }
403 
404     /**
405      * Verifies that swiping down when the lock pattern is not secure does not dismiss the dream.
406      */
407     @Test
testSwipeDown_keyguardNotSecure_doesNotExpand()408     public void testSwipeDown_keyguardNotSecure_doesNotExpand() {
409         when(mLockPatternUtils.isSecure(CURRENT_USER_INFO.id)).thenReturn(false);
410         mTouchHandler.onSessionStart(mTouchSession);
411         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
412                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
413         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
414 
415         final OnGestureListener gestureListener = gestureListenerCaptor.getValue();
416 
417         final float distanceY = SCREEN_HEIGHT_PX * 0.3f;
418         // Swiping down near the bottom of the screen where the touch initiation region is.
419         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
420                 0, SCREEN_HEIGHT_PX - distanceY, 0);
421         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
422                 0, SCREEN_HEIGHT_PX, 0);
423 
424         reset(mScrimController);
425 
426         // Scroll gesture is not consumed.
427         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
428                 .isTrue();
429         // We should not expand since the keyguard is not secure
430         verify(mScrimController, never()).expand(any());
431         // Since we are swiping down, we should not dismiss the dream.
432         verify(mCentralSurfaces, never()).awakenDreams();
433     }
434 
verifyScroll(float percent, OnGestureListener gestureListener)435     private void verifyScroll(float percent,
436             OnGestureListener gestureListener) {
437         final float distanceY = SCREEN_HEIGHT_PX * percent;
438 
439         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
440                 0, SCREEN_HEIGHT_PX, 0);
441         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
442                 0, SCREEN_HEIGHT_PX - distanceY, 0);
443 
444         reset(mScrimController);
445         assertThat(gestureListener.onScroll(event1, event2, 0,
446                 distanceY))
447                 .isTrue();
448 
449         // Ensure only called once
450         verify(mScrimController).expand(any());
451 
452         final float expansion = 1 - percent;
453 
454         // Ensure correct expansion passed in.
455         ShadeExpansionChangeEvent event =
456                 new ShadeExpansionChangeEvent(
457                         expansion, /* expanded= */ false, /* tracking= */ true);
458         verify(mScrimController).expand(event);
459     }
460 
461     /**
462      * Tests that ending an upward swipe before the set threshold leads to bouncer collapsing down.
463      */
464     @Test
testSwipeUpPositionBelowThreshold_collapsesBouncer()465     public void testSwipeUpPositionBelowThreshold_collapsesBouncer() {
466         final float swipeUpPercentage = .3f;
467         final float expansion = 1 - swipeUpPercentage;
468         // The upward velocity is ignored.
469         final float velocityY = -1;
470         swipeToPosition(swipeUpPercentage, velocityY);
471 
472         verify(mValueAnimatorCreator).create(eq(expansion),
473                 eq(KeyguardBouncerConstants.EXPANSION_HIDDEN));
474         verify(mValueAnimator, never()).addListener(any());
475 
476         verify(mFlingAnimationUtilsClosing).apply(eq(mValueAnimator),
477                 eq(SCREEN_HEIGHT_PX * expansion),
478                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_HIDDEN),
479                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
480         verify(mValueAnimator).start();
481         verify(mUiEventLogger, never()).log(any());
482     }
483 
484     /**
485      * Tests that ending an upward swipe above the set threshold will continue the expansion.
486      */
487     @Test
testSwipeUpPositionAboveThreshold_expandsBouncer()488     public void testSwipeUpPositionAboveThreshold_expandsBouncer() {
489         final float swipeUpPercentage = .7f;
490         final float expansion = 1 - swipeUpPercentage;
491         // The downward velocity is ignored.
492         final float velocityY = 1;
493         swipeToPosition(swipeUpPercentage, velocityY);
494 
495         verify(mValueAnimatorCreator).create(eq(expansion),
496                 eq(KeyguardBouncerConstants.EXPANSION_VISIBLE));
497 
498         ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor =
499                 ArgumentCaptor.forClass(AnimatorListenerAdapter.class);
500         verify(mValueAnimator).addListener(endAnimationListenerCaptor.capture());
501         AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue();
502 
503         verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion),
504                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE),
505                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
506         verify(mValueAnimator).start();
507         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED);
508 
509         endAnimationListener.onAnimationEnd(mValueAnimator);
510         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
511     }
512 
513     /**
514      * Tests that swiping up with a speed above the set threshold will continue the expansion.
515      */
516     @Test
testSwipeUpVelocityAboveMin_expandsBouncer()517     public void testSwipeUpVelocityAboveMin_expandsBouncer() {
518         when(mFlingAnimationUtils.getMinVelocityPxPerSecond()).thenReturn((float) 0);
519 
520         // The ending position below the set threshold is ignored.
521         final float swipeUpPercentage = .3f;
522         final float expansion = 1 - swipeUpPercentage;
523         final float velocityY = -1;
524         swipeToPosition(swipeUpPercentage, velocityY);
525 
526         verify(mValueAnimatorCreator).create(eq(expansion),
527                 eq(KeyguardBouncerConstants.EXPANSION_VISIBLE));
528 
529         ArgumentCaptor<AnimatorListenerAdapter> endAnimationListenerCaptor =
530                 ArgumentCaptor.forClass(AnimatorListenerAdapter.class);
531         verify(mValueAnimator).addListener(endAnimationListenerCaptor.capture());
532         AnimatorListenerAdapter endAnimationListener = endAnimationListenerCaptor.getValue();
533 
534         verify(mFlingAnimationUtils).apply(eq(mValueAnimator), eq(SCREEN_HEIGHT_PX * expansion),
535                 eq(SCREEN_HEIGHT_PX * KeyguardBouncerConstants.EXPANSION_VISIBLE),
536                 eq(velocityY), eq((float) SCREEN_HEIGHT_PX));
537         verify(mValueAnimator).start();
538         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_SWIPED);
539 
540         endAnimationListener.onAnimationEnd(mValueAnimator);
541         verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
542     }
543 
544     @Test
testTouchSessionOnRemovedCalledTwice()545     public void testTouchSessionOnRemovedCalledTwice() {
546         mTouchHandler.onSessionStart(mTouchSession);
547         ArgumentCaptor<TouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
548                 ArgumentCaptor.forClass(TouchHandler.TouchSession.Callback.class);
549         verify(mTouchSession).registerCallback(onRemovedCallbackCaptor.capture());
550         onRemovedCallbackCaptor.getValue().onRemoved();
551         onRemovedCallbackCaptor.getValue().onRemoved();
552     }
553 
swipeToPosition(float percent, float velocityY)554     private void swipeToPosition(float percent, float velocityY) {
555         Mockito.clearInvocations(mTouchSession);
556         mTouchHandler.onSessionStart(mTouchSession);
557         ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
558                 ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
559         verify(mTouchSession).registerGestureListener(gestureListenerCaptor.capture());
560         ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor =
561                 ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
562         verify(mTouchSession).registerInputListener(inputEventListenerCaptor.capture());
563 
564         when(mVelocityTracker.getYVelocity()).thenReturn(velocityY);
565 
566         final float distanceY = SCREEN_HEIGHT_PX * percent;
567 
568         final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
569                 0, SCREEN_HEIGHT_PX, 0);
570         final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
571                 0, SCREEN_HEIGHT_PX - distanceY, 0);
572 
573         assertThat(gestureListenerCaptor.getValue().onScroll(event1, event2, 0,
574                 distanceY))
575                 .isTrue();
576 
577         final MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP,
578                 0, 0, 0);
579 
580         inputEventListenerCaptor.getValue().onInputEvent(upEvent);
581     }
582 }
583