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