1 /*
2  * Copyright 2023 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 android.widget;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 
22 import android.view.MotionEvent;
23 import android.widget.flags.FakeFeatureFlagsImpl;
24 import android.widget.flags.Flags;
25 
26 import androidx.test.core.app.ApplicationProvider;
27 import androidx.test.ext.junit.runners.AndroidJUnit4;
28 import androidx.test.filters.SmallTest;
29 
30 import org.junit.Before;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 
34 @SmallTest
35 @RunWith(AndroidJUnit4.class)
36 public class DifferentialMotionFlingHelperTest {
37     private int mMinVelocity = 0;
38     private int mMaxVelocity = Integer.MAX_VALUE;
39     /** A fake velocity value that's going to be returned from the velocity provider. */
40     private float mVelocity;
41     private boolean mVelocityCalculated;
42 
43     private final DifferentialMotionFlingHelper.DifferentialVelocityProvider mVelocityProvider =
44             (vt, event, axis) -> {
45                 mVelocityCalculated = true;
46                 return mVelocity;
47             };
48 
49     private final DifferentialMotionFlingHelper.FlingVelocityThresholdCalculator
50             mVelocityThresholdCalculator =
51                     (ctx, buffer, event, axis) -> {
52                         buffer[0] = mMinVelocity;
53                         buffer[1] = mMaxVelocity;
54                     };
55 
56     private final TestDifferentialMotionFlingTarget mFlingTarget =
57             new TestDifferentialMotionFlingTarget();
58 
59     private final FakeFeatureFlagsImpl mFakeWidgetFeatureFlags = new FakeFeatureFlagsImpl();
60 
61     private DifferentialMotionFlingHelper mFlingHelper;
62 
63     @Before
setUp()64     public void setUp() throws Exception {
65         mFlingHelper = new DifferentialMotionFlingHelper(
66                 ApplicationProvider.getApplicationContext(),
67                 mFlingTarget,
68                 mVelocityThresholdCalculator,
69                 mVelocityProvider,
70                 mFakeWidgetFeatureFlags);
71         mFakeWidgetFeatureFlags.setFlag(
72                 Flags.FLAG_ENABLE_PLATFORM_WIDGET_DIFFERENTIAL_MOTION_FLING, true);
73     }
74 
75     @Test
deviceDoesNotSupportFling_noVelocityCalculated()76     public void deviceDoesNotSupportFling_noVelocityCalculated() {
77         mMinVelocity = Integer.MAX_VALUE;
78         mMaxVelocity = Integer.MIN_VALUE;
79 
80         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
81 
82         assertFalse(mVelocityCalculated);
83     }
84 
85     @Test
flingVelocityOppositeToPrevious_stopsOngoingFling()86     public void flingVelocityOppositeToPrevious_stopsOngoingFling() {
87         deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, 50);
88         deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, -10);
89 
90         // One stop on the initial event, and second stop due to opposite velocities.
91         assertEquals(2, mFlingTarget.mNumStops);
92     }
93 
94     @Test
flingParamsChanged_stopsOngoingFling()95     public void flingParamsChanged_stopsOngoingFling() {
96         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 50);
97         deliverEventWithVelocity(createRotaryEncoderEvent(), MotionEvent.AXIS_SCROLL, 10);
98 
99         // One stop on the initial event, and second stop due to changed axis/source.
100         assertEquals(2, mFlingTarget.mNumStops);
101     }
102 
103     @Test
positiveFlingVelocityTooLow_doesNotGenerateFling()104     public void positiveFlingVelocityTooLow_doesNotGenerateFling() {
105         mMinVelocity = 50;
106         mMaxVelocity = 100;
107         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 20);
108 
109         assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
110     }
111 
112     @Test
negativeFlingVelocityTooLow_doesNotGenerateFling()113     public void negativeFlingVelocityTooLow_doesNotGenerateFling() {
114         mMinVelocity = 50;
115         mMaxVelocity = 100;
116         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -20);
117 
118         assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
119     }
120 
121     @Test
positiveFlingVelocityAboveMinimum_generateFlings()122     public void positiveFlingVelocityAboveMinimum_generateFlings() {
123         mMinVelocity = 50;
124         mMaxVelocity = 100;
125         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
126 
127         assertEquals(60, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
128     }
129 
130     @Test
negativeFlingVelocityAboveMinimum_generateFlings()131     public void negativeFlingVelocityAboveMinimum_generateFlings() {
132         mMinVelocity = 50;
133         mMaxVelocity = 100;
134         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -60);
135 
136         assertEquals(-60, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
137     }
138 
139     @Test
positiveFlingVelocityAboveMaximum_velocityClamped()140     public void positiveFlingVelocityAboveMaximum_velocityClamped() {
141         mMinVelocity = 50;
142         mMaxVelocity = 100;
143         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 3000);
144 
145         assertEquals(100, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
146     }
147 
148     @Test
flingFeatureFlagDisabled_noFlingCalculation()149     public void flingFeatureFlagDisabled_noFlingCalculation() {
150         mFakeWidgetFeatureFlags.setFlag(
151                 Flags.FLAG_ENABLE_PLATFORM_WIDGET_DIFFERENTIAL_MOTION_FLING, false);
152         mMinVelocity = 50;
153         mMaxVelocity = 100;
154         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, 60);
155 
156         assertFalse(mVelocityCalculated);
157         assertEquals(0, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
158     }
159 
160     @Test
negativeFlingVelocityAboveMaximum_velocityClamped()161     public void negativeFlingVelocityAboveMaximum_velocityClamped() {
162         mMinVelocity = 50;
163         mMaxVelocity = 100;
164         deliverEventWithVelocity(createPointerEvent(), MotionEvent.AXIS_VSCROLL, -3000);
165 
166         assertEquals(-100, mFlingTarget.mLastFlingVelocity, /* delta= */ 0);
167     }
168 
createRotaryEncoderEvent()169     private MotionEvent createRotaryEncoderEvent() {
170         return MotionEventUtils.createRotaryEvent(-2);
171     }
172 
createPointerEvent()173     private MotionEvent createPointerEvent() {
174         return MotionEventUtils.createGenericPointerEvent(/* hScroll= */ 0, /* vScroll= */ -1);
175 
176     }
177 
deliverEventWithVelocity(MotionEvent ev, int axis, float velocity)178     private void deliverEventWithVelocity(MotionEvent ev, int axis, float velocity) {
179         mVelocity = velocity;
180         mFlingHelper.onMotionEvent(ev, axis);
181         ev.recycle();
182     }
183 
184     private static class TestDifferentialMotionFlingTarget
185             implements DifferentialMotionFlingHelper.DifferentialMotionFlingTarget {
186         float mLastFlingVelocity = 0;
187         int mNumStops = 0;
188 
189         @Override
startDifferentialMotionFling(float velocity)190         public boolean startDifferentialMotionFling(float velocity) {
191             mLastFlingVelocity = velocity;
192             return true;
193         }
194 
195         @Override
stopDifferentialMotionFling()196         public void stopDifferentialMotionFling() {
197             mNumStops++;
198         }
199 
200         @Override
getScaledScrollFactor()201         public float getScaledScrollFactor() {
202             return 1;
203         }
204     }
205 }
206